【问题标题】:OpenSSL EVP_aes_128_cbc decryption got unexpected resultOpenSSL EVP_aes_128_cbc 解密得到意外结果
【发布时间】:2020-12-02 03:51:27
【问题描述】:

我用 EVP_aes_128_cbc 密码加密了一个字符串,然后更改了密文的第一个字节,并解密了这个更改后的密文。出乎意料的是,它没有解密错误或得到完全错误的结果,而是得到了错误的第一个 16 字节和相同的剩余字符串。这是加密函数:

    int do_crypt1(unsigned char *in, unsigned char *outbuf, int inlen, int do_encrypt)
 {
     unsigned char inbuf[1024];
     int outlen;
     EVP_CIPHER_CTX *ctx;

     /*
      * Bogus key and IV: we'd normally set these from
      * another source.
      */
     unsigned char key[32] = "test";
     unsigned char iv[] = "1234567812345678";

     /* Don't set key or IV right away; we want to check lengths */
     ctx = EVP_CIPHER_CTX_new();
     EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, NULL, NULL,
                       do_encrypt);
     OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 16);
     OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == 16);

     /* Now we can set key and IV */
     EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, do_encrypt);
     int update_len = 0;

     for (;;) {
         memcpy(inbuf, in, inlen);
         if (inlen <= 0)
             break;
         
         if (!EVP_CipherUpdate(ctx, outbuf, &outlen, inbuf, inlen)) {
             /* Error */
             EVP_CIPHER_CTX_free(ctx);
             return 0;
         }
         update_len += outlen;
         if(inlen <= 1024)
            break;
        
     }
     if (!EVP_CipherFinal_ex(ctx, outbuf+outlen, &outlen)) {
         /* Error */
         EVP_CIPHER_CTX_free(ctx);
         return 0;
     }

     EVP_CIPHER_CTX_free(ctx);
    update_len += outlen;

     return update_len;
 }

主要是:

    unsigned char* b = (unsigned char*)malloc(1024);
    unsigned char* hmac_code = (unsigned char*)malloc(1024);

    int len = do_crypt1(value, hmac_code, strlen(value), AES_ENCRYPT);
    printf("%s\n", value);
    for (i = 0; i < len; i++)
        printf("%x", *(hmac_code + i));
    printf("\n");
    printf("%d\n", len);
    // printf("%s\n", hmac_code);
    *hmac_code = 0x23;
    len = do_crypt1(hmac_code, b, len, AES_DECRYPT);
    printf("%d\n", len);
    printf("%s\n", b);

    free(hmac_code);
    free(b);

result

谁能告诉我原因以及如何解决这个问题?

【问题讨论】:

    标签: c encryption openssl aes


    【解决方案1】:

    实际上,只要您的明文至少有 17 个字节,第一个 17 个解密字节就应该是错误的 - 但由于您将无效解密显示为文本,因此某些字节可能是不可见:在您的示例中,输出短了 2 个字符,因此前 17 个字符中的 2 个字符显然不可见。对于像 AES 这样的 16 字节块密码,这是 2 块(或更多)CBC 密文的第一个块(开始时)损坏的预期结果;请参阅wikipedia 中的图表以了解原因。

    如果你想检测密文的损坏,不要使用CBC模式,或者至少不要单独使用它。现在的常见/标准解决方案是:

    • 添加身份验证,例如 HMAC。 (有趣的是,您的代码已经使用了变量名称 hmac_code,即使您没有做任何与 HMAC 有远程关联的事情。)

    • 使用经过身份验证的加密模式,如 GCM,它在内部有效地结合了加密和(某种类型的)MAC。当今大多数经过身份验证的模式(包括 GCM)也支持经过身份验证但未加密的“附加”或“关联”数据,因此称为AEAD,但您可能会也可能不会关心这一点。

    • 使用错误传播模式。这些在几十年前很流行,大约在尼克松、福特、卡特和里根总统的时代,但现在大多被认为已经过时,并且不受 OpenSSL 直接支持。

    另外,顺便说一句,对于超过 1024 字节的值,您的代码完全是裤子,您的测试显然没有执行。首先,您根本不需要将这样的值分解成块,但如果出于某种原因,您实现的方法是错误的。

    另外,CBC 的 IV 应该是不同的并且每次都不可预测;使用这样的硬编码值会使您面临两种不同类型的攻击。一般来说,你会在 Stacks 上得到的与安全性相关的建议是“不要滚动你自己的”。由不知道自己在做什么的人编写的密码代码,即使/当它产生正确的输出时,通常也是不安全的。这是加密/安全软件与其他软件的主要区别;您可以轻松查看您的编辑器或电子表格或数据库是否产生正确或不正确的输出,只要输出正确,通常就是您所需要的,但您无法通过查看加密/解密程序的输出来判断它是否是否安全。

    【讨论】:

    • 感谢您的回答!其实这是一个简单的 OpenSSL 加密库测试,我也做了 HMAC 测试等等,所以变量名可能不清楚。方法的错误逻辑和iv的原因是一样的。
    • 我无法理解的是对于CBC模式,解密使用前一个块密文解密下一个块,当第一个块被篡改时,为什么其余块可以成功正确地解密? BTW,现在我实现的方法是使用ECB模式加密明文,在末尾添加一个字符串的crc32值。这种方式是否有效且安全?
    • 如果(任何)第一个密文块被更改,则第一个明文块是(全部)垃圾。第二个密文块没有更改并且被正确解密除了明文与第一个密文块中的更改有效异或,在您的测试中它只是它的第一个字节。第三个块没有改变,第二个块也没有改变,所以它可以正确解密。当您查看图表时,这一切都应该是显而易见的。 CRC 不是 MAC;更改密文的攻击者可以轻松伪造 CRC。在几乎所有情况下,欧洲央行都允许更多攻击。我的观点是正确的。
    • 知道了!现在我知道CBC是做什么的了。对于ECB和CRC,我的工作关注系统性能优化而不是最好的安全性,所以这可能相对来说不是一个坏方法,不确定。
    猜你喜欢
    • 2021-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多