通过使用使用ECB模式(这是玩具加密)和PKCS7填充的AES128加密的密文,以下代码块将导致在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;
}
我在下面添加了一个自包含的测试工具,并提供了结果。
测试工具:
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中的结果符合预期。为什么是这样??我知道字节数是按块对齐的,因为密文用PKCS7填充来填充,所以这是有道理的,但是为什么设置
kCCOptionPKCS7Padding | kCCOptionECBMode
只会导致iOS 7中的截断行为?编辑:上面的测试密文是从this web site生成的,并在以下功能中独立使用PHP的带有PKCS7手动填充的mcrypt:
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"));
出:
I9JIk5BskZMZKJFB / EAs + N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh
更新:正确填充PKCS#7的密文应为
I9JIk5BskZMZKJFB / EAs + N2AYzkVR15DoBbUL7cBydA6aE5a3JrRst9Gn3sb3heC
Here是为什么不是。
最佳答案
数据不是使用PKCS#7填充加密的,而是使用null填充加密的。您可以通过记录plainData
来告知:
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()虽然很流行,但它是由某些bozos编写的,并且使用非标准的null填充,这既不安全,并且如果数据的最后一个字节为0x00也将不起作用。
如果填充明显不正确,则早期版本的CCCrypt将返回错误,这是一个安全错误,后来已得到纠正。 IIRC iOS7是最后一个报告填充错误为错误的版本。
解决方案是在加密之前添加PKCS#7填充:
PKCS#7填充总是添加填充。填充是按字节排列的序列,其中添加了填充字节数的值。填充的长度为block_size-(length(data)%block_size。
对于块为16字节的AES(希望php有效,已经有一段时间了):
$pad_count = 16 - (strlen($data) % 16);
$data .= str_repeat(chr($pad_count), $pad_count);
或在解密后删除尾随的0x00字节。
参见PKCS7。
如果填充明显不正确,则早期版本的CCCrypt将返回错误,这是一个安全错误,后来已得到纠正。在苹果论坛上,盖恩曾进行过多次讨论,奎因参加了很多讨论。但这是一个安全漏洞,因此删除了奇偶校验,并且使多个开发人员感到不安/敌意。现在,如果奇偶校验不正确,则不会报告任何错误。