Reputation: 6333
For some odd reason, Node's built-in Cipher
and Decipher
classes aren't working as expected. The documentation states that cipher.update
"Returns the enciphered contents, and can be called many times with new data as it is streamed."
The docs also state that cipher.final
"Returns any remaining enciphered contents."
However, in my tests you must call cipher.final
to get all of the data, thus rendering the Cipher object worthless, and to process the next block you have to create a new Cipher object.
var secret = crypto.randomBytes(16)
, source = crypto.randomBytes(8)
, cipher = crypto.createCipher("aes128", secret)
, decipher = crypto.createDecipher("aes128", secret);
var step = cipher.update(source);
var end = decipher.update(step);
assert.strictEqual(source.toString('binary'), end); // should not fail, but does
Note that this happens when using crypto.createCipher
or crypto.createCipheriv
, with the secret as the initialization vector. The fix is to replace lines 6 and 7 with the following:
var step = cipher.update(source) + cipher.final();
var end = decipher.update(step) + decipher.final();
But this, as previously noted, renders both cipher
and decipher
worthless.
This is how I expect Node's built-in cryptography to work, but it clearly doesn't. Is this a problem with how I'm using it or a bug in Node? Or am I expecting the wrong thing? I could go and implement AES directly, but that would be time-consuming and annoying. Should I just create a new Cipher
or Decipher
object every time I need to encrypt or decrypt? That seems expensive if I'm doing so as part of a stream.
Upvotes: 1
Views: 6938
Reputation: 94098
AES uses block sizes of 16 bytes (not two times 8 as you were suggesting). Furthermore, if padding is enabled it should always pad. The reason for this is that otherwise the unpadding algorithm cannot distinguish between padding and the last bytes of the plaintext.
Most of the time you should not expect the ciphertext to be the same size as the plain text. Make sure that doFinal()
is always called. You should only use update this way for encryption / decryption if you are implementing your own encryption scheme.
Upvotes: 2
Reputation: 6333
I was having two problems: the first is that I assumed, incorrectly, that the size of a block would be 64 bits, or 8 bytes, which is what I use to create the "plaintext." In reality the internals of AES split the 128 bit plaintext into two 64 bit chunks, and go from there.
The second problem was that despite using the correct chunk size after applying the above changes, the crypto
module was applying auto padding, and disabling auto padding solved the second problem. Thus, the working example is as follows:
var secret = crypto.randomBytes(16)
, source = crypto.randomBytes(16)
, cipher = crypto.createCipheriv("aes128", secret, secret); // or createCipher
, decipher = crypto.createDecipheriv("aes128", secret, secret);
cipher.setAutoPadding(false);
decipher.setAutoPadding(false);
var step = cipher.update(source);
var end = decipher.update(step);
assert.strictEqual(source.toString('binary'), end); // does not fail
Upvotes: 4
Reputation: 8083
There's a node.js issue with calling update multiple times in a row. I suppose it's been solved and reflected in the next release.
Upvotes: 0