StreamTransformationFilter:使用 AES 解密发现无效的 PKCS #7 块填充

StreamTransformationFilter: invalid PKCS #7 block padding found using AES decryption

我正在尝试使用 crypto++ 库执行 AES 解密。我有一个加密文件,其前 8 个字节是文件长度,随后的 16 个字节是初始化向量,其余数据是感兴趣的数据。我也有我的密钥的字符串表示(我使用 SHA256 对其进行哈希处理)

尝试执行 AES 解密时出现以下错误: StreamTransformationFilter: invalid PKCS #7 block padding found

我正在使用以下 C++ 代码:

std::string keyStr = "my_key";
std::string infilePath = "my/file/path";


CryptoPP::SHA256 hash;
unsigned char digest[CryptoPP::SHA256::DIGESTSIZE];

hash.CalculateDigest( digest, reinterpret_cast<const unsigned char*>(&keyStr[0]), keyStr.length() );
auto key = CryptoPP::SecByteBlock(digest, CryptoPP::SHA256::DIGESTSIZE);

std::ifstream fin(infilePath, std::ifstream::binary);

// First 8 bytes is the file size
std::vector<char> fileSizeVec(8);
fin.read(fileSizeVec.data(), fileSizeVec.size());

// Read the next 16 bytes to get the initialization vector
std::vector<char> ivBuffer(16);
fin.read(ivBuffer.data(), ivBuffer.size());
CryptoPP::SecByteBlock iv(reinterpret_cast<const unsigned char*>(ivBuffer.data()), ivBuffer.size());

// Create a CBC decryptor
CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption decryption;
decryption.SetKeyWithIV(key, sizeof(key), iv);

CryptoPP::StreamTransformationFilter decryptor(decryption);

std::vector<char> buffer(CHUNK_SIZE, 0);

while(fin.read(buffer.data(), buffer.size())) {
    CryptoPP::SecByteBlock tmp(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size());
    decryptor.Put(tmp, tmp.size());
        }

decryptor.MessageEnd();
size_t retSize = decryptor.MaxRetrievable();

std::vector<char> decryptedBuff;
decryptedBuff.resize(retSize);

decryptor.Get(reinterpret_cast<CryptoPP::byte*>(decryptedBuff.data()), decryptedBuff.size());

我不确定是什么导致了我的错误。我正在处理以下 python 代码。当我 运行 python 代码与相同的输入文件时,它成功解密了文件。

def decrypt_file(in_filename, out_filename=None):
    key = hashlib.sha256(PASSWORD).digest()
    """loads and returns the embedded model"""
    chunksize = 24 * 1024
    if not out_filename:
        out_filename = os.path.splitext(in_filename)[0]

    with open(in_filename, 'rb') as infile:
        # get the initial 8 bytes with file size
        tmp = infile.read(8)
        iv = infile.read(16)
        decryptor = AES.new(key, AES.MODE_CBC, iv)
        string = b''
        # with open(out_filename, 'wb') as outfile:
        while True:
            chunk = infile.read(chunksize)
            if len(chunk) == 0:
                break
            string += decryptor.decrypt(chunk)
    return string

除了解决错误之外,我还喜欢一些关于如何改进的一般 c++ 编码反馈。

提前致谢!

编辑: 看起来我并没有从头到尾读取输入文件(因为最后一个块的长度小于 CHUNK_SIZE)。下面的代码现在读取整个文件,但我仍然遇到同样的问题。我还确认 IVkey 与 python 代码生成的完全匹配。

 // Get the length of the file in bytes
fin.seekg (0, fin.end);
size_t fileLen = fin.tellg();
fin.seekg (0, fin.beg);

std::vector<char> buffer(CHUNK_SIZE, 0);
size_t readSize = CHUNK_SIZE;

while(fin.read(buffer.data(), readSize)) {
     CryptoPP::SecByteBlock tmp(reinterpret_cast<const unsigned char*>(buffer.data()), CHUNK_SIZE);
     decryptor.Put(tmp, tmp.size());

      std::fill(buffer.begin(), buffer.end(), 0);

      size_t bytesReamining = fileLen - fin.tellg();
      readSize = CHUNK_SIZE < bytesReamining ? CHUNK_SIZE : bytesReamining;

       if (!readSize)
            break;
        }
}

