Node: Crypto aes-256-ecb wrong results

Created on 29 Jan 2018  路  12Comments  路  Source: nodejs/node

  • Version: v8.9.1
  • Platform: win 7 x64 ultimate

I'm not sure it's a bug or it's my fault, but better be safe than sorry. I tried to encrypt "aaaaaaaaaaa" in aes-256-ecb the results doesn't match an online tool:

var encrypt = function(cryptkey, cleardata) {
    var encipher = crypto.createCipher('aes-256-ecb', cryptkey);
    return Buffer.concat([
        encipher.update(cleardata),
        encipher.final()
    ]);
}
var buf_hex_key = new Buffer([0x2A,0x46,0x29,0x4A,0x40,0x4E,0x63,0x52,0x66,0x55,0x6A,0x58,0x6E,0x32,0x72,
0x35,0x75,0x38,0x78,0x2F,0x41,0x3F,0x44,0x28,0x47,0x2B,0x4B,0x61,0x50,0x64,0x53,0x67])
var buf_hex_plain_text =  new Buffer([0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61]) 
console.log(encrypt(buf_hex_key,buf_hex_plain_text))

//prints
// <Buffer 2c 2d 90 b3 0f ef 3d 6f 73 68 db da 72 34 9c 80>

Here I encrypted the same string "aaaaaaaaaaa" with the same key, the result is:
ad 5b f9 11 82 ce dd 1d 1d b0 da b3 48 c1 c8 b8

(edited by @addaleax: syntax highlighting)

crypto invalid

Most helpful comment

Ah, I got it. It seems like you are (unintentionally) using zero padding (PaddingMode.Zeros in .NET). Unless you know exactly what you are doing, you should probably stick to PKCS#7 padding, but if you really want to use zero padding, you can do the same with node:

const crypto = require('crypto');

const key = Buffer.from('2a46294a404e635266556a586e3272357538782f413f4428472b4b6150645367', 'hex');
const plaintext = Buffer.from('aaaaaaaaaaa', 'utf8');

function zeroPad(data) {
  const bs = data.length % 16;
  return bs === 0 ? data :
                    Buffer.concat([ data, Buffer.alloc(16 - bs, 0) ]);
}

const data = zeroPad(plaintext);
const cipher = crypto.createCipheriv('aes-256-ecb', key , Buffer.from([]));
cipher.setAutoPadding(false);
const ciphertext = Buffer.concat([ cipher.update(data), cipher.final() ]);
console.log(ciphertext.toString('hex'));
// prints ad5bf91182cedd1d1db0dab348c1c8b8

Again, neither ECB nor zero padding is an obvious choice, so unless there is a good reason to use either setting, consider using a more secure mode such as CBC with a random IV and PKCS#7 padding.

All 12 comments

I鈥檓 certainly not an expert, but I cannot see anything wrong with your code.

/cc @nodejs/crypto

crypto.createCipher() does key derivation/stretching (don't ask - https://gist.github.com/bnoordhuis/2de2766d3d3a47ebe41aaaec7e8b14df is the algorithm if you're interested.)

Use crypto.createCipheriv() with a zero-length IV and you should get the expected output. I'll close this out, cheers.

You are using buf_hex_key as a password, not as the key. createCipher derives a key from the password instead of using the password itself as the key: The password is 2a46294a404e635266556a586e3272357538782f413f4428472b4b6150645367 and the generated key is 2445acf9c7c26716c2b58bb94e7357d1e594a5653c66bb056e7545b3d9527499. You should use crypto.createCipheriv:

const crypto = require('crypto');

const key = Buffer.from('2a46294a404e635266556a586e3272357538782f413f4428472b4b6150645367', 'hex');
const plaintext = Buffer.from('aaaaaaaaaaa', 'utf8');
const cipher = crypto.createCipheriv('aes-256-ecb', key , Buffer.from([]));
const ciphertext = Buffer.concat([ cipher.update(plaintext), cipher.final() ]);
console.log(ciphertext.toString('hex'));
// prints 8fc98936ba7b162aa8bc11a4b4cde308

You can verify the result with any other crypto library, e.g. PyCrypto:

from binascii import unhexlify, hexlify
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

key = unhexlify('2A46294A404E635266556A586E3272357538782F413F4428472B4B6150645367')
data = pad(unhexlify('6161616161616161616161'), 16)
cipher = AES.new(key, AES.MODE_ECB)
ciphertext = cipher.encrypt(data)
print(hexlify(ciphertext))
# prints b'8fc98936ba7b162aa8bc11a4b4cde308'

@tniessen thanks, but as you can see the output still doesn't match that online tool.
I even checked with a c++ library, the output should be:
ad 5b f9 11 82 ce dd 1d 1d b0 da b3 48 c1 c8 b8

Two separate crypto libraries agree; you can hardly argue with that. Chances are the bug is in that tool.

