3
votes

I'm writing my own security class using the crypto module of Node.js and the AES-256-CBC cipher algorithm.

But when I try to decrypt an encrypted string, encrypted from input data longer than 15 characters, fails with this error:

crypto.js:153
var ret = this._handle.final();
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt

I think that the problem is with the encryption or the IV generation, in fact, the encrypted hex string is always 32 chars long.

Let's review the code together:

var crypto = require("crypto"),
    password = "mySecureKey",
    salt = "mySaltKey";

//generate the IV
crypto.pbkdf2(password , salt, 4096, 8, "sha1", function(err, key) {
    if (err) throw err;   
    var cipher_iv = new Buffer(key.toString('hex'));

    //encrypt the string
    var input = "helloPrettyWorld";
    cipher = crypto.createCipheriv("aes-256-cbc", new Buffer(password), cipher_iv);
    cipher.update(input, "utf8", "hex");
    var encrypted = cipher.final("hex"); //i.e: input = "hello"; encrypted = "2300743605fbdaf0171052ccc6322e96"

    //decrypt the string
    cipher = crypto.createDecipheriv("aes-256-cbc", new Buffer(password), cipher_iv); /* THE ERROR IS THROWN HERE */
    cipher.update(encrypted, "hex", "utf8")     
    var decrypted = cipher.final("utf8");
});

I tried with resizing the password/salt lengths and even using string with fixed-length (32, 16, etc..), but does not to resolve the problem.

Recap:

An input data like: "helloNiceWorld" (14 chars) will be encrypted and decrypted perfectly, while an input data like "helloPrettyWorld" (16 chars) will not.

1

1 Answers

10
votes

TL;DR You're throwing away a part of the plaintext and ciphertext by not using the result of cipher.update().


AES is a block cipher that only works on blocks of 16 bytes (block size). CBC mode extends this to plaintexts with a length of a multiple the block size. A padding (PKCS#7 padding by default) is then needed to fill up the plaintext to the next multiple of the block size.

This means that the padding is part of the last operation before finishing the encryption. Padding is applied in the cipher.final() function.

cipher.update() returns a part of the ciphertext, but doesn't process (it's cached internally) the last bytes in order to apply padding. At the end of the encryption cipher.final() must be called in order to apply the padding to the last cached bytes and do the encryption.

Since the PKCS#7 padding always adds padding, you will get a two block padded plaintext when the plaintext is 16 to 31 bytes long. Now, the problem is that you're not storing the result from the cipher.update() call which results in incomplete ciphertext. If your message is smaller than a single block, then cipher.update() would return something empty (string or Buffer) and you'll get the complete ciphertext from cipher.final().

Don't forget to concatenate the different ciphertext and plaintext parts:

cipher = crypto.createCipheriv("aes-256-cbc", new Buffer(password), cipher_iv);
var encrypted = cipher.update(input, "utf8", "hex");
encrypted += cipher.final("hex");

//decrypt the string
cipher = crypto.createDecipheriv("aes-256-cbc", new Buffer(password), cipher_iv);
var decrypted = cipher.update(encrypted, "hex", "utf8")     
decrypted += cipher.final("utf8");

Other considerations:

password = "mySecureKey" If this is indeed some text, then it's not secure and not a key, but as the variable names says, it's a password. Don't use passwords directly as keys. Derive the key from a password with a randomly generated salt (per password).

Additionally, generate a new random IV during each encryption and simply put it in front of the ciphertext. The IV doesn't have to be secret, but it needs to be unpredictable. If you re-use the IV, then an attacker who observes your ciphertexts may determine whether you send previously sent messages again. If you use random IV, you get semantic security.

Furthermore, authenticate your ciphertexts with an encrypt-then-MAC scheme with a strong MAC like HMAC-SHA256. This enables you to detect any (malicious) modifications of the ciphertext and prevents attacks such as the padding-oracle attack.