【问题标题】:Common Crypto -- Testing for bad key in decryption?通用加密——在解密中测试坏密钥?
【发布时间】:2014-10-28 08:28:05
【问题描述】:

我编写了使用 iOS 的 Common Crypto 加密和解密 NSData 对象的代码。加密密钥为 AES128,并存储在 iOS 钥匙串中。我可以成功地加密和解密数据,所以我知道那部分代码正在工作。然而,作为健全性检查,我还生成了 second AES128 密钥并尝试解密使用 first 加密密钥加密的数据。我原以为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 作为我的选项。有没有办法在使用错误密钥进行解密时进行捕获,以便我可以返回适当的错误?

【问题讨论】:

    标签: ios objective-c encryption cryptography commoncrypto


    【解决方案1】:

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


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

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

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

    【讨论】:

    • 就我个人而言,我喜欢 KCV(密钥检查值),因为它们可以为您提供一些可能出现问题的迹象。但是 KCV 也可能会损坏,因此您仍然无法区分坏密钥和损坏的数据。另请参阅我的问题here。请注意,您的协议使用 KCV 和 MAC - 仅添加 KCV 是不够的,您应该在回答中明确说明。
    • 由于我已经在使用 HKDF-Expand 生成其他密钥材料,继续该过程并使用它来生成验证器似乎很自然。不过,我很高兴听到有关格式的任何改进建议。虽然验证器(或 KCV)本身可能已损坏,但对于可能需要大量时间进行 HMAC 验证的大型消息而言,它仍然代表着显着的好处,并且验证器之外的损坏更可能发生(但错误的密码是甚至更有可能)。如果验证器签出但 HMAC 失败,验证器也是明确的。
    • 我对验证令牌很满意。是的,使用 KDF 创建验证令牌显然是个好主意,然后我自己肯定会选择 HKDF。验证令牌是比 KCV 更好的名称,因为计算不直接涉及密钥本身。
    • 杰出的建议!我的加密背景是在 Java 平台上的,所以我更关心在 iOS 上如何处理加密。我一定会看看 RNCrypto。看起来是一个经过深思熟虑的项目。
    • 请注意,v3 文件格式有几种现有的实现,包括在 Java 中。不过,该格式不包括密码验证器。您无法区分错误密码和损坏消息(尽管通常是错误密码)。
    【解决方案2】:

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

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

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

    【讨论】:

    • PS 只浏览了您的代码,所以不要将其视为完整评论。
    • 它特别不区分这两种情况。
    • @Zaph 我从未说过这是一个指标,但我编辑了这个问题以明确解决您的问题。
    • @Zaph 您能否指出该讨论,正如CCCryptorFinal 明确指出的那样:“kCCDecodeError 表示密文乱码或解密过程中的密钥错误。这只能在启用填充的情况下解密时返回。"
    • CCCrypt always return kCCSuccess。特别看3。这个问题在论坛里不是唯一出现的,只是最简洁的。
    【解决方案3】:

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

    错误CCCryptorStatus 只处理严重错误。它不会为不正确的填充设置 kCCDecodeError,这已在 Apple 开发者论坛上进行了详细讨论。

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

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

    【讨论】:

    • 在二战中使用“婴儿床”可能是个好主意,但现在不行了。身份验证标签(经过身份验证的加密、MAC 或 HMAC)现在是。
    • 使用“Crib”表示解密可能已成功。它是对消息的一部分的了解,例如标准前缀或称呼。这不是身份验证。
    • 是的,这就是问题所在。只有一个婴儿床,密文很容易受到填充预言攻击。如果它太小,那么攻击者可以尝试,直到你得到正确的婴儿床。而这只是问题的开始......
    • @owlstead 也许您误解了婴儿床是什么以及它的用途。 Crib 用于创建安全性,它用于破解加密。见Known-plaintext attack,那是婴儿床。
    • 你知道,我以前很尊重你的答案和cmets,但看起来你好争辩,不愿意放弃鞭打死马。请注意,整个婴儿床都是历史视角(二战)。
    猜你喜欢
    • 2020-04-23
    • 2015-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-24
    • 1970-01-01
    • 2011-05-09
    • 2020-05-01
    相关资源
    最近更新 更多