请注意,我已将此行作为 CryptoPP::SecByteBlock tmp(reinterpret_cast<const unsigned char*>(buffer.data()), CHUNK_SIZE);CryptoPP::SecByteBlock tmp(reinterpret_cast<const unsigned char*>(buffer.data()), readSize);(使用 CHUNK_SIZE 打击垫和 0

看起来问题与填充有关。我转而使用 StringSource,它只在我指定 CryptoPP::BlockPaddingSchemeDef::BlockPaddingScheme::ZEROS_PADDING 作为 StreamTransformationFilter

的参数后才起作用

这里是任何感兴趣的人的工作代码:

    void Crypto::decryptFileAES(CryptoPP::SecByteBlock key, std::string infilePath) {
        std::ifstream fin(infilePath, std::ifstream::binary);

        // Get the length of the file in bytes
        fin.seekg (0, fin.end);
        size_t fileLen = fin.tellg();
        fin.seekg (0, fin.beg);

        // First 8 bytes is the file size
        std::vector<char> fileSizeVec(8);
        fin.read(fileSizeVec.data(), fileSizeVec.size());

        // Read the first 16 bytes to get the initialization vector
        std::vector<char> ivBuffer(16);
        fin.read(ivBuffer.data(), ivBuffer.size());
        CryptoPP::SecByteBlock iv(reinterpret_cast<const unsigned char*>(ivBuffer.data()), ivBuffer.size());

        // Create a CBC decryptor
        CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption decryption;
        decryption.SetKeyWithIV(key, sizeof(key), iv);

        size_t bytesReamining = fileLen - fin.tellg();
        std::vector<char> buffer(bytesReamining);

        if(!fin.read(buffer.data(), bytesReamining)) {
            throw std::runtime_error("Unable to read file");
        }

        std::string decryptedText;

        CryptoPP::StringSource ss(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), true,
                new CryptoPP::StreamTransformationFilter(decryption,
                        new CryptoPP::StringSink(decryptedText), CryptoPP::BlockPaddingSchemeDef::BlockPaddingScheme::ZEROS_PADDING));

        std::cout << decryptedText << std::endl;
    }

I have an encrypted file whose first 8 bytes are the filelength, subsequent 16 bytes are the initialization vector, and the remaining data is the data of interest...

我想我会切入正题,向您展示一种使用 Crypto++ 库进行操作的更简单方法。 key和iv都是hard-coded来简化代码。该示例不需要推导。顺便说一下,如果 Python 有它,你应该考虑使用 HKDF 来推导 AES 密钥和 iv。 HKDF 具有可证明的安全属性。

Crypto++ 为您处理分块。您不需要明确执行它;请参阅 Crypto++ wiki 上的 Pumping Data

我相信 Python 代码存在潜在的填充 oracle,因为使用了没有 MAC 的 CBC 模式。您可以考虑添加 MAC 或使用 Authenticated Encryption 操作模式。

#include "cryptlib.h"
#include "filters.h"
#include "osrng.h"
#include "modes.h"
#include "files.h"
#include "aes.h"
#include "hex.h"

#include <string>
#include <iostream>

const std::string infilePath = "test.dat";

