2

使用 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 中的截断行为?


编辑:上面的测试密文是从这个网站生成的,并且在以下函数中独立使用 PHP 的 mcrypt 和手动 PKCS7 填充:

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-padded 密文将是

I9JIk5BskZMZKJFB/EAs+N2AYzkVR15DoBbUL7cBydA6aE5a3JrRst9Gn3sb3heC

就是为什么不是。

4

3 回答 3

2

数据未使用 PKCS#7 填充加密,而是使用空填充加密。您可以通过记录来判断这一点plainData

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

输出:
普通数据:74686520 71756963 6b206272 6f776e20 666f7820 6a756d70 6564206f 76657220 74686520 66656e63 65000000 00000000

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

mcrypt(),虽然流行是由一些 bozos 编写的,并且使用非标准的空填充,这既不安全,如果数据的最后一个字节是 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 的早期版本会返回一个错误,这是一个后来被纠正的安全错误。这在 Apple 论坛中被多次讨论,Quinn 参与了许多讨论。但这是一个安全漏洞,因此删除了奇偶校验,一些开发人员感到不安/充满敌意。现在,如果奇偶校验不正确,则不会报告错误。

于 2015-08-06T15:14:37.500 回答
1

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

@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)

同样来自文档:

初始化向量,可选。用于密码块链接 (CBC) 模式。如果存在,其长度必须与所选算法的块大小相同。如果选择了 CBC 模式(由于选项标志中没有任何模式位)并且不存在 IV,则将使用 NULL(全零)IV。如果使用 ECB 模式或选择流密码算法,则忽略此设置。

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

于 2015-08-06T09:43:31.903 回答
0

事实证明,在 PHP 的 mcrypt 之前应用的手动 PKCS#7 填充例程存在一个微妙的缺陷。

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

// 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 问题。然而,事实证明

$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128); // null

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

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

正确返回块大小。实际上,没有应用填充,并且填充恢复为空填充(la mcrypt)。归功于 zaph的演示

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

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

于 2015-08-06T16:23:40.463 回答