Reputation: 3853
I've reimplemented this AES c++ decryption in nodejs.
The "buffer" contains the encrypted content. The "decryptKey" contains the key to decrypt the "buffer". The "expectedOutput" contains the expected output.
In order to bypass the bad decrypt exception thrown by node I had to disable the autoPadding in my crypt object.
To simplify I added the c++ code and I added tests to three different algorithms (AES-128-ECB, AES-192-ECB, AES-256-ECB). None of the decrypt results match the C++ output.
What am I missing?
var crypto = require('crypto');
var buffer = new Buffer([
0x5e,0x51,0xa3,0x53,0x9d,0xe7,0xe5,0xd3,
0xee,0x30,0xbb,0xf8,0x0c,0x72,0x9f,0x80
]);
var decryptKey = new Buffer([
0x36, 0x46, 0xb4, 0xf6,
0x8e, 0x6d, 0xdc, 0xf4,
0xb0, 0x31, 0x7e, 0x81,
0x6b, 0x5d, 0x96, 0x55
])
/*
After looking to my C++ code I noticed that despite of providing a 32 length key the 128 argument ensures that only the first 16 bytes are used
var decryptKey = new Buffer([
0x36, 0x46, 0xb4, 0xf6,
0x8e, 0x6d, 0xdc, 0xf4,
0xb0, 0x31, 0x7e, 0x81,
0x6b, 0x5d, 0x96, 0x55, // 16
0x15, 0x9c, 0x78, 0x54,
0x8c, 0xca, 0x3e, 0x39,
0x2d, 0x49, 0x75, 0x5d,
0xa1, 0x1a, 0xc3, 0xe3 // 32
])*/
var expectedOutput = new Buffer([
0xc8,0x6c,0x8f,0x2b,0xe8,0x21,0xc4,0x2e,
0xfb,0x4a,0x8e,0x8b,0xc3,0x94,0x19,0xc2
]);
// aes_context aes_ctx;
function decrypt(data, password, algorithm, padding){
if (padding === void 0) padding = true;
algorithm = algorithm || 'aes-128-ecb';
//aes_setkey_dec( &aes_ctx, digest, 128 );
var crypt = crypto.createDecipher(algorithm,password);
crypt.setAutoPadding(padding);
// aes_crypt_ecb( &aes_ctx, AES_DECRYPT, buffer, buffer );
var res = crypt.update(data, null, 'hex')
res += crypt.final('hex');
return new Buffer(res,'hex');
}
// aes_setkey_dec( &aes_ctx, digest, 128 );
var algoList = [
'aes-128-ecb',
'aes-192-ecb',
'aes-256-ecb'
];
for (var i = 0; i<= 1; i++){
console.log('\n ******* AUTO PADDING: ' + (padding ? 'ON': 'OFF') + ' ********* ');
var padding = i === 0;
for (let algo of algoList){
try {
var output = decrypt(buffer, decryptKey, algo, padding);
console.log(algo + ' => ' + output.toString('hex') + ' < ' + (Buffer.compare(expectedOutput, output) === 0 ? 'ok' : 'ko'))
} catch (err){
console.log('Failed to perform ' + algo + ' with autopadding ' + (padding ? ' on ': ' off ') + ' due to ' + err.message);
}
}
}
/*
******* AUTO PADDING: OFF *********
Failed to perform aes-128-ecb with autopadding on due to error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Failed to perform aes-192-ecb with autopadding on due to error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Failed to perform aes-256-ecb with autopadding on due to error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
******* AUTO PADDING: ON *********
aes-128-ecb => d9817f142f9bca262b67f6a6be570345 < ko
aes-192-ecb => 9181784373bb6060c04c9ba75de26322 < ko
aes-256-ecb => c5945203368de477e5f0dbeedeb2189f < ko
*/
Heres the c++ code
#include "aes.h"
#include "sha2.h"
int main(int argc, char *argv[]) {
unsigned char data[16] = {
0x5e,0x51,0xa3,0x53,0x9d,0xe7,0xe5,0xd3,0xee,0x30,0xbb,0xf8,0x0c,0x72,0x9f,0x80
};
unsigned char key[32] = {
0x36, 0x46, 0xb4, 0xf6,
0x8e, 0x6d, 0xdc, 0xf4,
0xb0, 0x31, 0x7e, 0x81,
0x6b, 0x5d, 0x96, 0x55, // 16
0x15, 0x9c, 0x78, 0x54,
0x8c, 0xca, 0x3e, 0x39,
0x2d, 0x49, 0x75, 0x5d,
0xa1, 0x1a, 0xc3, 0xe3 // 32
};
aes_context aes_ctx;
aes_setkey_dec(&aes_ctx, key, 128);
aes_crypt_ecb(&aes_ctx, AES_DECRYPT, data, data);
for (int i = 0; i< sizeof(data); ++i)
std::cout << std::hex << (int)data[i];
/* Output => c86c8f2be821c42efb4a8e8bc39419c2*/
}
references:
The solution based in answer below
var crypto = require('crypto')
var buffer = new Buffer([
0x5e,0x51,0xa3,0x53,0x9d,0xe7,0xe5,0xd3,
0xee,0x30,0xbb,0xf8,0x0c,0x72,0x9f,0x80
]);
var decryptKey = new Buffer([
0x36, 0x46, 0xb4, 0xf6,
0x8e, 0x6d, 0xdc, 0xf4,
0xb0, 0x31, 0x7e, 0x81,
0x6b, 0x5d, 0x96, 0x55
])
var expectedOutput = new Buffer([
0xc8,0x6c,0x8f,0x2b,0xe8,0x21,0xc4,0x2e,
0xfb,0x4a,0x8e,0x8b,0xc3,0x94,0x19,0xc2
]);
// aes_context aes_ctx;
function decrypt(data, password, algorithm, padding){
if (padding === void 0) padding = true;
algorithm = algorithm || 'aes-128-ecb';
//aes_setkey_dec( &aes_ctx, digest, 128 );
var crypt = crypto.createDecipheriv(algorithm,password, new Buffer([]));//new Buffer(32).fill(0).byteLength
crypt.setAutoPadding(padding);
// aes_crypt_ecb( &aes_ctx, AES_DECRYPT, buffer, buffer );
var res = crypt.update(data, null,'hex')
+ crypt.final('hex');
return new Buffer(res,'hex');
}
// aes_setkey_dec( &aes_ctx, digest, 128 );
var output = decrypt(buffer, decryptKey, 'aes-128-ecb', false);
console.log(Buffer.compare(expectedOutput, output) === 0 ? 'ok' : 'ko');
Upvotes: 2
Views: 1573
Reputation: 85371
The deprecated crypto.createDecypher()
derives the key from the password argument:
The implementation of
crypto.createDecipher()
derives keys using the OpenSSL functionEVP_BytesToKey
with the digest algorithm set to MD5, one iteration, and no salt.
What you want instead is to use a raw key. For that you should use crypto.createDecipheriv()
instead:
var crypt = crypto.createDecipheriv(algorithm,password,new Buffer([]));
(In ECB mode the IV can be empty)
Of course, the key length must then match the requested algorithm (128, 192 or 256 bits). You key is 128 bits, so only aes-128-ecb will work.
Upvotes: 3
Reputation: 960
The reason you need to pass false
to setAutoPadding
is that your ciphertext is not padded. You haven't included your encryption code that generates the ciphertext, but I can tell from the length of it that it is not padded, because a plaintext of one block size in length, when encrypted using PKCS#7 padding, will produce a ciphertext that is two block sizes in length, rather than the single block that you have.
The reason this decrypts with aes_crypt_ecb
is that it looks like aes_crypt_ecb
simply performs individual block encryption/decryption, so does not do any padding or unpadding. The Javscript code though, if passed true
to the setAutoPadding
function, will be expecting padding (probably PKCS#7). Which will of course fail because your encryption has not used padding.
Ideally you should modify your encryption code so that it uses proper padding when doing the encryption, as this is much more secure than not using padding. Otherwise you will need to make sure that your call to setAutoPadding
is passing false
when doing the decryption, so the Javascript knows there is no padding.
Upvotes: 0