AES128在iOS 7上截断解密的文本,在iOS 8上没有问题

使用ECB模式(这是玩具encryption)和PKCS7填充使用AES128encryption的密文,以下代码块导致在iOS 8下恢复完整的明文。

在iOS 7下运行相同的代码块会导致正确的明文,但会被截断。 为什么是这样?

#import "NSData+AESCrypt.h" // <-- a category with the below function #import <CommonCrypto/CommonCryptor.h> - (NSData *)AES128Operation:(CCOperation)operation key:(NSString *)key iv:(NSString *)iv { char keyPtr[kCCKeySizeAES128 + 1]; bzero(keyPtr, sizeof(keyPtr)); [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding]; char ivPtr[kCCBlockSizeAES128 + 1]; bzero(ivPtr, sizeof(ivPtr)); if (iv) { [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding]; } NSUInteger dataLength = [self length]; size_t bufferSize = dataLength + kCCBlockSizeAES128; void *buffer = malloc(bufferSize); size_t numBytesEncrypted = 0; CCCryptorStatus cryptStatus = CCCrypt(operation, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode, keyPtr, kCCBlockSizeAES128, ivPtr, [self bytes], dataLength, buffer, bufferSize, &numBytesEncrypted); if (cryptStatus == kCCSuccess) { return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted]; } free(buffer); return nil; } 

我已经在下面添加了一个独立的testing工具。

testing装置:

 NSString *key = @"1234567890ABCDEF"; NSString *ciphertext = @"I9JIk5BskZMZKJFB/EAs+N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh"; NSData *encData = [[NSData alloc]initWithBase64EncodedString:ciphertext options:0]; NSData *plainData = [encData AES128Operation:kCCDecrypt key:key iv:nil]; NSString *plaintext = [NSString stringWithUTF8String:[plainData bytes]]; DLog(@"key: %@\nciphertext: %@\nplaintext: %@", key, ciphertext, plaintext); 

iOS 8的结果:

 key: 1234567890ABCDEF ciphertext: I9JIk5BskZMZKJFB/EAs+N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh plaintext: the quick brown fox jumped over the fence 

iOS 7的结果:

 key: 1234567890ABCDEF ciphertext: I9JIk5BskZMZKJFB/EAs+N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh plaintext: the quick brown fox jumped over 0 

和后续结果:

 plaintext: the quick brown fox jumped over plaintext: the quick brown fox jumped over * 

更新:谜语:当我改变

kCCOptionPKCS7Padding | kCCOptionECBMode⇒kCCOptionECBMode

iOS 7的结果如预期。 为什么是这样?? 我知道字节数是块alignment的,因为密文是填充PKCS7填充,所以这是有道理的,但为什么设置kCCOptionPKCS7Padding | kCCOptionECBMode kCCOptionPKCS7Padding | kCCOptionECBMode导致在iOS 7截断的行为?


