DeronW/rc5.js

Trailing zeros are lost

Closed this issue · 3 comments

Thanks for this project. I think I may have found a small bug. It seems like when decrypting data, trailing zeros are truncated. Given the following script:

import RC5  from "rc5";

const rc5 = new RC5("a key", 64, 255);

const ptBinary = Uint8Array.from([1, 2, 3, 4, 5, 0, 0]);
console.log(ptBinary);

const encrypted = rc5.encrypt(Buffer.from(ptBinary));
console.log(new Uint8Array(encrypted));

const decrypted = rc5.decrypt(encrypted);
const decryptedBinary = new Uint8Array(decrypted);

console.log(decryptedBinary);

The output will be:

Uint8Array(7) [
  1, 2, 3, 4,
  5, 0, 0
]
Uint8Array(16) [
   63, 25,  84, 142, 217, 231,
  151, 93, 213,  18,  76, 156,
  224, 88,  64, 216
]
Uint8Array(5) [ 1, 2, 3, 4, 5 ]

You can see in the output that the decrypted array is missing the two trailing zeros.

It looks like the problem might arise in the decrypt function:

decrypt(cipher) {
    let filled = this.process(cipher, decryption), pos = filled.length - 1;
    while (filled[pos] == 0)
        pos--;
    return filled.slice(0, pos + 1);
}

It looks like the while loop and slice will trim off any trailing zeros.

It's truly a bug, now decrypt method accept a trim option, default is true, for no breaking reason, and decrypt will not trim tailing zeros if set trim to false. the version of rc5.js update from 2.0.0 to 2.1.0 now. Thanks for suggestion.

more detail in README.md

note rc5 is block cipher algorithm, every block has the same length, if the last block is not long enough, it will completing with 0x00, and rc5.js will trim the tailing 0x00 by default, but maybe it's wrong because the source data is the 0x00. In such case, you should pass set option trim to false, not finished yet! then you should calculate the correct bytes by yourself, see the example:

Great! Thanks for the update. If it helps your users, I have included a quick example of how I deal with this. In the example below, you can see that after encrypting the data I prepend the plainText length to the buffer before returning it. When decrypting, I strip that value off prior to passing it back to RC5, and then use the value to manually trim the decrypted data. I have had to use this same approach for a few other block ciphers.

Anyway, this might help other people that run into this issue, so I thought I would share it here.

import RC5  from "rc5";

const key = "a key";

const plainText = Buffer.of(1, 2, 3, 4, 5, 0, 0);
console.log(plainText);

const encrypted = encrypt(plainText, key);
console.log(encrypted);

const decrypted = decrypt(encrypted, key);
console.log(decrypted);

function encrypt(plainText: Buffer, key: string): Buffer {
  const rc5 = new RC5(key, 64, 255);
  const cipherText = rc5.encrypt(plainText);

  const result = Buffer.alloc(cipherText.byteLength + 4);
  new DataView(result.buffer).setInt32(0, plainText.length);
  result.set(cipherText, 4);

  return result;
}

function decrypt(cipherText: Buffer, key: string): Buffer {
  const ptLen = new DataView(cipherText.buffer).getInt32(0);
  const encryptedData = Buffer.from(cipherText.subarray(4));

  const rc5 = new RC5(key, 64, 255);
  const plainText = rc5.decrypt(encryptedData, {trim: false});

  return Buffer.from(plainText.subarray(0, ptLen));
}