Saturday, February 1, 2014

Node.js Crypto Module Examples

I recently had the need to use the node crypto module in a project.  To get this to work properly, paying attention to the last two sentences on the cipher.update function documentation is critical.

cipher.update(data, [input_encoding], [output_encoding])#

Updates the cipher with data, the encoding of which is given in input_encoding and can be 'utf8','ascii' or 'binary'. If no encoding is provided, then a buffer is expected. If data is a Buffer theninput_encoding is ignored.
The output_encoding specifies the output format of the enciphered data, and can be 'binary''base64' or'hex'. If no encoding is provided, then a buffer is returned.

To illustrate, here are 3 simple examples:

1.  Concat cipher text strings with in/out encoding specified.

var plainText = '1234567812345678'; 
var cipher1 = crypto.createCipher('aes256', 'password'); 
var cipherText1 = cipher1.update(plainText, 'ascii','binary'); 
cipherText1 +='binary'); 
console.log('Method 1 - plainText:' + plainText ); 
console.log('Method 1 - cipherText length:' + cipherText1.length); 
var decipher1 = crypto.createDecipher('aes256', 'password'); 
var result1 = decipher1.update(cipherText1); 
result1 +=; 
console.log('Method 1 - result:' + result1);

This one works as advertised. Output below:

Method 1 - plainText:1234567812345678 
Method 1 - cipherText length:32 
Method 1 - result:1234567812345678

2.  Concat buffers with no in/out encoding specified.
var plainText = '1234567812345678'; 
var cipher2 = crypto.createCipher('aes256', 'password'); 
var cipherText2 = Buffer.concat([cipher2.update(new Buffer(plainText)),]); 
console.log('Method 2 - plainText:' + plainText ); 
console.log('Method 2 - cipherText length:' + cipherText2.length); 
var decipher2 = crypto.createDecipher('aes256', 'password'); 
var result2 = decipher2.update(cipherText2); 
result2 +=; 
console.log('Method 2 - result:' + result2);

Also works. Output below
Method 2 - plainText:1234567812345678 Method 2 - cipherText length:32 Method 2 - result:1234567812345678
3. String concat with NO encoding specification.
var cipher3 = crypto.createCipher('aes256', 'password'); 
var cipherText3 = cipher3.update(plainText); 
cipherText3 +=; 
console.log('Method 3 - plainText:' + plainText ); 
console.log('Method 3 - cipherText length:' + cipherText3.length); 
var decipher3 = crypto.createDecipher('aes256', 'password'); 
var result3 = decipher3.update(cipherText3); 
result3 +=; //Boom 
console.log('Method 3 - result:' + result3);
Method 3 - plainText:1234567812345678 
Method 3 - cipherText length:29 error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
The tip off as to what is happening is that cipherText length. It's only 29 in this example,but was 32 in the others. cipher.update() is returning a Buffer object, just as advertised. Concat'ing those buffers leads to an implicit toString() call on those buffer objects. That call leads to a default UTF-8 conversion in Buffer (also as advertised in the documentation).Cipher text is mangled in that conversion and the result can't be decrypted. Source code used in this discussion here.