Search code examples
c++encryptionaescrypto++

Decryption of an arbitrary chosen element from a vector of encrypted items causes invalid PKCS #7 block error


I am trying to encrypt some sequential data using AES in Crypto++. However, I noticed that randomly choosing an encrypted item and decrypt it causes a StreamTransformationFilter: invalid PKCS #7 block padding found

A simple code that causes the same problem is the following:

AESUtil aes;
std::vector<std::string> t = std::vector<std::string>();

for (int i = 0; i < 100; ++i) {
    std::string p = "a";
    auto c = aes.encrypt(p);
    t.push_back(c);
}

auto d = aes.decrypt(t[78]);

where AESUtil is as follow:

AESUtil::AESUtil() {
    this->key = CryptoPP::SecByteBlock(0x00, CryptoPP::AES::DEFAULT_KEYLENGTH);
    rnd.GenerateBlock(key, key.size());

    // Generate a random IV
    this->iv = CryptoPP::SecByteBlock(CryptoPP::AES::BLOCKSIZE);
    rnd.GenerateBlock(iv, iv.size());

    this->aesEncryption = CryptoPP::AES::Encryption(key, CryptoPP::AES::DEFAULT_KEYLENGTH);
    this->cbcEncryption = CryptoPP::CBC_Mode_ExternalCipher::Encryption(aesEncryption, iv);

    this->aesDecryption = CryptoPP::AES::Decryption(key, CryptoPP::AES::DEFAULT_KEYLENGTH);
    this->cbcDecryption = CryptoPP::CBC_Mode_ExternalCipher::Decryption(aesDecryption, iv);
}

void AESUtil::encrypt(std::string &ciphertext, std::string &plaintext) {
    CryptoPP::StreamTransformationFilter stfEncryptor(this->cbcEncryption, new CryptoPP::StringSink(ciphertext));
    stfEncryptor.Put(reinterpret_cast<const unsigned char *>( plaintext.c_str()), plaintext.size());
    stfEncryptor.MessageEnd();
}

void AESUtil::decrypt(std::string &plaintext, std::string &ciphertext) {
    CryptoPP::StreamTransformationFilter stfDecryptor(this->cbcDecryption, new CryptoPP::StringSink(plaintext));
    stfDecryptor.Put(reinterpret_cast<const unsigned char *>( ciphertext.c_str()), ciphertext.size());
    stfDecryptor.MessageEnd();
}

std::string AESUtil::encrypt(std::string plaintext) {
    std::string ciphertext;
    //ciphertext.reserve(plaintext.size()+16);
    encrypt(ciphertext, plaintext);
    return ciphertext;
}

std::string AESUtil::decrypt(std::string ciphertext) {
    std::string plaintext;
    //plaintext.reserve(ciphertext.size()+16);
    decrypt(plaintext, ciphertext);
    return plaintext;
}


I thought the problem was linked to \0 null terminating character in strings, so I changed the code to use hex (not sure it is actually correct). However, the issue persists.

void AESUtil::encrypt(std::string &ciphertext, std::string &plaintext) {
    CryptoPP::ByteQueue encrypted;
    CryptoPP::StreamTransformationFilter f1(this->cbcEncryption, new CryptoPP::Redirector(encrypted));

    //f1.PutWord32((uint32_t)v1.size(), BIG_ENDIAN_ORDER);
    f1.Put((const unsigned char *) plaintext.c_str(), plaintext.size());
    f1.MessageEnd();
    CryptoPP::HexEncoder encoder(new CryptoPP::StringSink(ciphertext));
    encrypted.CopyTo(encoder);
    encoder.MessageEnd();
}

void AESUtil::decrypt(std::string &plaintext, std::string &ciphertext) {
    CryptoPP::ByteQueue decrypted;
    CryptoPP::HexDecoder decoder(new CryptoPP::Redirector(decrypted));
    decoder.Put(reinterpret_cast<const unsigned char *>( ciphertext.data()), ciphertext.size());
    decoder.MessageEnd();

    CryptoPP::StreamTransformationFilter f2(this->cbcDecryption, new CryptoPP::StringSink(plaintext));
    decrypted.CopyTo(f2);
    f2.MessageEnd();
}

With the following code, both the AESUtil versions work without any problem:

for (int i = 0; i < 100; ++i) {
        std::string p = "a";
        auto c = aes.encrypt(p);
        auto d = aes.decrypt(c);
    }

Any idea what is the problem and how I could solve it? Any help would be very appreciated


Solution

  • I have found the problem.

    The IV was changing even if MessageEnd was called, keeping a reference to the state of AESUtil.

    I solved the problem by randomly generating the IV during the encryption process and storing it together with the ciphertext.

    If anyone is interested, here is the new code:

    void AESUtil::encrypt(std::string &ciphertext, std::string &plaintext) {
        // Generate a random IV
        CryptoPP::SecByteBlock iv(CryptoPP::AES::BLOCKSIZE);
        this->rnd.GenerateBlock(iv, iv.size());
        //memset( iv, 0x00, CryptoPP::AES::BLOCKSIZE );
    
        CryptoPP::StringSink stringSink(ciphertext);
    
        CryptoPP::ArraySource as(iv, iv.size(), true,
                                 new CryptoPP::Redirector(stringSink));
    
        CryptoPP::CBC_Mode_ExternalCipher::Encryption cbcEncryption(this->aesEncryption, iv);
    
        CryptoPP::ByteQueue encrypted;
        CryptoPP::StreamTransformationFilter f1(cbcEncryption, new CryptoPP::Redirector(encrypted));
    
        f1.Put(reinterpret_cast<const unsigned char *>(plaintext.c_str()), plaintext.size());
        f1.MessageEnd();
    
        CryptoPP::HexEncoder encoder(new CryptoPP::Redirector(stringSink));
        encrypted.CopyTo(encoder);
        encoder.MessageEnd();
    
        std::string recovered;
        CryptoPP::StringSink sink(recovered);
        encrypted.CopyTo(sink);
    }
    
    void AESUtil::decrypt(std::string &plaintext, std::string &ciphertext) {
        CryptoPP::SecByteBlock iv(CryptoPP::AES::BLOCKSIZE);
        CryptoPP::StringSource ss(ciphertext, false /* DO NOT Pump All */);
    
        // Attach new filter
        CryptoPP::ArraySink as(iv, iv.size());
        ss.Attach(new CryptoPP::Redirector(as));
        ss.Pump(CryptoPP::AES::BLOCKSIZE);  // Pump first 16 bytes
    
        CryptoPP::StringSink stringSink(plaintext);
        CryptoPP::ByteQueue decrypted;
        CryptoPP::HexDecoder decoder(new CryptoPP::Redirector(decrypted));
    
        ss.Detach(new CryptoPP::Redirector(decoder));
        ss.PumpAll();
    
        CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(this->aesDecryption, iv);
        CryptoPP::StreamTransformationFilter f2(cbcDecryption, new CryptoPP::Redirector(stringSink));
        decrypted.CopyTo(f2);
        f2.MessageEnd();
    }