I need to work with data which was encrypted using the aes-128-ctr implementation of trezor-crypto in python. However, using pycryptodome in python to encrypt the same data with the same parameters giving me a different result.
C++ code using trezor-crypto library
#include <vector>
#include <iostream>
#include <cassert>
#include "TrezorCrypto/aes.h"
std::vector<uint8_t> iv = HexToBytes("cd26feb0cb51469d06b8134251da1d02");
std::vector<uint8_t> key = HexToBytes("130886bab757196acdd97243cad73d74571c462ed8c8791afbfce94ca6a37a1c");
std::vector<uint8_t> plaintext = HexToBytes("48656c6c6f20576f726c6421"); // = Hello World! in utf-8
aes_encrypt_ctx ctx;
auto result = aes_encrypt_key128(key.data(), &ctx);
assert(result == EXIT_SUCCESS);
std::vector<uint8_t> encrypted(plaintext.size());
result = aes_ctr_encrypt(plaintext.data(), encrypted.data(), plaintext.size(), iv.data(), aes_ctr_cbuf_inc, &ctx)
assert(result == EXIT_SUCCESS);
std::cout << BytesToHex(encrypted.data(), encrypted.size()) << std::endl;
// e61da4e002a75e857a3a2954
I double-checked the
HexToBytes
/BytesToHex
helpers which produce the exact same byte sequence asbytes.fromhex
/bytes.hex
methods in python!
Python code using PyCryptoDome
from Crypto.Util import Counter
from Crypto.Cipher import AES
iv = bytes.fromhex("cd26feb0cb51469d06b8134251da1d02")
key = bytes.fromhex("130886bab757196acdd97243cad73d74571c462ed8c8791afbfce94ca6a37a1c")
plaintext = bytes.fromhex("48656c6c6f20576f726c6421")
ctr = Counter.new(128, initial_value=int.from_bytes(iv))
aes = AES.new(key, AES.MODE_CTR, counter=ctr)
encrypted = aes.encrypt(plaintext)
print(encrypted.hex())
# c80f8d90f05dc9346f972b62
Both scripts use the exact same data but produce different results (e61da4e002a75e857a3a2954
in c++/trezor-crypto and c80f8d90f05dc9346f972b62
in python/pycryptodome).
My best guess is that the Counter
setup in the python code is wrong but I can't find the exact issue.
Is there something wrong with the python code or does trezor-crypto's functions produce wrong results?
After following the advice of J_H's answer, I realized the key I use is 32-byte (which is aes-256-ctr) instead of 16-byte long. Since the PyCryptoDome aes implementation handles encrypting the key itself, it (correctly) used aes-256-ctr while my c++ implementation was using aes-128-ctr without key length check.
So for my example, the fix is to use aes_encrypt_key
(which checks the key length and would chose aes_encrypt_key256
for my key) instead of aes_encrypt_key128
.
#include <vector>
#include <iostream>
#include <cassert>
#include "TrezorCrypto/aes.h"
std::vector<uint8_t> iv = HexToBytes("cd26feb0cb51469d06b8134251da1d02");
std::vector<uint8_t> key = HexToBytes("130886bab757196acdd97243cad73d74571c462ed8c8791afbfce94ca6a37a1c");
std::vector<uint8_t> plaintext = HexToBytes("48656c6c6f20576f726c6421"); // = Hello World! in utf-8
aes_encrypt_ctx ctx;
auto result = aes_encrypt_key(key.data(), key.size(), &ctx); // let trezor-crypto check and chose keylength
assert(result == EXIT_SUCCESS);
std::vector<uint8_t> encrypted(plaintext.size());
result = aes_ctr_encrypt(plaintext.data(), encrypted.data(), plaintext.size(), iv.data(), aes_ctr_cbuf_inc, &ctx)
assert(result == EXIT_SUCCESS);
std::cout << BytesToHex(encrypted.data(), encrypted.size()) << std::endl;
// c80f8d90f05dc9346f972b62