aes-ctr-concurrent
Version:
Native TypeScript Node.js library for AES-256-CTR, enabling concurrent encryption/decryption with precise IV offset handling down to any byte level, not just full blocks.
55 lines • 2.71 kB
JavaScript
import crypto from "crypto";
import AesCtrConcurrentError from "./errors/aesCtrConcurrentError.js";
const aesBlockSizeInBytes = 16n;
const IV_MAX = 0xffffffffffffffffffffffffffffffffn;
const IV_OVERFLOW_MODULO = IV_MAX + 1n;
export function createCipher(key, iv, startPositionInBytesNumberOrBigInt = 0n) {
const startPositionInBytes = BigInt(startPositionInBytesNumberOrBigInt);
return getCryptoStream(`cipher`, key, iv, startPositionInBytes);
}
export function createDecipher(key, iv, startPositionInBytesNumberOrBigInt = 0n) {
const startPositionInBytes = BigInt(startPositionInBytesNumberOrBigInt);
return getCryptoStream(`decipher`, key, iv, startPositionInBytes);
}
function getCryptoStream(cipherOrDecipher, key, iv, startPositionInBytes) {
throwIfParametersAreInvalid(iv, key, startPositionInBytes);
const fullAesBlocksIncrement = startPositionInBytes / aesBlockSizeInBytes;
const incrementedIv = incrementIvByFullBlocks(iv, fullAesBlocksIncrement);
const cipher = cipherOrDecipher === `cipher`
? crypto.createCipheriv(`aes-256-ctr`, key, incrementedIv)
: crypto.createDecipheriv(`aes-256-ctr`, key, incrementedIv);
moveCounterToCurrentStartPositionWithinCurrentBlock(cipher, startPositionInBytes);
return cipher;
}
function throwIfParametersAreInvalid(iv, key, startPositionInBytes) {
if (!Buffer.isBuffer(iv) || iv.length !== 16)
throw new AesCtrConcurrentError(`IV is required to be 16 bytes long Buffer`);
if (!Buffer.isBuffer(key) || key.length !== 32)
throw new AesCtrConcurrentError(`Key is required to be 32 bytes long Buffer`);
if (startPositionInBytes < 0n)
throw new AesCtrConcurrentError(`Start position must be greater or equal to 0`);
}
// This method is exported only for test purpose
export function incrementIvByFullBlocks(originalIv, fullBlocksToIncrement) {
let ivBigInt = bufferToBigInt(originalIv);
ivBigInt += fullBlocksToIncrement;
if (ivBigInt > IV_MAX)
ivBigInt %= IV_OVERFLOW_MODULO;
return bigIntToBuffer(ivBigInt);
}
function bufferToBigInt(buffer) {
const hexedBuffer = buffer.toString(`hex`);
return BigInt(`0x${hexedBuffer}`);
}
function bigIntToBuffer(bigInt) {
const hexedBigInt = bigInt.toString(16).padStart(32, `0`);
return Buffer.from(hexedBigInt, `hex`);
}
function moveCounterToCurrentStartPositionWithinCurrentBlock(cipher, startPositionInBytes) {
const currentBlockOffset = Number(startPositionInBytes % aesBlockSizeInBytes);
if (currentBlockOffset === 0)
return;
const bytesToBeDiscarded = Buffer.alloc(currentBlockOffset);
cipher.update(bytesToBeDiscarded);
}
//# sourceMappingURL=aesCtrConcurrent.js.map