So, a new year, a new set of assignments… one being that I’ve been given the job to write a comms driver for a series of electricity meters. These meters happen to use encryption using the AES-256-CBC
cipher, and I needed to talk to these from within a NodeJS environment.
No problem I thought, there’s a crypto
module. I muddled my way through the documentation… wrote pack
and unpack
functions that process the packets for the meter… then began writing unit tests.
Testing the pack
function went fine, but when I came to the unpack
, I hit a strange issue. I was getting truncated data. WTF? I added some console.log
statements to see what was going on…
write 8b6fbcd1964bc93b6dcd6be409ff0b6b15c11f0764b6c54b02f515b83cd1b164e620d753c349ac45bcf3eca31f93de4c - 48 bytes
- read cd25400dc12613687df9ce2f9f6161d23c2d1e0f000000000000000000000000
read done
read done
read cd25400dc12613687df9ce2f9f6161d23c2d1e0f000000000000000000000000 - 32 bytes
So this is using the stream interface of the Decipher
object. Initially this is what I used since that was the first example I came to in the documentation. Did I miss an error? No, nothing there. Did I need to break up the input into smaller pieces instead of writing it in one gulp? No, that didn’t help.
The only thing that was async
in my async
function was the cryptographic steps… so I thought maybe reverting to update
/final
might help? No, although it did mean I could drop async
/await
so that wasn’t so bad.
Nothing made sense… then I had a look at what pack
was doing… I just assumed ciphertext output would be the same size as the cleartext. Was it? No, it was giving me extra, that I was then truncating when inserting it into the packet. Where did this extra junk come from?
In the end, the problem was right here:
if (decrypt) {
const ciphertext = packet.fields.inner.raw;
/* Next IV will be the last 128 bits */
next_iv = Uint8Array.prototype.slice.call(
ciphertext, ciphertext.length - 16, ciphertext.length
);
let cleartext = [];
let decipher = crypto.createDecipheriv(
cipher_algo || CIPHER_ALGO, key, iv
);
decipher.setAutoPadding(false); // ← HERE!!!
cleartext.push(decipher.update(ciphertext));
cleartext.push(decipher.final());
packet.fields.inner.raw = Buffer.concat(cleartext);
}
NodeJS by default, pads the data you give it. In my code, I am already padding the input, as I actually need to know how many bytes of the input are padding bytes so I can compute/validate CRC checksums and do things the way the meter does them.
Naturally, setAutoPadding
needs to be called both sides. I had to update my test cases for pack
, but now at least, I get out the same quantity of data I put in both sides with no surprises.
Recent Comments