Reputation: 1077
My application uses AES 256 encryption to encrypt a string. The same code that was used before is generating a different result. This problem started when iOS 13 was released. And it happens only to applications that are shipped to the store or built with Xcode 11.
Here is the code used for the encryption:
- (NSData *)encrypt:(NSData *)plainText key:(NSString *)key iv:(NSString *)iv {
char keyPointer[kCCKeySizeAES256+2],// room for terminator (unused) ref: https://devforums.apple.com/message/876053#876053
ivPointer[kCCBlockSizeAES128+2];
BOOL patchNeeded;
bzero(keyPointer, sizeof(keyPointer)); // fill with zeroes for padding
patchNeeded= ([key length] > kCCKeySizeAES256+1);
if(patchNeeded)
{
NSLog(@"Key length is longer %lu", (unsigned long)[[self md5:key] length]);
key = [key substringToIndex:kCCKeySizeAES256]; // Ensure that the key isn't longer than what's needed (kCCKeySizeAES256)
}
//NSLog(@"md5 :%@", key);
[key getCString:keyPointer maxLength:sizeof(keyPointer) encoding:NSUTF8StringEncoding];
[iv getCString:ivPointer maxLength:sizeof(ivPointer) encoding:NSUTF8StringEncoding];
if (patchNeeded) {
keyPointer[0] = '\0'; // Previous iOS version than iOS7 set the first char to '\0' if the key was longer than kCCKeySizeAES256
}
NSUInteger dataLength = [plainText length];
//see https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man3/CCryptorCreateFromData.3cc.html
// For block ciphers, the output size will always be less than or equal to the input size plus the size of one block.
size_t buffSize = dataLength + kCCBlockSizeAES128;
void *buff = malloc(buffSize);
size_t numBytesEncrypted = 0;
//refer to http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-36064/CommonCrypto/CommonCryptor.h
//for details on this function
//Stateless, one-shot encrypt or decrypt operation.
CCCryptorStatus status = CCCrypt(kCCEncrypt, /* kCCEncrypt, etc. */
kCCAlgorithmAES128, /* kCCAlgorithmAES128, etc. */
kCCOptionPKCS7Padding, /* kCCOptionPKCS7Padding, etc. */
keyPointer, kCCKeySizeAES256, /* key and its length */
ivPointer, /* initialization vector - use random IV everytime */
[plainText bytes], [plainText length], /* input */
buff, buffSize,/* data RETURNED here */
&numBytesEncrypted);
if (status == kCCSuccess) {
return [NSData dataWithBytesNoCopy:buff length:numBytesEncrypted];
}
free(buff);
return nil;
}
- (NSString *) encryptPlainTextWith:(NSString *)plainText key:(NSString *)key iv:(NSString *)iv {
return [[[[CryptLib alloc] init] encrypt:[plainText dataUsingEncoding:NSUTF8StringEncoding] key:[[CryptLib alloc] sha256:key length:32] iv:iv] base64EncodedStringWithOptions:0];
}
/**
* This function computes the SHA256 hash of input string
* @param key input text whose SHA256 hash has to be computed
* @param length length of the text to be returned
* @return returns SHA256 hash of input text
*/
- (NSString*) sha256:(NSString *)key length:(NSInteger) length{
const char *s=[key cStringUsingEncoding:NSASCIIStringEncoding];
NSData *keyData=[NSData dataWithBytes:s length:strlen(s)];
uint8_t digest[CC_SHA256_DIGEST_LENGTH]={0};
CC_SHA256(keyData.bytes, (CC_LONG)keyData.length, digest);
NSData *out=[NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
NSString *hash=[out description];
hash = [hash stringByReplacingOccurrencesOfString:@" " withString:@""];
hash = [hash stringByReplacingOccurrencesOfString:@"<" withString:@""];
hash = [hash stringByReplacingOccurrencesOfString:@">" withString:@""];
if(length > [hash length])
{
return hash;
}
else
{
return [hash substringToIndex:length];
}
}
##
I would like to know if something in the code path has changed in the way it works. The method called to do the encryptions is "encryptPlainTextWith". Thanks in advance.
Upvotes: 6
Views: 1804
Reputation: 1077
Inside:
- (NSString*) sha256:(NSString *)key length:(NSInteger) length
I replaced
NSString *hash=[out description];
To
NSString *hash=[out debugDescription];
And everything got back to normal. Cheers Happy coding.
Alternative Solution as per @Rob Napier
create separate function for converting NSData to Hex
#pragma mark - String Conversion
-(NSString*)hex:(NSData*)data{
NSMutableData *result = [NSMutableData dataWithLength:2*data.length];
unsigned const char* src = data.bytes;
unsigned char* dst = result.mutableBytes;
unsigned char t0, t1;
for (int i = 0; i < data.length; i ++ ) {
t0 = src[i] >> 4;
t1 = src[i] & 0x0F;
dst[i*2] = 48 + t0 + (t0 / 10) * 39;
dst[i*2+1] = 48 + t1 + (t1 / 10) * 39;
}
return [[NSString alloc] initWithData:result encoding:NSASCIIStringEncoding];
}
After that Inside:
- (NSString*) sha256:(NSString *)key length:(NSInteger) length
I replaced
NSString *hash=[out description];
To
NSString *hash = [self hex:out];
Upvotes: 16
Reputation: 299445
I suspect that your key is longer than 32 UTF-8 bytes. In that case, this code is incorrect. Your patchNeeded
conditional is basically creating a garbage key. The contents of buffer
aren't promised if this function return returns false, but you're relying on them.
There is no secure way to truncate a key you were given, so I'm not really certain what behavior you want here. It depends on what kinds of strings you're passing.
This code is also incorrect if iv
is shorter than 16 UTF-8 bytes. You'll wind up including random values from the stack. That part can be fixed with:
bzero(ivPointer, sizeof(ivPointer));
But if your previous version relied on random values, this will still be different.
Assuming you need to match the old behavior, the best way to debug this is to run your previous version in a debugger and see what keyPointer
and ivPointer
wind up being.
(Note that this approach to creating a key is very insecure. It's drastically shrinking the AES keyspace. How much depends on what kind of strings you're passing, but it's dramatic. You also should never reuse the same key+iv combination in two messages when using CBC, which this looks like it probably does. If possible, I recommend moving to a correct AES implementation. You can look at RNCryptor for one example of how to do that, or use RNCryptor directly if you prefer.)
Upvotes: 1