NodeJS uses OpenSSL under the hood and the code for CTR mode can be found here: ctr128.c implementation An equivalent function in Node.js might look like this:
function ctr128Inc(counter) {
let c = 1;
let n = 16;
do {
n -= 1;
c += counter[n];
counter[n] = c & 0xFF;
c = c >> 8;
} while (n);
}
This function increments the counter by one block. To increment by multiple blocks, you might wrap it as follows:
function incrementIVOpenSSL(iv, increment) {
for (let i = 0; i < increment; i++)
ctr128Inc(iv)
}
However, this method is inefficient for large increments due to its linear time complexity and is practically unusable in real-world applications.
BigInt
Node.js introduces the BigInt
type, which can handle arbitrarily large integers efficiently. We can utilize it to increment the IV by converting the IV buffer to a BigInt
, performing the increment, and converting it back to a Buffer
:
const IV_MAX = 0xffffffffffffffffffffffffffffffffn;
const IV_OVERFLOW_MODULO = IV_MAX + 1n;
function incrementIvByFullBlocks(originalIv: Buffer, fullBlocksToIncrement: bigint): Buffer {
let ivBigInt = bufferToBigInt(originalIv);
ivBigInt += fullBlocksToIncrement;
if (ivBigInt > IV_MAX)
ivBigInt %= IV_OVERFLOW_MODULO;
return bigIntToBuffer(ivBigInt);
}
function bufferToBigInt(buffer: Buffer): bigint {
const hexedBuffer = buffer.toString(`hex`);
return BigInt(`0x${hexedBuffer}`);
}
function bigIntToBuffer(bigInt: bigint): Buffer {
const hexedBigInt = bigInt.toString(16).padStart(32, `0`);
return Buffer.from(hexedBigInt, `hex`);
}
Only this method isn't as fast as the one proposed by @youen. On my PC, for 100k iterations, @youn's method finishes in 15ms and BigInt
version in 90ms. It is not a big difference though and BigInt version is by far more obvious for a reader.
Another implementation can be found in the crypto-aes-ctr library.
It performs the increment operation more quickly (~7ms for 100,000 iterations) but sacrifices readability. It also supports more edge cases, mostly connected with incrementing IV by very big numbers. Something that probably won't be the case in real-life scenarios for a very long time (until we switch to Petabytes drives).
For a detailed comparison refer to my GitHub gist. The BigInt
method and the OpenSSL-inspired function are the only ones passing all edge case tests, with the BigInt
approach offering a good balance between performance and readability.
aes-ctr-concurrent
To simplify the process and enhance performance in concurrent environments, I've developed the aes-ctr-concurrent
library, available on NPM. This library:
crypto
module.