Using AES-256-ctr to encode Id

Viewed 274

I'm using Node's crypto native api to encode an id because it's going to be shown in an URL parameter, the problem is, it encodes in a way it can be guessed and I can't comprehend why, here's my code.

const encrypt = (key, value) => {
const iv = crypto.randomBytes(16);

const cipher = crypto.createCipheriv('aes-256-ctr', Buffer.from(key), iv);
let encrypted = cipher.update(value);
encrypted = Buffer.concat([encrypted, cipher.final()]);

return `${iv.toString('hex')}-${encrypted.toString('hex')}`;
}

what happens is that the AES encoded id outputs in a predictable way, for example, a number 5 outputs the code 160f20bea36be22f90b092f876f1abdd-55 and note that if I change the last two numbers to 56, the output became 6, or to 54, the id's discovered because it outputs 7.

What I want it to do is to give me a secure encoded id.

1 Answers

Your problem with predictability of output comes from the fact that you are using CTR mode. Let's see how CTR mode works:

CTR Mode

CTR is a streaming mode. As you see, CTR mode works based on encrypting a counter which starts from 1 and XORing result with your plain data. Nonce in this picture is IV. This mechanism has a drawback as you mentioned. If we assume that C(i) is encrypted counter i, we have:

Cipher(i) = Plain(i) xor C(i)

Since C(i) does not depend on input, if you change Plain(i), Cipher(i) will be changed based on your Plain(i) change:

Cipher(i) = Plain(i) xor C(i) => C(i) = Cipher(i) xor Plain(i)
then for block i:
Cipher(x) = Plain(x) xor C(i) => Cipher(x) = Plain(x) xor Cipher(i) xor Plain(i)

XOR is add without carry. That's why you feel that your result is predictable. But this only happens IF your IV is fixed and as a result, shows importance of IV in CTR mode.

I should say that your code in general does not have any main issue since you use:

const iv = crypto.randomBytes(16);

to generate a new IV each time for encrypting and add that IV to your encoded result. Since you change IV every time, C(i) will be different for each encrypted data and no one can guess the new encrypted data. As an example:

input data              encrypted output
---------------------------------------------------
5               160f20bea36be22f90b092f876f1abdd-16
6               160f20bea36be22f90b092f876f1abdd-15  // Feels predictable since IV is similar

6               160f20bea36be22f90b092f876f1abde-ee  // Sample input, but completely deferent 
                                                     // output since I changed 1 bit in IV

So in general, your code does not have any major problems cryptography-wise. But since you need to keep IV in your result for decryption, your encrypted data result will be large.

Related