I would like to sign a request with HMAC SHA512, but I seem to mess up encoding and decoding from and to NSData and NSString. I desperately tried to figure out what is wrong, but I just don't seem to get it right.
PSEUDOCODE:
function hmac_512(msg, sec) {
sec = Base64Decode(sec);
result = hmac(msg, sec, sha512);
return Base64Encode(result);
}
secret = "7pgj8Dm6";
message = "Test\0Message";
result = hmac_512(message, secret);
if (result == "69H45OZkKcmR9LOszbajUUPGkGT8IqasGPAWqW/1stGC2Mex2qhIB6aDbuoy7eGfMsaZiU8Y0lO3mQxlsWNPrw==")
print("Success!");
else
printf("Error: %s", result);
My implementation:
+(void)doSomeMagic{
NSString *message = #"Test\0Message";
NSString *signedRequest = [self signRequestForParameterString:message];
//checking against CORRECT (from JAVA equivalent) signed request
if ([signedRequest isEqualToString:#"69H45OZkKcmR9LOszbajUUPGkGT8IqasGPAWqW/1stGC2Mex2qhIB6aDbuoy7eGfMsaZiU8Y0lO3mQxlsWNPrw==" ])
NSLog(#"Success!");
else
NSLog(#"Error!");
}
Here is the signing method:
+(NSString *)signRequestForParameterString:(NSString*)paramStr{
NSString *secret = #"7pgj8Dm6";
// secret is base64 encoded, so I decode it
NSData *decodedSecret = [secret base64DecodedData];
NSString *decodedSecretString = [NSString stringWithUTF8String:[decodedSecret bytes]];
NSData *data = [paramStr dataUsingEncoding:NSUTF8StringEncoding];
NSString *dataString = [NSString stringWithUTF8String:[data bytes]];
return [self generateHMACSHA512Hash:decodedSecretString data:dataString];
}
Here is the hashing function:
+(NSString *)generateHMACSHA512Hash:(NSString *)key data:(NSString *)data{
const char *cKey = [key cStringUsingEncoding:NSASCIIStringEncoding];
const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];
unsigned char cHMAC[CC_SHA512_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA512, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC
length:sizeof(cHMAC)];
NSString *hash = [HMAC base64EncodedString];
return hash;
}
I am pretty sure it is due to the encoding of the strings (decodedSecretString and dataString). decodedSecretString (decoded base64) after decoding is encoded in ASCII. However, when I call the hashing method, I encode it in ascii again, which will result in a null error. Everything is confusing me right now.
Your secret doesn't decode to a valid UTF-8 string, and Java allows NUL bytes in strings, but when you're converting "Test\0Message" to a C string and using strlen, its length is 4.
Something like this should work:
+(NSString *)signRequestForParameterString:(NSString*)paramStr{
NSString *secret = #"7pgj8Dm6";
NSData *data = [paramStr dataUsingEncoding:NSUTF8StringEncoding];
return [self generateHMACSHA512Hash:[secret base64DecodedData] data:data];
}
+(NSString *)generateHMACSHA512Hash:(NSData *)key data:(NSData *)data{
unsigned char cHMAC[CC_SHA512_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA512, key.bytes, key.length, data.bytes, data.length, cHMAC);
NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
return [HMAC base64EncodedString];
}
When doing HMAC or other cryptographic functions, you should build up some fundamental methods/functions that don't deal with strings first. Then you can create wrapper methods that decode/encode string data or digests in a convenient way.
+ (NSData *)dataBySigningData:(NSData *)data withKey:(NSData *)key
{
unsigned char cHMAC[CC_SHA512_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA512, [key bytes], [key length], [data bytes], [data lenght], cHMAC);
return [[NSData alloc] initWithBytes:cHMAC length:CC_SHA512_DIGEST_LENGTH];
}
+ (NSData *)dataBySigningMessage:(NSString *)message withKey:(NSData *)key
{
return [self dataBySigningData:[message dataUsingEncoding:NSUTF8StringEncoding]
withKey:[key dataUsingEncoding:NSUTF8StringEncoding]];
}
(Note: this code is not tested, just hacked together in a text editor)
Don't worry about the string representation of your key or data. Then you can go from there, e.g. getting the base64 encoding of the digest.
Cryptographic functions DO NOT CARE about strings or text encodings. They care about bytes. Strings (in C, since they are null-terminated) are a mere subset of what can be represented in data. So it would be severely limiting to deal with strings.
Related
I am trying to encrypt a string with 'AES-128 CBC with IV'. Here is the input parameter and expected output:
Key:
000102030405060708090A0B0C0D0E0F
IV:
00102030405060708090A0B0C0D0E0F0
Input data:
EA010B23CDA9B16F0001020304050607
Output:
B773C36749E87D3F8FED98FE52026A15
I have verified the output on this web site:
http://extranet.cryptomathic.com/aescalc/index?key=000102030405060708090A0B0C0D0E0F&iv=00102030405060708090A0B0C0D0E0F0&input=EA010B23CDA9B16F0001020304050607&mode=cbc&action=Encrypt&output=B773C36749E87D3F8FED98FE52026A15
How to encrypt a string with AES-128 CBC with IV in objective C? (With same result as http://extranet.cryptomathic.com/aescalc) I am trying to get the encrypted string - B773C36749E87D3F8FED98FE52026A15 , but no luck.
I have tried to use this library for the encryption: https://github.com/Pakhee/Cross-platform-AES-encryption/tree/master/iOS
Here is my objective c code:
NSString* data = #"EA010B23CDA9B16F0001020304050607";
NSString* key = #"000102030405060708090A0B0C0D0E0F";
NSString* iv = #"00102030405060708090A0B0C0D0E0F0";
NSData *encryptedData = [[StringEncryption alloc] encrypt:[#"EA010B23CDA9B16F0001020304050607" dataUsingEncoding:NSUTF8StringEncoding] key:key iv:iv];
NSLog(#"encryptedData %#", encryptedData);
The output of encryptedData is:
<68f8ed75 e79f2ba2 c80e67a2 f0c84b7a c4b07fd1 59e937e5 14644cba c0ddb60c 40502375 7798e7a1 58bd05a5 b3d9e7bd>
I expect the value of *encryptedData should be <42373733 43333637 34394538 37443346 38464544 39384645 35323032 36413135>, which is hex of B773C36749E87D3F8FED98FE52026A15
I have tried another library - https://github.com/dev5tec/FBEncryptor
NSData* _data = [data dataUsingEncoding:NSUTF8StringEncoding];
NSData* _key = [key dataUsingEncoding:NSUTF8StringEncoding];
NSData* _iv = [iv dataUsingEncoding:NSUTF8StringEncoding];
NSData *encryptedData2 = [FBEncryptorAES encryptData:_data key:_key iv:_iv];
NSLog(#"encryptedData2 = %#", encryptedData2);
Output is <2beea977 aef69eb1 ed9f6dd0 7bf5f1ce d1e5df46 2cbf8465 773f122d 03267abb 2e113d9b 07189268 4fd6babe 7b1c0056>
It seems that I am using wrong library or I have wrong input to the encryption function. Any recommendation of AES library for objective c?
Common Crypto is the correct thing to use for encryption for iOS and OSX. The issue it to provide the correct input.
The input key, iv and data appear to be in hexadecimal. Cryptomathic expects it inputs to be in hexadecimal and it's output is in hexadecimal so it works correctly.
But the ObjC code uses:
NSString* data = #"EA010B23CDA9B16F0001020304050607";
NSData* _data = [data dataUsingEncoding:NSUTF8StringEncoding];
which uses the hexadecimal as a character string.
Instead use a hex to data conversion such as #Larme linked to, see the first comment.
From the sizes of the input and output it appears you are using PKCS#7 padding which adds a full block of padding if the input is an exact multiple of the block size, Cryptomathic does not add PKCS#7 padding.
Update
#interface Crypt : NSObject
+ (NSData *)aes128Data:(NSData *)dataIn;
+ (NSData *)dataFromHexString:(NSString *)hexString;
#end
#implementation Crypt
+ (NSData *)aes128Data:(NSData *)dataIn
operation:(CCOperation)operation // kCC Encrypt, Decrypt
key:(NSData *)key
options:(CCOptions)options // kCCOption PKCS7Padding, ECBMode,
iv:(NSData *)iv
error:(NSError **)error
{
CCCryptorStatus ccStatus = kCCSuccess;
size_t cryptBytes = 0;
NSMutableData *dataOut = [NSMutableData dataWithLength:dataIn.length + kCCBlockSizeAES128];
ccStatus = CCCrypt( operation,
kCCAlgorithmAES,
options,
key.bytes, key.length,
iv.bytes,
dataIn.bytes, dataIn.length,
dataOut.mutableBytes, dataOut.length,
&cryptBytes);
if (ccStatus == kCCSuccess) {
dataOut.length = cryptBytes;
}
else {
if (error) {
*error = [NSError errorWithDomain:#"kEncryptionError"
code:ccStatus
userInfo:nil];
}
dataOut = nil;
}
return dataOut;
}
+ (NSData *)dataFromHexString:(NSString *)hexString {
char buf[3];
buf[2] = '\0';
unsigned char *bytes = malloc([hexString length]/2);
unsigned char *bp = bytes;
for (CFIndex i = 0; i < [hexString length]; i += 2) {
buf[0] = [hexString characterAtIndex:i];
buf[1] = [hexString characterAtIndex:i+1];
char *b2 = NULL;
*bp++ = strtol(buf, &b2, 16);
}
return [NSData dataWithBytesNoCopy:bytes length:[hexString length]/2 freeWhenDone:YES];
}
#end
NSString *dataHexString = #"EA010B23CDA9B16F0001020304050607";
NSString *keyHexString = #"000102030405060708090A0B0C0D0E0F";
NSString *ivHexString = #"00102030405060708090A0B0C0D0E0F0";
NSLog(#"dataHexString: %#", dataHexString);
NSLog(#"keyHexString: %#", keyHexString);
NSLog(#"ivHexString: %#", ivHexString);
NSData *data = [Crypt dataFromHexString:dataHexString];
NSData *key = [Crypt dataFromHexString:keyHexString];
NSData *iv = [Crypt dataFromHexString:ivHexString];
NSLog(#"data: %#", data);
NSLog(#"key: %#", key);
NSLog(#"iv: %#", iv);
NSError *error;
NSData *encryptedData = [Crypt
aes128Data:data
operation:kCCEncrypt
key:key
options:0
iv:iv
error:&error];
NSLog(#"encryptedData %#", encryptedData);
Output:
dataHexString: EA010B23CDA9B16F0001020304050607
keyHexString: 000102030405060708090A0B0C0D0E0F
ivHexString: 00102030405060708090A0B0C0D0E0F0
data: <ea010b23 cda9b16f 00010203 04050607>
key: <00010203 04050607 08090a0b 0c0d0e0f>
iv: <00102030 40506070 8090a0b0 c0d0e0f0>
encryptedData: <b773c367 49e87d3f 8fed98fe 52026a15>
Note encryptedData matches the Cryptomathic result.
Trying to get the digest using HMac SHA256 with below code but every time it is giving different output.
Here key parameter is in Base64string format while plaintext parameter is without any encoding.
+(NSString *)hmacWithIndicies:(NSString *)plaintext withKey:(NSString *)key {
NSLog(#"Input text::%#",plaintext);
NSLog(#"Input Key::%#",key);
NSData *keyData = [[NSData alloc] initWithBase64EncodedString:key options:0];
NSLog(#"Key Data is::%#",keyData);
const char *cKey = (char *)[keyData bytes];
NSLog(#"Key Length is::%lu",strlen(cKey));
NSData *keyInData = [NSData dataWithBytes:cKey length:sizeof(cKey)];
NSLog(#"Key data = %#", keyInData);
//Data here
const char *cData = [plaintext cStringUsingEncoding:NSUTF8StringEncoding];
NSLog(#"Input Length is::%lu",strlen(cData));
NSData *dataData = [NSData dataWithBytes:cData length:sizeof(cData)];
NSLog(#"Input data = %#", dataData);
uint8_t cHMAC[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
NSData *hMacInData =[[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
NSLog(#"Hash Mac data generated is %#", hMacInData);
NSString *b64EncStrHmac = [hMacInData base64EncodedStringWithOptions:0];
NSLog(#"Hash Mac generated is %#", b64EncStrHmac);
return b64EncStrHmac;
}
Calling the above method as below:-
NSString * hMacOutput= [KeyGeneration hmacWithIndicies:#"2SagarPra2983688" withKey:#"qDwki5t1SSuKER4mzSMBHXhtt+PRMCv0B2LgXaBZmgE="];
NSLog(#"Output of HMac digest::%#",hMacOutput);
hMacOutput digest is resulting in different output every time it is being called.
You can not use strlen() on non "C" strings, "C" strings are null terminated strings that do not contain any 0x00 bytes. strlen() counts until it finds the first 0x00 byte, on data bytes that could be early or past the end of the data, possible causing a crash.
You are trying to hard, there is no reason for "C" style arrays, just use the bytes member of NSData and NSMutableData along with the length method.
[NSMutableData dataWithLength: ] allocates memory.
Example:
+(NSString *)hmacWithIndicies:(NSString *)plaintext withKey:(NSString *)key {
NSLog(#"Input text: %#", plaintext);
NSLog(#"Input Key: %#", key);
NSData *keyData = [[NSData alloc] initWithBase64EncodedString:key options:0];
NSLog(#"keyData Length: %lu, Data: %#", keyData.length, keyData);
NSData *inData = [plaintext dataUsingEncoding:NSUTF8StringEncoding];
NSLog(#"inData Length: %lu, Data: %#", inData.length, inData);
NSMutableData *HMACdata = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, keyData.bytes, keyData.length, inData.bytes, inData.length, (void *)HMACdata.bytes);
NSLog(#"Hash Mac data generated: %#", HMACdata);
NSString *b64EncStrHmac = [HMACdata base64EncodedStringWithOptions:0];
NSLog(#"Hash Mac generated: %#", b64EncStrHmac);
return b64EncStrHmac;
}
Output:
Input text: 2SagarPra2983688
Input Key: qDwki5t1SSuKER4mzSMBHXhtt+PRMCv0B2LgXaBZmgE=
keyData Length: 32, Data: a83c248b 9b75492b 8a111e26 cd23011d 786db7e3 d1302bf4 0762e05d a0599a01
inData Length: 16, Data: 32536167 61725072 61323938 33363838
Hash Mac data generated: b681d2b1 251f1953 3716258c 8eeb9101 db3ecad2 c4a5077e 0cf76617 e45e5459
Hash Mac generated: toHSsSUfGVM3FiWMjuuRAds+ytLEpQd+DPdmF+ReVFk=
Output of HMac digest::toHSsSUfGVM3FiWMjuuRAds+ytLEpQd+DPdmF+ReVFk=
It is not possible to use strlen for binary data. As the key of HMAC can be of any size you may be using more bytes than the key actually contains. If the key changes each time, you will get different output. You need to retrieve the size of the key from keyData, not from a cKey.
I use below code to encode and decode a string on objective C. The encoding is good, I debug and see that it throw a hash string when input is #"1". But when I try to decode this hash string, it return nil.
Please help me.
+(NSString *)encrypt: (NSString*) input
{
//Base64 Encoding
char base64Result[32];
size_t theResultLength = 32;
Base64EncodeData(input, 20, base64Result, &theResultLength);
NSData *theData = [NSData dataWithBytes:base64Result length:theResultLength];
NSString *base64EncodedResult = [[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding];
NSString* decryptedString = [self decrypt:base64EncodedResult];
return [base64EncodedResult autorelease];
}
+ (NSString *) decrypt:(NSString*) input{
Byte inputData[[input lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];//prepare a Byte[]
[[input dataUsingEncoding:NSUTF8StringEncoding] getBytes:inputData];//get the pointer of the data
size_t inputDataSize = (size_t)[input length];
size_t outputDataSize = EstimateBas64DecodedDataSize(inputDataSize);//calculate the decoded data size
Byte outputData[outputDataSize];//prepare a Byte[] for the decoded data
Base64DecodeData(inputData, inputDataSize, outputData, &outputDataSize);//decode the data
NSData *theData = [[NSData alloc] initWithBytes:outputData length:outputDataSize];//create a NSData object from the decoded data
NSString *result = [[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding];
return [result autorelease];
}
In you encoding method, you have to convert the input string to a byte buffer and feed that to Base64EncodeData:
NSData *inputData = [input dataUsingEncoding:NSUTF8StringEncoding];
Base64EncodeData([inputData bytes], [inputData length], base64Result, &theResultLength, NO);
(The NSString *input argument in the encoding method points to an Objective-C structure, not to a C string. So your encoding method seems to work. It encodes something, but not the input string. The decoding method then fails at
NSString *result = [[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding];
because the decoded data does not contain valid UTF-8.)
I'm trying to convert an NSString to uint8_t. The problem I'm having is that the NSString and the resulting uint8_t variables do not match. Here is some example code:
NSLog(#"Key now: %#", key);
NSData* keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
const uint8_t *plainBuffer = (const uint8_t*)[data bytes];
size_t plainBufferSize = strlen((char *) plainBuffer);
NSLog(#"Plain buffer: %s", plainBuffer);
NSData* testData = [[NSData alloc] initWithBytes: plainBuffer length:plainBufferSize];
NSString* testString = [[NSString alloc] initWithData: testData encoding: NSUTF8StringEncoding];
NSLog(#"Test string: %#", testString);
And example output:
Key now: 9iIWBpf5R6yu5pJ93l218RsMdWBLidXt
Plain buffer: 9iIWBpf5R6yu5pJ93l218RsMdWBLidXtMdWBLidXt
Test string: 9iIWBpf5R6yu5pJ93l218RsMdWBLidXtMdWBLidXt
Clearly its the NSData -> uint8_t step thats going wrong, but I don't know why!
You're using strlen() to get the size of an NSData*. That's not going to work. The NSData* isn't NUL-terminated. So you're getting garbage on the end.
Don't use strlen(). Just ask the NSData* for its size directly.
Alternatively, don't use NSData* at all and just ask for [key UTF8String]. That hands back a NUL-terminated const char *.
I have the following Objective-C function:
+(NSString *)stringToSha1:(NSString *)str{
NSMutableData *dataToHash = [[NSMutableData alloc] init];
[dataToHash appendData:[str dataUsingEncoding:NSUTF8StringEncoding]];
unsigned char hashBytes[CC_SHA1_DIGEST_LENGTH];
CC_SHA1([dataToHash bytes], [dataToHash length], hashBytes);
NSData *encodedData = [NSData dataWithBytes:hashBytes length:CC_SHA1_DIGEST_LENGTH];
[dataToHash release];
NSString *encodedStr = [NSString stringWithUTF8String:[encodedData bytes]];
//NSString *encodedStr = [[NSString alloc] initWithBytes:[encodedData bytes]
// length:[encodedData length] encoding: NSUTF8StringEncoding];
NSLog(#"String is %#", encodedStr);
return encodedStr;
}
What I'm trying to do is take an NSString and SHA1 encode it. That part seems to be working, I think where I am falling over is in how to convert the NSData object back to a legible string. If I use UTF8 encoding I get blank, if I say ASCII I get weird characters. What I really want is the hex string, but I have no idea how to get it. This is using the iPhone 3.0 SDK.
At the moment any String I pass in comes back out NULL.
My version of SHA1 function (simplier):
- (NSString *)sha1:(NSString *)str {
const char *cStr = [str UTF8String];
unsigned char result[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(cStr, strlen(cStr), result);
NSString *s = [NSString stringWithFormat:
#"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3], result[4],
result[5], result[6], result[7],
result[8], result[9], result[10], result[11], result[12],
result[13], result[14], result[15],
result[16], result[17], result[18], result[19]
];
return s;
}
And MD5:
- (NSString *)md5:(NSString *)str {
const char *cStr = [str UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(cStr, strlen(cStr), result);
NSString *s = [NSString stringWithFormat:
#"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3], result[4],
result[5], result[6], result[7],
result[8], result[9], result[10], result[11], result[12],
result[13], result[14], result[15]
];
return s;
}
Short answer: turn on gcc warnings (-Wall).
Long answer:
NSMutableData *dataToHash = [[NSMutableData alloc] init];
[dataToHash appendData:[str dataUsingEncoding:NSUTF8StringEncoding]];
is broken: You try to use a C string where an NSData argument is expected. Use
NSMutableData *dataToHash = [str dataUsingEncoding:NSUTF8StringEncoding];
instead.
The rest of the method takes the SHA1 buffer and tries interpret this data as an UTF-8 C string, which might crash or give any unexpected result. First, the buffer is not a UTF-8 string. Secondly, it's not null terminated.
What you want is to convert the SHA1 to a base 64 or similar string. Here's a nice post about how to do that.
This is what I ended up with, the next step would be to convert it to be a Category of NSString instead of a static method in a helper class:
+(NSString *)stringToSha1:(NSString *)str{
const char *s = [str cStringUsingEncoding:NSASCIIStringEncoding];
NSData *keyData = [NSData dataWithBytes:s length:strlen(s)];
// This is the destination
uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0};
// This one function does an unkeyed SHA1 hash of your hash data
CC_SHA1(keyData.bytes, keyData.length, digest);
// Now convert to NSData structure to make it usable again
NSData *out = [NSData dataWithBytes:digest length:CC_SHA1_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:#""];
NSLog(#"Hash is %# for string %#", hash, str);
return hash;
}
glycol,
You will need to define your CC_SHA1_DIGEST_LENGTH constant, probably to 20, as that's the length of an SHA-1 digest according to the NIST spec at http://www.itl.nist.gov/fipspubs/fip180-1.htm