编辑:上面的testing密文是从这个网站生成的,并独立使用PHP的mcrypt与手动PKCS7填充在以下function:

 function encryptAES128WithPKCS7($message, $key) { if (mb_strlen($key, '8bit') !== 16) { throw new Exception("Needs a 128-bit key!"); } // Add PKCS7 Padding $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128); $pad = $block - (mb_strlen($message, '8bit') % $block); $message .= str_repeat(chr($pad), $pad); $ciphertext = mcrypt_encrypt( MCRYPT_RIJNDAEL_128, $key, $message, MCRYPT_MODE_ECB ); return $ciphertext; } // Demonstration encryption echo base64_encode(encryptAES128WithPKCS7("the quick brown fox jumped over the fence", "1234567890ABCDEF")); 

date:

I9JIk5BskZMZKJFB /中介公司+ N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh


更新:正确的PKCS#7填充的密文将是

I9JIk5BskZMZKJFB /中介公司+ N2AYzkVR15DoBbUL7cBydA6aE5a3JrRst9Gn3sb3heC

这是为什么不是。

数据没有使用PKCS#7填充进行encryption,但使用空填充。 你可以通过loggingplainData来告诉它:

 NSData *fullData = [NSData dataWithBytes:buffer length:dataLength]; NSLog(@"\nfullData: %@", fullData); 

输出:
plainData:74686520 71756963 6b206272 6f776e20 666f7820 6a756d70 6564206f 76657220 74686520 66656e63 65000000 00000000

PHP mcrypt方法这样做,这是非标准的。

mcrypt(),而stream行的是由一些bozos写的,并使用非标准的null填充,这是不安全的,如果数据的最后一个字节是0x00将无法正常工作。

CCCrypt的早期版本会返回一个错误,如果填充显然是不正确的,这是一个安全错误,后来被纠正。 IIRC iOS7是报告错误填充的最后一个版本。

解决scheme是在encryption之前添加PKCS#7填充:

PKCS#7填充总是添加填充。 填充是以字节为单位的一系列填充字节数的值。 填充的长度是block_size – (长度(数据)%块大小。

对于块是16个字节的AES(并希望PHP是有效的,这是一段时间):

 $pad_count = 16 - (strlen($data) % 16); $data .= str_repeat(chr($pad_count), $pad_count); 

或解密后删除尾随0x00字节。

请参阅PKCS7 。

CCCrypt的早期版本会返回一个错误,如果填充显然是不正确的,这是一个安全错误,后来被纠正。 这在苹果论坛上已经有好几次了,奎因在很多讨论中。 但是这是一个安全弱点,所以平价检查被取消了,几个开发商对此感到不安/充满敌意。 现在如果有不正确的奇偶校验,则不报告错误。

我无法用以下代码重现您的问题:

 @implementation ViewController { NSData *_data; } - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"system version: %@", [[UIDevice currentDevice] systemVersion]); NSMutableString *text = [NSMutableString string]; for (int i = 0; i < 80; i+=4) { [text appendFormat:@"ABCD"]; } _data = [text dataUsingEncoding:NSUTF8StringEncoding]; NSString *key = @"password"; NSString *iv = @"12345678"; NSData *encrypted = [self AES128Operation:kCCEncrypt key:key iv:iv]; NSLog(@"encrypted: %@", encrypted); _data = encrypted; NSData *decrypted = [self AES128Operation:kCCDecrypt key:key iv:iv]; NSLog(@"decrypted: %@ (%@)", decrypted, [[NSString alloc] initWithData:decrypted encoding:NSUTF8StringEncoding]); } - (NSData *)AES128Operation:(CCOperation)operation key:(NSString *)key iv:(NSString *)iv { char keyPtr[kCCKeySizeAES128 + 1]; bzero(keyPtr, sizeof(keyPtr)); [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding]; char ivPtr[kCCBlockSizeAES128 + 1]; bzero(ivPtr, sizeof(ivPtr)); if (iv) { [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding]; } NSUInteger dataLength = [_data length]; size_t bufferSize = dataLength + kCCBlockSizeAES128; void *buffer = malloc(bufferSize); size_t numBytesEncrypted = 0; CCCryptorStatus cryptStatus = CCCrypt(operation, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode, keyPtr, kCCBlockSizeAES128, ivPtr, [_data bytes], dataLength, buffer, bufferSize, &numBytesEncrypted); if (cryptStatus == kCCSuccess) { return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted]; } free(buffer); return nil; } 

以下是结果:

 2015-08-06 12:39:29.716 Test[37788:13220246] system version: 8.4 2015-08-06 12:39:29.717 Test[37788:13220246] encrypted: <17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 c6b4234e 1d0709c9 45113e4f 2a9607f7> 2015-08-06 12:39:29.717 Test[37788:13220246] decrypted: <41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344> (ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD) 2015-08-06 13:39:50.270 Test[37841:607] system version: 7.1 2015-08-06 13:39:50.272 Test[37841:607] encrypted: <17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 c6b4234e 1d0709c9 45113e4f 2a9607f7> 2015-08-06 13:39:50.273 Test[37841:607] decrypted: <41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344> (ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD) 

另外从文档:

初始化vector,可选。 用于密码块链接(CBC)模式。 如果存在,则必须与所选algorithm的块大小相同。 如果selectCBC模式(由于选项标志中没有任何模式位),并且不存在IV,则将使用NULL(全零)IV。 如果使用ECB模式或selectstream密码algorithm,则忽略此项。

所以IV在ECB模式下是无用的。

死去的权利 ,事实certificate,在PHP的mcrypt之前应用手动 PKCS#7填充例程有一个微妙的缺陷。

众所周知 ,mcrypt使用null填充,因此要使其与PKCS7兼容,需要手动通过在每个数据中附加n个包含值n的字节来对数据进行字节alignment。 在这个问题的情况下,它是用这个代码的gem完成的:

 // Add PKCS#7 Padding $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128); $pad = $block - (mb_strlen($message, '8bit') % $block); $message .= str_repeat(chr($pad), $pad); $ciphertext = mcrypt_encrypt( MCRYPT_RIJNDAEL_128, $key, $message, MCRYPT_MODE_ECB ); 

我们在这做着正确的事情, 不是吗? 我被要求专注于iOS 7/8问题。 但事实certificate

 $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128); // null 

链接到更新版本的libmcrypt> = 2.4( ref )时,什么也不做,但是

 $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, 'ecb'); // 16 

正确地返回块大小。 实际上,没有填充正在被应用,并且填充还原为空填充lacryptpt。 信用certificatezaph

很显然,testing数据没有用PKCS#7填充,因为它具有空的填充字节。 PKCS#7传递一个字节,这些字节是填充的长度。 fullData将是:74686520 71756963 6b206272 6f776e20 666f7820 6a756d70 6564206f 76657220 74686520 66656e63 65070707 07070707

现在,有人提到iOS 7处理“坏填充”的方式与iOS 8不同。我绝对需要一个参考,但这两个错误组合解释了在OP中观察到的行为。