I even checked with a c++ library, the output should be:
ad 5b f9 11 82 ce dd 1d 1d b0 da b3 48 c1 c8 b8

Can you show us your code? If you didn't believe me after seeing what node and PyCrypto compute, let's try javax.crypto:

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
Key key = new SecretKeySpec(new byte[] { 0x2A, 0x46, ..., 0x53, 0x67 }, "AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] ciphertext = cipher.doFinal("aaaaaaaaaaa".getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : ciphertext)
    sb.append(String.format("%02x", b & 0xff));
System.out.println(sb);
// prints 8fc98936ba7b162aa8bc11a4b4cde308

Nope, still the same. Let's try a fourth codebase, .NET, just for fun:

using (Aes aes = new AesManaged { Mode = CipherMode.ECB, Padding = PaddingMode.PKCS7 })
{
  byte[] key = new byte[] { 0x2A, 0x46, ..., 0x53, 0x67 };
  byte[] data = new byte[] { 0x61, 0x61, ..., 0x61, 0x61 };

  ICryptoTransform s = aes.CreateEncryptor(key, null);
  byte[] cipher = s.TransformFinalBlock(data, 0, 11);
  Console.Write(BitConverter.ToString(cipher));
  // prints 8F-C9-89-36-BA-7B-16-2A-A8-BC-11-A4-B4-CD-E3-08
}

Still correct.

Ah, I got it. It seems like you are (unintentionally) using zero padding (PaddingMode.Zeros in .NET). Unless you know exactly what you are doing, you should probably stick to PKCS#7 padding, but if you really want to use zero padding, you can do the same with node:

const crypto = require('crypto');

const key = Buffer.from('2a46294a404e635266556a586e3272357538782f413f4428472b4b6150645367', 'hex');
const plaintext = Buffer.from('aaaaaaaaaaa', 'utf8');

function zeroPad(data) {
  const bs = data.length % 16;
  return bs === 0 ? data :
                    Buffer.concat([ data, Buffer.alloc(16 - bs, 0) ]);
}

const data = zeroPad(plaintext);
const cipher = crypto.createCipheriv('aes-256-ecb', key , Buffer.from([]));
cipher.setAutoPadding(false);
const ciphertext = Buffer.concat([ cipher.update(data), cipher.final() ]);
console.log(ciphertext.toString('hex'));
// prints ad5bf91182cedd1d1db0dab348c1c8b8

Again, neither ECB nor zero padding is an obvious choice, so unless there is a good reason to use either setting, consider using a more secure mode such as CBC with a random IV and PKCS#7 padding.

This is the class I tried https://github.com/Urban82/Aes256.
Very thanks for your time.

This is the class I tried https://github.com/Urban82/Aes256.

I didn't have a closer look, but I would personally advise against using that implementation for various reasons.

thanks, maybe you can recommend a good c++ library?
I choose that library and ECB because they're easy to manage and integrate in my project (it's just an academic proof of concept, the pratical security is not a main issue)

No personal opinion there. Node.js is written in C++ and we are using OpenSSL which is the most popular crypto library there is, though some people might prefer LibreSSL. If you are looking for a more C++-ish library, Crypto++ seems to be a popular choice, but I never used it, so I don't have any insights about usability, features or security.

Ah, I got it. It seems like you are (unintentionally) using zero padding (PaddingMode.Zeros in .NET). Unless you know exactly what you are doing, you should probably stick to PKCS#7 padding, but if you really want to use zero padding, you can do the same with node:

const crypto = require('crypto');

const key = Buffer.from('2a46294a404e635266556a586e3272357538782f413f4428472b4b6150645367', 'hex');
const plaintext = Buffer.from('aaaaaaaaaaa', 'utf8');

function zeroPad(data) {
  const bs = data.length % 16;
  return bs === 0 ? data :
                    Buffer.concat([ data, Buffer.alloc(16 - bs, 0) ]);
}

const data = zeroPad(plaintext);
const cipher = crypto.createCipheriv('aes-256-ecb', key , Buffer.from([]));
cipher.setAutoPadding(false);
const ciphertext = Buffer.concat([ cipher.update(data), cipher.final() ]);
console.log(ciphertext.toString('hex'));
// prints ad5bf91182cedd1d1db0dab348c1c8b8

Again, neither ECB nor zero padding is an obvious choice, so unless there is a good reason to use either setting, consider using a more secure mode such as CBC with a random IV and PKCS#7 padding.

Thanks Tniessen,

Was this page helpful?
0 / 5 - 0 ratings

Related issues

vsemozhetbyt picture vsemozhetbyt  路  3Comments

danialkhansari picture danialkhansari  路  3Comments

filipesilvaa picture filipesilvaa  路  3Comments

dfahlander picture dfahlander  路  3Comments

fanjunzhi picture fanjunzhi  路  3Comments