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中观察到的行为。
- 如何在iOS中使用AES128encryption的Openssl工具解密数据
- AES / cbc / pkcs5padding encription IOS
- PHP服务器和iOS上的AES Rijndael生成有时不同的密码
- AES快速encryption
- AESencryption在iOS和Android中造成不同的结果
- Swift(iOS)和PHP中AES256encryption的结果不同
- 在iOS 7中,AES解密在iOS 8/9中具有不同的行为
- AVPlayer在线模式下停止播放AESencryption离线HLSvideo
- AES在iOS(Obj-C)和Android(Java)中获得了不同的结果