node.jscryptographyaes

How do I fix "garbage" at the head of my AES decrypted ciphertext?


I am playing around with AES 256 implemented by the crypto module that is part of Node.js, to assess whether I can use it for a particular data protection feature I am planning as part of an application I am designing.

I am trying to verify decryption of some encrypted arbitrary plaintext and I can't get the original plaintext and the decrypted result to match, meaning something is wrong with my encryption, decryption or both.

From what I understand, I better pick a random initialization vector (IV), which I did using crypto.randomBytes(16) -- evidence suggests (documentation does not say much) it needs to be 128 bits. I also apparently need the CBC mode, which makes sense as my use case demands plaintexts be of arbitrary length. I also do not need PBKDF2 or anything of the sort because I choose my own key myself and this is not about passwords at all.

Now, some of it seems to be working, apparently, but the first 16 bytes of the plaintext I obtain from decryption are "garbled". A hunch tells me this has something to do with either the padding, IV or both. I am not sure why I should be choosing an IV for Decipher but regardless, only part of the decrypted plaintext matches the original, and I am at a loss about why.

To explain with code:

var key = fs.readFileSync("/root/key"); // 256 bits worth of key from a file

function encrypt(plaintext) {
    var cipher = crypto.createCipheriv("aes-256-cbc", key, crypto.randomBytes(16));
    return Buffer.concat([ cipher.update(plaintext), cipher.final() ]);
}

function decrypt(ciphertext) {
    var decipher = crypto.createDecipheriv("aes-256-cbc", key, crypto.randomBytes(16));
    return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
}

var plaintext = new Buffer("Quick brown fox jumps over the lazy dog.");

Evaluating plaintext.equals(decrypt(encrypt(plaintext))) yields false, so does plaintext.toString("utf8") == decrypt(encrypt(plaintext)).toString("utf8"). As mentioned earlier, visual inspection of decrypted plaintext vs original plaintext suggests different (wrong) data at the first 128 bits of the recovered (decrypted) plaintext.

This may have something to do with my misunderstanding about using IV at the decrypting stage, or the fact that two different random IVs are used.

What am I doing wrong, and more importantly what am I not getting with AES and the CBC mode if anything, without me having to grok everything there is to grok on chaining block ciphers?

I also tried mucking about with the data types involved -- using plain UTF-8 strings instead of Buffer for plain- and ciphertext, but the code I have above looks shortest and actually at least successfully decrypts (subsequently failing my later assertions about the result), whereas some of the other attempts get me an actual "bad decrypt" error from the underlying decryption procedure.


Solution

  • The IV needs to be identical for encryption and decryption. This is often accomplished by prefixing the randomly generated (and unencrypted) IV to the ciphertext. For CBC the IV is always the same size as the block size - always 16 bytes - to decrypt first retrieve the IV from the first 16 bytes and then decrypt the rest.