1

我编写了使用 iOS 的 Common Crypto 加密和解密NSData对象的代码。加密密钥为 AES128,存储在 iOS 钥匙串中。我可以成功地加密和解密数据,所以我知道那部分代码正在工作。然而,作为健全性检查,我还生成了第二个AES128 密钥并尝试解密使用第一个加密密钥加密的数据。我期望该CCCryptorStatus值不是kCCSuccess,但事实并非如此。我收到了一个NSData对象,没有错误。我的加密/解密代码看起来像这样......

-(NSData *)dataDecryptedUsingAlgorithm:(CCAlgorithm)algorithm
                                  data:(NSData *)data
                                   key:(id)key
                  initializationVector:(id)iv
                               options:(CCOptions)options
                                 error:(CCCryptorStatus *)error {
    CCCryptorRef cryptor = NULL;
    CCCryptorStatus status = kCCSuccess;

    NSParameterAssert([key isKindOfClass: [NSData class]] || [key isKindOfClass: [NSString class]]);
    NSParameterAssert(iv == nil || [iv isKindOfClass: [NSData class]] || [iv isKindOfClass: [NSString class]]);

    NSMutableData * keyData, * ivData;
    if ( [key isKindOfClass: [NSData class]] )
        keyData = (NSMutableData *) [key mutableCopy];
    else
        keyData = [[key dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];

    if ( [iv isKindOfClass: [NSString class]] )
        ivData = [[iv dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];
    else
        ivData = (NSMutableData *) [iv mutableCopy];    // data or nil

    //    [keyData autorelease];
    //    [ivData autorelease];

    // ensure correct lengths for key and iv data, based on algorithms
    FixKeyLengths( algorithm, keyData, ivData );

    status = CCCryptorCreate( kCCDecrypt, algorithm, options,
                             [keyData bytes], [keyData length], [ivData bytes],
                             &cryptor );

    if ( status != kCCSuccess )
    {
        if ( error != NULL )
            *error = status;
        return ( nil );
    }

    NSData *result = [self runCryptor:cryptor onData:data result:&status];
    if ( (result == nil) && (error != NULL) )
        *error = status;

    CCCryptorRelease(cryptor);

    return ( result );
}

-(NSData *)dataEncryptedUsingAlgorithm:(CCAlgorithm) algorithm
                                  data:(NSData *)data
                                   key:(id)key
                  initializationVector:(id)iv
                               options:(CCOptions)options
                                 error:(CCCryptorStatus *)error {
    CCCryptorRef cryptor = NULL;
    CCCryptorStatus status = kCCSuccess;

    NSParameterAssert([key isKindOfClass: [NSData class]] || [key isKindOfClass: [NSString class]]);
    NSParameterAssert(iv == nil || [iv isKindOfClass: [NSData class]] || [iv isKindOfClass: [NSString class]]);

    NSMutableData * keyData, * ivData;
    if ( [key isKindOfClass: [NSData class]] )
        keyData = (NSMutableData *) [key mutableCopy];
    else
        keyData = [[key dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];

    if ( [iv isKindOfClass: [NSString class]] )
        ivData = [[iv dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];
    else
        ivData = (NSMutableData *) [iv mutableCopy];    // data or nil

    //    [keyData autorelease];
    //    [ivData autorelease];

    // ensure correct lengths for key and iv data, based on algorithms
    FixKeyLengths( algorithm, keyData, ivData );

    status = CCCryptorCreate( kCCEncrypt, algorithm, options,
                             [keyData bytes], [keyData length], [ivData bytes],
                             &cryptor );

    if ( status != kCCSuccess )
    {
        if ( error != NULL )
            *error = status;
        return ( nil );
    }

    NSData *result = [self runCryptor:cryptor onData:data result:&status];
    if ( (result == nil) && (error != NULL) )
        *error = status;

    CCCryptorRelease( cryptor );

    return ( result );
}

-(NSData *)runCryptor:(CCCryptorRef)cryptor onData:(NSData *)data result:(CCCryptorStatus *)status {
    size_t bufsize = CCCryptorGetOutputLength( cryptor, (size_t)[data length], true );
    void * buf = malloc( bufsize );
    size_t bufused = 0;
    size_t bytesTotal = 0;
    *status = CCCryptorUpdate( cryptor, [data bytes], (size_t)[data length],
                              buf, bufsize, &bufused );
    if ( *status != kCCSuccess )
    {
        free( buf );
        return ( nil );
    }

    bytesTotal += bufused;

    // From Brent Royal-Gordon (Twitter: architechies):
    //  Need to update buf ptr past used bytes when calling CCCryptorFinal()
    *status = CCCryptorFinal( cryptor, buf + bufused, bufsize - bufused, &bufused );
    if ( *status != kCCSuccess )
    {
        free( buf );
        return ( nil );
    }

    bytesTotal += bufused;

    return ( [NSData dataWithBytesNoCopy: buf length: bytesTotal] );
}

当我调用加密和解密方法时,我将kCCAlgorithmAES128作为我的算法和kCCOptionPKCS7Padding我的选项传入。有没有办法在使用错误密钥进行解密时捕获,以便我可以返回适当的错误?

4

3 回答 3

2

正如 Zaph 指出的那样,区分坏密钥和损坏数据的唯一可靠方法是某种婴儿床(即在最广泛的意义上使用;即您对加密有所了解)。如果您对此方法感兴趣,请参阅RNCryptor v4 规范。目前还没有实现,它只是一个规范,但它包含一个验证器字段,可用于确定密码是否正确。它使用 HKDF-Expand 步骤将您的一些初始密钥材料转换为验证令牌。


请注意,您方法的这一部分非常令人担忧:

if ( [iv isKindOfClass: [NSString class]] )
    ivData = [[iv dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];
 else
    ivData = (NSMutableData *) [iv mutableCopy];    // data or nil

如果传入一个字符串,则它的键空间比您预期的要小得多。即使它是 16 个完全随机字节的字符串,合法的 UTF8 字符串代表的空间也比等效的 16 个字节的随机数据小得多。

于 2014-09-03T21:55:40.583 回答
1

不,除非您使用添加身份验证的密码模式,例如 GCM 或 EAX 操作模式,否则您不能。否则,解密总是会返回成功的奇怪机会,因为在解密后填充可能是正确的。换句话说,您不能使用CCCryptorStatus来(可靠地)检测不正确的密钥或损坏的密文。正如 Zaph 通过指向Apple 论坛上的讨论所指出的那样,由于可能填充 Oracle 攻击,CCCryptorStatus可能永远不会kCCDecodeError为更新版本的 iOS(6 和 7)设置为。

除了使用添加身份验证的密码,您还可以添加自己的身份验证标签,例如通过计算密文上的 HMAC 值。最好为 HMAC 使用第二个密钥,并将 IV 包含在经过身份验证的数据中。请注意,在使用明文或解密最后一个块(CBC 模式加密)之前,您需要检查身份验证标签。否则你将容易受到填充预言机攻击。

请注意,您将无法完全区分不正确的密钥和密文的损坏。

于 2014-09-03T21:47:00.163 回答
0

本质上,没有办法知道消息是正确解密还是错误解密。就 AES(以及大多数加密)而言,它只是位输入和位输出。这是一个特点。

该错误CCCryptorStatus仅处理严重错误。它不会为不正确的填充设置 kCCDecodeError,这已在 Apple Developer Forums 上进行了详细讨论。

确定解密是否正确是二战中解密的一个主要问题。本质上需要一个“婴儿床”,即已知用于测试解密的消息的某些部分。来自维基百科:“婴儿床”一词起源于英国二战时期的解密行动布莱切利公园。

如果您需要,您需要将其添加到您用于通信的协议中。

于 2014-09-03T21:47:52.150 回答