通过使用使用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将返回错误,这是一个安全错误,后来已得到纠正。在苹果论坛上,盖恩曾进行过多次讨论,奎因参加了很多讨论。但这是一个安全漏洞,因此删除了奇偶校验,并且使多个开发人员感到不安/敌意。现在,如果奇偶校验不正确,则不会报告任何错误。

10-06 13:25