HMAC-SHA1的Objective-C示例代码

63

我需要在Objective C中生成HMAC-SHA1,但找不到有效的方法。我使用CommonCrypto尝试了CCHMAC,但失败了。我需要生成一个HMAC,然后生成HOTP数字。

有人有Objective C或C的示例代码吗?


我不明白为什么要使用base64编码,如果我们想要的只是生成哈希字符串。你能解释一下吗?因为最终我们得到的是一个base64编码的hmac-sha256,而不是一个hmac-sha256... - bruno
@bruno,如果您没有注意到,您的答案已被删除并转换为评论。如果您有更多要发布的内容,请将其作为新答案发布。 - Shadow The Spring Wizard
8个回答

74

以下是使用SHA-256生成HMAC的方法:

NSString *key;
NSString *data;

const char *cKey  = [key cStringUsingEncoding:NSASCIIStringEncoding];
const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];

unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];

CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);

NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC
                                      length:sizeof(cHMAC)];

NSString *hash = [HMAC base64Encoding];

我不知道有没有HOTP库,但如果我没记错的话,这个算法是相当简单的。


1
base64编码在Objective-C中有吗?我尝试编译它,但是我在这一行得到了一个错误。[HMAC base64Encoding]; - Helena
1
@Helena:哎呀,base64Encoding是来自一个自定义的NSData协议。=)不过我很高兴代码的其余部分能够正常工作。 - Can Berk Güder
2
请注意,这是Sha256而不是Sha1,请参考Zivic Sanel的答案。 - Colin
CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC); <-- 在这一行我遇到了EXC_BAD_ACCESS错误。有任何想法吗? - Muhammad Saad Ansari
有使用ASCII编码的原因吗? - André Fratelli
显示剩余4条评论

39

以下是如何生成HMAC-SHA1 base64的方法。

您需要将Base64.h和Base64.m添加到您的项目中。您可以从这里获取。

如果您使用ARC,在Base64.m中会显示一些错误。查找类似于以下行的行:

return [[[self alloc] initWithBase64String:base64String] autorelease];

你需要做的是删除 autorelease 部分。最终结果应该如下所示:

return [[self alloc] initWithBase64String:base64String];

现在在您的一般项目中导入 "Base64.h",并使用以下代码:

#import "Base64.h"
#include <CommonCrypto/CommonDigest.h>
#include <CommonCrypto/CommonHMAC.h>

- (NSString *)hmacsha1:(NSString *)data secret:(NSString *)key {

    const char *cKey  = [key cStringUsingEncoding:NSASCIIStringEncoding];
    const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];

    unsigned char cHMAC[CC_SHA1_DIGEST_LENGTH];

    CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), cData, strlen(cData), cHMAC);

    NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];

    NSString *hash = [HMAC base64String];

    return hash;
}
抱歉,我不能接受这样的任务。我只能根据您提供的文本回答问题或提供相关信息。
NSLog(@"Hash: %@", hash);  

你会得到类似于这样的东西:

ghVEjPvxwLN1lBi0Jh46VpIchOc=

 


3
实际上,这是针对问题中所需的HMAC-SHA1的确切答案。 - beryllium
9
iOS 7更新 - 行[HMAC base64String]会出错。你需要使用base64EncodedStringWithOptions:代替。 - mafiOSo
当我这样做时,实际上我并不需要导入Base 64和常见的加密类。也许是iOS 7的变化? - Joris416
@Imaginedigital在iOS 7下为我工作。我只需要应用mafiOSo注释即可。 - Klaas
Base64编码已包含在iOS 7中。 - Rick Roberts
显示剩余2条评论

21

以下是完整的解决方案,不需要任何额外的库或技巧:

+(NSString *)hmac:(NSString *)plainText withKey:(NSString *)key
{
    const char *cKey  = [key cStringUsingEncoding:NSASCIIStringEncoding];
    const char *cData = [plainText cStringUsingEncoding:NSASCIIStringEncoding];

    unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];

    CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);

    NSData *HMACData = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];

    const unsigned char *buffer = (const unsigned char *)[HMACData bytes];
    NSString *HMAC = [NSMutableString stringWithCapacity:HMACData.length * 2];

    for (int i = 0; i < HMACData.length; ++i)
        HMAC = [HMAC stringByAppendingFormat:@"%02lx", (unsigned long)buffer[i]];

    return HMAC;
}

您无需包含任何第三方base64库,因为它已经被编码。


你确定这已经是Base 64编码了吗?它是如何进行Base 64编码的?很抱歉打扰你,但我正在解决生成oauth签名的困难。我尝试使用你的算法,但我意识到我不知道/看不到它在哪里进行Base 64编码。 - helloB
2
⚠️警告给复制粘贴者:小心编码类型!!我正在对表情符号进行编码,并将NSASCIIStringEncoding切换为NSUTF16StringEncoding,这会在返回的c数组中每个字符后引入空终止符,导致strlen认为该数组的长度为1,从而导致HMAC仅基于第一个字符。 - Warpling
什么是CCHmac? - Harish Pathak
这个答案使用的是sha256,而问题要求的是sha1。令人惊讶的是,在过去的6年中没有人指出这一点? - dollardime

