Search code examples
pythonc++encryptionopenssl

C++ openssl decryption replaces beginning of data with some unreadable characters


While I was trying to decrypt some data (encrypted with openssl in c++ - code below) using python Crypto.Cipher aes and php openssl the same error appeared - beginning of the string was replaced with random binary characters like this:

{"pass":"azertyu","opt":...

replaced with:

"Ž{mÂ┬X▒ Íł.Q>4","opt":...

It's probably some issue with padding but I can't figure it out.

Python code to decrypt:

def decrypt(ciphertext_hex):
    key = b"t*H3_B3$t/k$%evr"
    data = binascii.unhexlify(ciphertext_hex)
    iv = data[:AES.block_size]
    data = data[AES.block_size:]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted = unpad(cipher.decrypt(data), AES.block_size)
    print(decrypted)
    # return decrypted.decode("utf-8", errors="replace")
    return decrypted.decode("utf-8")

C++ code to encrypt:

std::string binToHex(const std::string &input) 
{
    std::ostringstream hexStream;
    hexStream << std::hex << std::setfill('0');

    for (unsigned char c : input) 
    {
        hexStream << std::setw(2) << static_cast<int>(c);
    }

    return hexStream.str();
}

std::string crypt(const std::string &data, const std::string &key)
{
const unsigned char *keyBytes = reinterpret_cast<const unsigned char *>(key.c_str());
const unsigned char *dataBytes = reinterpret_cast<const unsigned char *>(data.c_str());
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();

    AES_KEY aesKey;
    if (AES_set_encrypt_key(keyBytes, 128, &aesKey) < 0) {
        std::cout << "key";
        return "";
    }
    unsigned char iv[AES_BLOCK_SIZE];
    if (RAND_bytes(iv, AES_BLOCK_SIZE) != 1)
    {
        std::cout << "rand bytes";
        return "";
    }
    
    size_t paddedLen = (data.length() + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE * AES_BLOCK_SIZE;
    unsigned char *paddedData = new unsigned char[paddedLen];
    std::memcpy(paddedData, dataBytes, data.length());
    memset(paddedData + data.length(), paddedLen - data.length(), paddedLen - data.length());
    unsigned char ciphertext[paddedLen];
    AES_cbc_encrypt(paddedData, ciphertext, paddedLen, &aesKey, iv, AES_ENCRYPT);
    std::string output(reinterpret_cast<char *>(iv), AES_BLOCK_SIZE);
    output.append(reinterpret_cast<char *>(ciphertext), paddedLen);
    
    delete[] paddedData;
    
    return binToHex(output);

}

Solution

  • As indicated in the comments, the IV changes. The data is simply replaced by the ciphertext of the last block, which is the vector to alter the plaintext before block-encrypting the next block. Please see the Wikipedia page on CBC for more information.

    Now you might ask why the IV is changed as this doesn't protect the user from the problem that you are experiencing. However, remember that you are using a low level, software implementation of AES if you use the AES_ functionality.

    As indicated in the documentation:

    These function provide a low-level interface to the AES symmetric cipher algorithm, also called Rijndael. For reasons of flexibility, it is recommended that application programs use the high-level interface described in EVP_EncryptInit(3) and EVP_aes_128_cbc(3) instead whenever possible.

    So I would not find the C++ acceptable at all; these low level functions should be avoided unless there are very specific requirements to use them.