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

2

6

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.

Indiscrimination answered 2/8, 2017 at 22:5 Comment(0)
D
8

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.

Dropsical answered 2/8, 2017 at 22:30 Comment(2)
Ah, I have to admit that thought crossed my mind. Corrected the code where I prefix the ciphertext returned by encrypt with the IV used during encryption, and separate it again before decryption. The assertion succeeds now. Thanks a lot!Indiscrimination
to decrypt first retrieve the IV from the first 16 bytes and **then decrypt the rest** - thank you! This is what helped me out for my issue.Nearby
M
1

I was observing the same issue yet in a slightly different context. Answering as other people might land on this question.

Encryption using AES w/ ECB was resulting in a correct decryption, but when I switched to encryption using AES w/ CBC, decryption and the first few bytes were being garbage.

This is probably because you are not feeding correctly the IV to the decryption API - look for it being invalid like hex string where api is waiting for binary, only when CBC the first block is being decrypted with the use of the initialization vector IV.

Matlock answered 17/8, 2021 at 1:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.