用OpenSSL解密EVP cipher后,8字节明文始终不正确

After decrypting EVP cipher with OpenSSL, 8 bytes of plaintext are always incorrect

我在使用 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 兆文件,对其进行加密、解密,然后使用 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$ 

iv 太短了,iv 需要是一个完整的块长度,AES 为 16 字节。这就是为什么字节 8-15 不正确,它们对应于丢失的 iv 字节。

CBC mode 中,加密时 iv 与第一个纯文本块进行异或运算,解密时与解密输出进行异或运算。最好的猜测是加密实现在(简而言之)iv 之后拾取 8 字节的垃圾,因此它在每个 运行 上都不同,用于加密和解密。