【问题标题】:After decrypting EVP cipher with OpenSSL, 8 bytes of plaintext are always incorrect用 OpenSSL 解密 EVP 密码后,8 字节的明文总是不正确
【发布时间】:2016-11-03 22:18:06
【问题描述】:

我在使用 EVP 函数和对称密钥使用 OpenSSL 库解密某些数据时遇到问题。我像使用 openssl enc 一样加密命令中的数据,然后使用 C++ 代码解密。这很有效。

无论我使用什么数据,在我执行解密之后,明文中的第二个 8 字节块是不正确的(字节 8 到 15) - 但文件的其余部分是正确的。我什至用一个 130 兆的文件完成了这项工作——所有 130 兆都是完全正确的,并且在文件中的正确位置,除了那些字节。

这发生在 ARM 目标和在 Ubuntu 12.04 上构建时(不同的库、不同的工具链)。

这是一个有问题的简短完整程序。下面是一些演示它的终端输出。

#include <string>
#include <fstream>
#include <stdexcept>
#include <openssl/evp.h>

void decrypt_and_untar(const std::string& infile, const std::string& outfile)
{
    unsigned char key[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
                           0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
    unsigned char iv[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};

    std::ifstream is(infile, std::ios_base::in | std::ios_base::binary);
    std::ofstream os(outfile, std::ios_base::out | std::ios_base::binary);

    auto ctx = EVP_CIPHER_CTX_new();

    if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key, iv))
    {
        const int BufferSize = 10240;
        unsigned char enc_buff[BufferSize];
        int bytes_in_enc_buff = 0;
        unsigned char dec_buff[BufferSize + EVP_CIPHER_CTX_block_size(ctx)];
        int bytes_in_dec_buff = 0;

        while (!is.eof())
        {
            is.read(reinterpret_cast<char*>(enc_buff), BufferSize);
            bytes_in_enc_buff = is.gcount();
            if (bytes_in_enc_buff > 0)
            {
                if (EVP_DecryptUpdate(ctx, dec_buff, &bytes_in_dec_buff, enc_buff, bytes_in_enc_buff))
                {
                    os.write(reinterpret_cast<char*>(dec_buff), bytes_in_dec_buff);
                    bytes_in_enc_buff = 0;
                    bytes_in_dec_buff = 0;
                }
                else
                    throw std::runtime_error("Failed DecryptUpdate.");
            }
        }

        if (EVP_DecryptFinal_ex(ctx, dec_buff, &bytes_in_dec_buff))
            os.write(reinterpret_cast<char*>(dec_buff), bytes_in_dec_buff);
        else
            throw std::runtime_error("Failed DecryptFinal.");
    }
    else
    {
        throw std::runtime_error("Failed DecryptInit.");
    }


    EVP_CIPHER_CTX_free(ctx);
}

int main(int argc, char* argv[])
{
    if (argc == 3)
        decrypt_and_untar(argv[1], argv[2]);

    return 0;
}

这是一个实际问题的演示。我创建了一个全为零的 1 meg 文件,对其进行加密、解密,然后使用od 查看它。如果我多次执行此操作,则这 8 个错误字节的值会随着运行而变化...

~/workspace/test_crypto/Debug$ dd if=/dev/zero of=plain.original bs=1024 count=1024
1024+0 records in
1024+0 records out
1048576 bytes (1.0 MB) copied, 0.00154437 s, 679 MB/s
~/workspace/test_crypto/Debug$ od -t x1 plain.original 
0000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
4000000
~/workspace/test_crypto/Debug$ /usr/bin/openssl enc -aes-256-cbc -salt -in plain.original -out encrypted -K 00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF -iv 0011223344556677
~/workspace/test_crypto/Debug$ ./test_crypto encrypted plain
~/workspace/test_crypto/Debug$ od -t x1 plain
0000000 00 00 00 00 00 00 00 00 00 40 02 0d 18 93 b8 bf
0000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
4000000
~/workspace/test_crypto/Debug$ 

【问题讨论】:

  • 哎哟!你甚至知道调用EVP_CIPHER_CTX_block_size(ctx) 来获取块大小:) 下一次,让代码自己调试:ASSERT(EVP_CIPHER_CTX_block_size(ctx) == COUNTOF(iv));。您甚至可以对密钥执行类似操作。
  • @jww 好吧,API 文档对此非常清楚。我希望其他事情也很清楚:) 谢谢。

标签: c++11 openssl aes evp-cipher


【解决方案1】:

iv 很短,iv 需要是一个完整的块长度,对于 AES 来说是 16 字节。这就是字节 8-15 不正确的原因,它们对应于缺失的 iv 字节。

CBC mode 中,在加密时,iv 与第一个纯文本块进行异或,在解密时与解密的输出进行异或。最好的猜测是,加密实现正在拾取 8 字节的垃圾,刚刚超过(简称)iv,因此每次运行时加密和解密都是不同的。

【讨论】:

  • 就是这样!谢谢!
  • 顺便说一句 - 这只会影响这 8 个字节吗?我希望该算法从那时起“关闭”,或者可能每隔一个 8 字节块错误,等等......
  • 加密时第一个块与iv异或,下一个块与第一个块的加密数据异或,iv仅用于第一个块。在解密期间,第一个块被解密并与 iv 异或,前 8 个字节是正确的,第二个是垃圾,所以输出的第二个 8 字节是垃圾。第二个块被解密并与前面的块加密数据进行异或,因此它是正确的。通常,CBC 模式将从损坏的块中恢复。请参阅CBC mode link
猜你喜欢
  • 2021-03-17
  • 1970-01-01
  • 1970-01-01
  • 2012-06-02
  • 1970-01-01
  • 2021-06-06
  • 1970-01-01
  • 2019-11-19
  • 2022-12-11
相关资源
最近更新 更多