AES128在iOS 7上截断解密文本,iOS 8上没有问题 [英] AES128 truncated decrypted text on iOS 7, no problems on iOS 8

查看:177
本文介绍了AES128在iOS 7上截断解密文本,iOS 8上没有问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



使用AES128加密的密文,使用ECB模式(这是玩具加密)和PKCS7填充,以下代码块导致在iOS 8下恢复完整的明文。 >在iOS 7下运行相同的代码块导致正确的明文,但被截断。为什么是这样?

  #importNSData + AESCrypt.h//<  - 具有以下功能的类别
#import< CommonCrypto / CommonCryptor.h>

- (NSData *)AES128操作:(CCOperation)操作键:(NSString *)键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;
}

我已经在下面添加了一个独立的测试工具和结果。 p>

测试工具:

  NSString * key = @1234567890ABCDEF; 
NSString * ciphertext = @I9JIk5BskZMZKJFB / EA / 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:%@ \\\
ciphertext:%@ \plaintext:%@,key,ciphertext,plaintext);

iOS 8结果

  key:1234567890ABCDEF 
密文:I9JIk5BskZMZKJFB / EAs + N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh
明文:快速的棕色狐狸跳过围栏

iOS 7结果

 密钥:1234567890ABCDEF 
密文:I9JIk5BskZMZKJFB / EAs + N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh
明文:快速褐色狐狸跳过0

和后续结果:

 明文:快速棕色狐狸跳过
明文:快速的棕色狐狸跳过*






更新:谜我:当我更改


kCCOptionPKCS7Padding | kCCOptionECBMode⇒kCCOptionECBMode


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






编辑: strong>上面的测试密文是从本网站生成的,并且独立使用PHP的手册PKCS7填充的mcrypt以下功能:

 函数encryptAES128WithPKCS7($ message,$ key)
{
if(mb_strlen key,'8bit')!== 16){
throw new Exception(需要128位密钥!);
}

//添加PKCS7填充
$ 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;
}

//演示加密
echo base64_encode(encryptAES128WithPKCS7(快速的棕色狐狸跳过围栏,1234567890ABCDEF));

Out:


I9JIk5BskZMZKJFB / EAs + N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh







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


I9JIk5BskZMZKJFB / EAs + N2AYzkVR15DoBbUL7cBydA6aE5a3JrRst9Gn3sb3heC


这里是为什么没有。

解决方案

数据没有使用PKCS#7填充加密,而是使用空填充。您可以通过记录 plainData 来说明这一点:

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

输出:

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



PHP mcrypt方法执行此操作是非标准的。



mcrypt(),while流行是由一些bozos写的,并使用非标准的空填充,这是不安全的,如果数据的最后一个字节为0x00,则不起作用。



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



解决方案是在加密之前添加PKCS#7填充:



PKCS#7填充总是添加填充。填充是以字节为单位的字符串,其中添加了填充字节数。填充的长度是block_size - (length(data)%block_size。



对于AES,其中块为16字节(希望php有效)已经有一段时间):

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

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



请参阅 PKCS7



如果填充显然不正确,CCCrypt的早期版本将返回错误,这是一个安全错误,后来被更正了,这被覆盖了几次在苹果论坛上,Quinn正在进行许多讨论,但这是一个安全漏洞,因此奇偶校验被删除,并且几个开发人员感到不安/敌意,现在如果不正确的奇偶校验没有报错。


Using ciphertext encrypted with AES128 using ECB mode (this is toy encryption) and PKCS7 padding, the following code block results in the complete plaintext being recovered under iOS 8.

Running the same code block under iOS 7 results in the correct plaintext, but truncated. Why is this?

#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;
}

I've added a self-contained test harness below with results.

Test harness:

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 results:

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

iOS 7 results:

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

and subsequent results:

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


Update: Riddle me this: when I change

kCCOptionPKCS7Padding | kCCOptionECBMode ⇒ kCCOptionECBMode

the results in iOS 7 are as expected. Why is this?? I know the number of bytes are block aligned because the ciphertext is padded with PKCS7 padding so this makes sense, but why does setting kCCOptionPKCS7Padding | kCCOptionECBMode cause the truncated behavior in iOS 7 only?


Edit: The test ciphertext above was generated from both this web site, and independently using PHP's mcrypt with manual PKCS7 padding in the following 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"));

Out:

I9JIk5BskZMZKJFB/EAs+N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh


Update: The properly PKCS#7-padded ciphertext would be

I9JIk5BskZMZKJFB/EAs+N2AYzkVR15DoBbUL7cBydA6aE5a3JrRst9Gn3sb3heC

Here is why is wasn't.

解决方案

The data is not encrypted with PKCS#7 padding but with null padding. You can tell this by logging plainData:

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

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

The PHP mcrypt method does this, it is non-standard.

mcrypt(), while popular was written by some bozos and uses non-standard null padding which is both insecure and will not work if the last byte of the data is 0x00.

Early versions of CCCrypt would return an error if the padding was obviously incorrect, that was a security error which was later corrected. IIRC iOS7 was the last version that reported bad padding as an error.

The solution is to add PKCS#7 padding prior to encryption:

PKCS#7 padding always adds padding. The padding is a series by bytes with the value of the number of padding bytes added. The length of the padding is the block_size - (length(data) % block_size.

For AES where the block is is 16-bytes (and hoping the php is valid, it had been a while):

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

Or remove trailing 0x00 bytes after decryption.

See PKCS7.

Early versions of CCCrypt would return an error if the padding was obviously incorrect, that was a security error which was later corrected. This was covered several times in the Apple forums, Quinn was in many of the discussions. But that is a security weakness so the parity check was removed and several developers were upset/hostile. Now if there is incorrect parity no error is reported.

这篇关于AES128在iOS 7上截断解密文本,iOS 8上没有问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