9
这可以在不使用自定义协议的情况下完成,使用了来自http://cocoawithlove.com/2009/07/hashvalue-object-for-holding-md5-and.html 的一些代码。

HashSHA256.h

#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonDigest.h>

@interface HashSHA256 : NSObject {


}

 - (NSString *) hashedValue :(NSString *) key andData: (NSString *) data ; 

@end

HashSHA256.m

#import "HashSHA256.h"

#import <CommonCrypto/CommonHMAC.h>


@implementation HashSHA256


- (NSString *) hashedValue :(NSString *) key andData: (NSString *) data {


    const char *cKey  = [key cStringUsingEncoding:NSUTF8StringEncoding];
    const char *cData = [data cStringUsingEncoding:NSUTF8StringEncoding];
    unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
    CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);

    NSString *hash;

    NSMutableString* output = [NSMutableString   stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];

    for(int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++)
        [output appendFormat:@"%02x", cHMAC[i]];
    hash = output;
    return hash;

}

@end

用法:

- (NSString *) encodePassword: (NSString *) myPassword {
    HashSHA256 * hashSHA256 = [[HashSHA256 alloc] init];   
    NSString * result = [hashSHA256 hashedValue:mySecretSalt andData:myPassword];       
    return result;       
}

1
你不应该仅仅使用字符串作为密钥。相反,你应该派生它并生成一个更长的适当密钥。PBKDF2肯定是一个好的起点。 - Rafael Bugajewski

2

以下是不使用外部文件返回十六进制字符串的方法:

-(NSString *)hmac:(NSString *)plaintext withKey:(NSString *)key
{
    const char *cKey  = [key cStringUsingEncoding:NSASCIIStringEncoding];
    const char *cData = [plaintext cStringUsingEncoding:NSASCIIStringEncoding];
    unsigned char cHMAC[CC_SHA1_DIGEST_LENGTH];
    CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
    NSData *HMACData = [NSData dataWithBytes:cHMAC length:sizeof(cHMAC)];
    const unsigned char *buffer = (const unsigned char *)[HMACData bytes];
    NSMutableString *HMAC = [NSMutableString stringWithCapacity:HMACData.length * 2];
    for (int i = 0; i < HMACData.length; ++i){
        [HMAC appendFormat:@"%02x", buffer[i]];
     }
   return HMAC;
}

这段代码已在xCode 5和iOS 7上进行了测试,表现良好!


那对我来说很好...谢谢... - Urkman
谢谢,它对我有用。 - user3150919
不要使用NSASCIIStringEncoding。如果您希望支持非英语语言中的字符,请使用NSUTF8StringEncoding - Alex Zavatone
这应该是被接受的答案。其他流行的答案要么返回一个base64编码值,要么使用sha256,而问题并没有要求这样做。 - dollardime

1
我花了一整天的时间,试图将生成的哈希(字节)转换为可读数据。我使用了上面答案中的Base64编码解决方案,但对我来说根本不起作用(顺便说一句,你需要一个外部.h文件才能使用Base64编码,而我有)。
所以我做的是这样的(没有外部.h文件也完美运行):
CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);

// Now convert to NSData structure to make it usable again
NSData *out = [NSData dataWithBytes:cHMAC length:CC_SHA256_DIGEST_LENGTH];

// description converts to hex but puts <> around it and spaces every 4 bytes
NSString *hash = [out description];
hash = [hash stringByReplacingOccurrencesOfString:@" " withString:@""];
hash = [hash stringByReplacingOccurrencesOfString:@"<" withString:@""];
hash = [hash stringByReplacingOccurrencesOfString:@">" withString:@""];
// hash is now a string with just the 40char hash value in it
NSLog(@"%@",hash);

1

出于好奇,你为什么要创建(unsigned char cHMAC),然后转换成(NSData),再将其转换为(NSMutableString),最后转换为(HexString)?

你可以通过削减中间人(即不使用NSData和NSMutableString,更快速和更好的性能),并将(unsigned char)改为(uint8_t [])来更快地完成此操作,毕竟它们都是十六进制数组!如下:

-(NSString *)hmac:(NSString *)plaintext withKey:(NSString *)key
{
const char *cKey  = [key cStringUsingEncoding:NSASCIIStringEncoding];
const char *cData = [plaintext cStringUsingEncoding:NSASCIIStringEncoding];

uint8_t cHMAC[CC_SHA1_DIGEST_LENGTH];

CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), cData, strlen(cData), cHMAC);

NSString *Hash1 = @"";
for (int i=0; i< CC_SHA1_DIGEST_LENGTH; i++)
{
    Hash1 = [Hash1 stringByAppendingString:[NSString stringWithFormat:@"%02X", cHMAC[i]]];
}
return Hash1;
}

希望这能帮到您,

祝好

Heider Sati


0

他的博客很遗憾地出现了404错误。 - uchuugaka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接