Question

I know there are a lot of posts about converting NSData to NSString, NSData to Base64 encoded string, generating HMAC etc. but none that seem to answer how to generate matching Base64 encoded HMAC-SHA1 strings in iOS and Rails.

Using the code below, the signatures do not match.

iOS code:

NSString *secret = @"xxx";
NSString *data = @"http://someurl?someparams";
const char *cKey = [secret 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 *signature = [HMAC base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];

Rails code:

secret = "xxx";
data = "http://someurl?someparams";
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha1'), secret.encode("ASCII"), data.encode("ASCII"))
signature = Base64.encode64(hmac)
puts "HMAC #{hmac}"
puts "Signature #{signature}"

iOS output:

HMAC <05651433 c9a3d449 5816ded7 80bef87f dc903e4a> 
Signature BWUUM8mj1ElYFt7XgL74f9yQPko=

Rails output:

HMAC 05651433c9a3d4495816ded780bef87fdc903e4a
Signature MDU2NTE0MzNjOWEzZDQ0OTU4MTZkZWQ3ODBiZWY4N2ZkYzkwM2U0YQ==

Using following code I can get them to match. But it feels hacky (use of description and string replacements in iOS side and chomp in Rails):

iOS code:

NSString *secret = @"xxx";
NSString *data = @"http://someurl?someparams";
const char *cKey = [secret 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 *HMACStr = [[HMAC description] stringByReplacingOccurrencesOfString:@"<" withString:@""];
HMACStr = [HMACStr stringByReplacingOccurrencesOfString:@">" withString:@""];
HMACStr = [HMACStr stringByReplacingOccurrencesOfString:@" " withString:@""];
HMAC = [HMACStr dataUsingEncoding:NSASCIIStringEncoding];
NSString *signature = [HMAC base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
NSLog(@"HMAC %@", [HMAC description]);
NSLog(@"Signature %@", signature);

Rails code:

secret = "xxx"
data = "http://someurl?someparams"
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha1'), secret.encode("ASCII"), data.encode("ASCII"))
signature = Base64.encode64(hmac).chomp
puts "HMAC #{hmac}"
puts "Signature #{signature}"

iOS output:

HMAC <30353635 31343333 63396133 64343439 35383136 64656437 38306265 66383766 64633930 33653461> 
Signature MDU2NTE0MzNjOWEzZDQ0OTU4MTZkZWQ3ODBiZWY4N2ZkYzkwM2U0YQ== 

Rails output:

HMAC 05651433c9a3d4495816ded780bef87fdc903e4a
Signature MDU2NTE0MzNjOWEzZDQ0OTU4MTZkZWQ3ODBiZWY4N2ZkYzkwM2U0YQ==

How can I generate matching signatures without using the hacks?

Was it helpful?

Solution

I am not familiar with Rails, but it seems to me that

hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha1'), secret.encode("ASCII"), data.encode("ASCII"))

computes the digest as a hex string, whereas

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)];

computes the digest as a NSData object, which is just a wrapper for a sequence of arbitrary bytes.

You probably should change the Rails code to compute the digest as sequence of bytes as well, instead of a hex string (perhaps using digest instead of hexdigest?)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top