I have been unable to get existing legacy code in Python to encrypt/decrypt the same as NodeJS. I do get the first 16 characters correctly decoded.
Here is the Python code:
from Crypto.Cipher import AES
counter = b'HOJFKGQMRCTKUQHP'
cipher = AES.new(self.symmetric_key, AES.MODE_CTR, counter=lambda: counter)
encrypted = cipher.encrypt(data)
I found a source that the same counter is used in each iteration: PyCrypto problem using AES+CTR
What does work in NodeJS (ts-node) for the first 16 characters only is this:
import { createDecipheriv, Decipher } from 'crypto'
const decrypt = (inputBase64: string): string => {
const algorithm = 'aes-192-ctr'; // 24 byte key, 16 byte "counter"
var decipher: Decipher = createDecipheriv(algorithm, key, counter /* yes, it's an iv */)
// decipher.setAutoPadding(false);
return decipher.update(inputBase64, 'base64', 'utf8') + decipher.final('utf8');
}
I have found various online sources, which all increment the counter - is there a way to control the counter increment with the built-in Node crypto library? I have found some online implementations that I could possibly override (if that is what's happening): https://github.com/ricmoo/aes-js/blob/master/index.js#L656 https://github.com/brix/crypto-js/blob/develop/src/mode-ctr.js#L26
How can I get this to work in Node? My python code (which is legacy and I cannot change without migrating existing values) has this output:
encrypt('Testing--StackOverflow')
# outputs: 'r7G8gFNIHuY27nBjSo51nZ6mqZhVUQ=='
From node using above decrypt
function:
const key = 'LKOXBRRUNBOSMENKEPPZUKWB';
const counter = 'HOJFKGQMRCTKUQHP';
const encrypted = 'r7G8gFNIHuY27nBjSo51nZ6mqZhVUQ==';
const clearText = decrypt(encrypted);
console.log('clear text:', clearText)
// outputs: clear text: Testing--StackOv�::��m
Hope somebody can share some insight here!
Essential for the CTR mode is the incrementation of the counter, which is why this mode is actually called counter mode [1]. Since in the Python code the counter is a constant, i.e. not incremented at all, this code implements the CTR mode at most formally, but certainly not functionally and is also negligently insecure [2][3].
Most libraries will try to prevent such a misuse of the CTR mode. Therefore it is very unlikely to find a library in NodeJS (or any other language) that makes this intentionally possible.
Nevertheless, decryption in NodeJS (or any other language) is relatively easy to achieve by manually implementing the mode: In the CTR mode, a byte sequence is generated from the IV, starting with the initial IV, by appending the corresponding incremented IV for each block. The sequence generated in this way is then encrypted with AES and the result is XOR-ed with the plaintext / ciphertext [1]. This scheme applies to both encryption and decryption. Since the Python code doesn't perform any incrementation, this step must simply be skipped, e.g.:
const crypto = require('crypto');
var algorithm = 'aes-192-ecb';
var key = Buffer.from('LKOXBRRUNBOSMENKEPPZUKWB', 'utf8');
var byteSeq = Buffer.from('HOJFKGQMRCTKUQHPHOJFKGQMRCTKUQHP', 'utf8');
var ciphertext = Buffer.from('r7G8gFNIHuY27nBjSo51nZ6mqZhVUQ==', 'base64');
// Encrypt the byte-sequence generated from the IV (no incrementation)
var cipher = crypto.createCipheriv(algorithm, key, null);
var byteSeqEnc = Buffer.concat([cipher.update(byteSeq), cipher.final()], byteSeq.length);
// XORing of ciphertext and IV
var decrypted = xor(ciphertext, byteSeqEnc);
console.log("Decrypted: " + decrypted.toString('utf8'));
// Output: Decrypted: Testing--StackOverflow
// Implementation of XOR
function xor(buf1, buf2){
var buf = Buffer.alloc(buf1.length);
for (i = 0; i < buf1.length; i++){
buf.writeUInt8(buf1.readUInt8(i, i + 1) ^ buf2.readUInt8(i, i + 1), i);
}
return buf;
}
The length of the byte sequence corresponds to the length of the plaintext / ciphertext rounded up to an integer multiple of the blocksize. In the current example, the byte sequence has a length of 32 bytes and has been hard-coded for simplicity.