int main(int argc, char* argv[])
{
    using namespace CryptoPP;

    const byte key[16] = {
        1,2,3,4, 1,2,3,4, 1,2,3,4, 1,2,3,4
    };
    const byte iv[16] = {
        8,7,6,5, 8,7,6,5, 8,7,6,5, 8,7,6,5
    };
    const byte data[] = // 70 characters
        "Now is the time for all good men to come to the aide of their country.";

    HexEncoder encoder(new FileSink(std::cout));
    std::string message;

    // Show parameters
    {        
        std::cout << "Key: ";
        StringSource(key, 16, true, new Redirector(encoder));
        std::cout << std::endl;

        std::cout << "IV: ";
        StringSource(iv, 16, true, new Redirector(encoder));
        std::cout << std::endl;

        std::cout << "Data: ";
        StringSource(data, 70, true, new Redirector(encoder));
        std::cout << std::endl;
    }

    // Write sample data
    {
        FileSink outFile(infilePath.c_str());
        word64 length = 8+16+70;

        outFile.PutWord64(length, BIG_ENDIAN_ORDER);
        outFile.Put(iv, 16);

        CBC_Mode<AES>::Encryption enc;
        enc.SetKeyWithIV(key, 16, iv, 16);

        StringSource(data, 70, true, new StreamTransformationFilter(enc, new Redirector(outFile)));
    }

    // Read sample data
    {
        FileSource inFile(infilePath.c_str(), true /*pumpAll*/);

        word64 read, l;
        read = inFile.GetWord64(l, BIG_ENDIAN_ORDER);
        if (read != 8)
            throw std::runtime_error("Failed to read length");

        SecByteBlock v(16);
        read = inFile.Get(v, 16);
        if (read != 16)
            throw std::runtime_error("Failed to read iv");

        CBC_Mode<AES>::Decryption dec;
        dec.SetKeyWithIV(key, 16, v, 16);

        SecByteBlock d(l-8-16);
        StreamTransformationFilter f(dec, new ArraySink(d, d.size()));
        inFile.CopyTo(f);
        f.MessageEnd();

        std::cout << "Key: ";
        StringSource(key, 16, true, new Redirector(encoder));
        std::cout << std::endl;

        std::cout << "IV: ";
        StringSource(v, 16, true, new Redirector(encoder));
        std::cout << std::endl;

        std::cout << "Data: ";
        StringSource(d, d.size(), true, new Redirector(encoder));
        std::cout << std::endl;

        message.assign(reinterpret_cast<const char*>(d.data()), d.size());
    }

    std::cout << "Message: ";
    std::cout << message << std::endl;

    return 0;
}

运行 程序结果:

$ g++ test.cxx ./libcryptopp.a -o test.exe
$ ./test.exe
Key: 01020304010203040102030401020304
IV: 08070605080706050807060508070605
Data: 4E6F77206973207468652074696D6520666F7220616C6C20676F6F64206D656E20746F2063
6F6D6520746F207468652061696465206F6620746865697220636F756E7472792E

Key: 01020304010203040102030401020304
IV: 08070605080706050807060508070605
Data: 4E6F77206973207468652074696D6520666F7220616C6C20676F6F64206D656E20746F2063
6F6D6520746F207468652061696465206F6620746865697220636F756E7472792E

Message: Now is the time for all good men to come to the aide of their country.

在此 Stack Overflow 问题之前,Crypto++ 库未提供 PutWord64GetWord64。与 Python 等库的互操作对项目很重要,因此将它们添加到 Commit 6d69043403a9 and Commit 8260dd1e81c3。它们将成为 Crypto++ 8.3 版本的一部分。

如果您使用的是 Crypto++ 8.2 或更低版本,您可以使用以下代码执行 64 位读取。

word64 length;
word32 h, l;

inFile.GetWord32(h, BIG_ENDIAN_ORDER);
inFile.GetWord32(l, BIG_ENDIAN_ORDER);

length = ((word64)h << 32) | l;

这是用于此示例的数据文件。

$ hexdump -C test.dat
00000000  00 00 00 00 00 00 00 5e  08 07 06 05 08 07 06 05  |.......^........|
00000010  08 07 06 05 08 07 06 05  b0 82 79 ee a6 d8 8a 0e  |..........y.....|
00000020  a6 b3 a4 7e 63 bd 9a bc  0e e4 b6 be 3e eb 36 64  |...~c.......>.6d|
00000030  72 cd ba 91 8d e0 d3 c5  cd 64 ae c0 51 de a7 c9  |r........d..Q...|
00000040  1e a8 81 6d c0 d5 42 2a  17 5a 19 62 1e 9c ab fd  |...m..B*.Z.b....|
00000050  21 3d b0 8f e2 b3 7a d4  08 8d ec 00 e0 1e 5e 78  |!=....z.......^x|
00000060  56 6d f5 3e 8c 5f fe 54                           |Vm.>._.T|