nhb-toolbox
Version:
A versatile collection of smart, efficient, and reusable utility functions, classes and types for everyday development needs.
69 lines (68 loc) • 3.15 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Cipher = void 0;
const primitives_1 = require("../guards/primitives");
const specials_1 = require("../guards/specials");
const helpers_1 = require("./helpers");
const utils_1 = require("./utils");
class Cipher {
#secretBytes;
#encKey;
#macKey;
constructor(secret) {
if (!(0, primitives_1.isNonEmptyString)(secret)) {
throw new Error('Secret must be non-empty string!');
}
this.#secretBytes = (0, utils_1.utf8ToBytes)(secret);
this.#encKey = (0, utils_1.hmacSha256)(this.#secretBytes, (0, utils_1.utf8ToBytes)('enc'));
this.#macKey = (0, utils_1.hmacSha256)(this.#secretBytes, (0, utils_1.utf8ToBytes)('mac'));
}
#genKeystream(target, iv) {
const blocks = Math.ceil(target.length / 32);
const keystreamParts = [];
for (let counter = 0; counter < blocks; counter++) {
keystreamParts.push((0, utils_1.hmacSha256)(this.#encKey, (0, utils_1.concatBytes)(iv, (0, utils_1.intTo4BytesBE)(counter))));
}
return (0, utils_1.concatBytes)(...keystreamParts).subarray(0, target.length);
}
encrypt(text) {
const plain = (0, utils_1.utf8ToBytes)(text);
const seed = (0, utils_1.utf8ToBytes)(`${Date.now()}-${Math.random()}`);
const ivFull = (0, utils_1.sha256Bytes)(seed);
const iv = ivFull.subarray(0, 16);
const keystream = this.#genKeystream(plain, iv);
const ct = plain.map((byte, i) => byte ^ keystream[i]);
const tag = (0, utils_1.hmacSha256)(this.#macKey, (0, utils_1.concatBytes)(iv, ct));
return (0, utils_1.bytesToBase64)((0, utils_1.concatBytes)(iv, ct, tag));
}
isValid(token) {
if (!(0, specials_1.isBase64)(token))
return false;
const blob = (0, utils_1.base64ToBytes)(token);
if (blob.length < 48)
return false;
const iv = blob.subarray(0, 16);
const tag = blob.subarray(blob.length - 32);
const ct = blob.subarray(16, blob.length - 32);
const expectedTag = (0, utils_1.hmacSha256)(this.#macKey, (0, utils_1.concatBytes)(iv, ct));
return (0, helpers_1._constantTimeEquals)(expectedTag, tag);
}
decrypt(token) {
if (!(0, specials_1.isBase64)(token))
throw new Error('Token must be a base64 string!');
const blob = (0, utils_1.base64ToBytes)(token);
if (blob.length < 48)
throw new Error('Malformed or tampered token!');
const iv = blob.subarray(0, 16);
const tag = blob.subarray(blob.length - 32);
const ct = blob.subarray(16, blob.length - 32);
const expectedTag = (0, utils_1.hmacSha256)(this.#macKey, (0, utils_1.concatBytes)(iv, ct));
if (!(0, helpers_1._constantTimeEquals)(expectedTag, tag)) {
throw new Error('Key in the token is tampered or invalid!');
}
const keystream = this.#genKeystream(ct, iv);
const pt = ct.map((byte, i) => byte ^ keystream[i]);
return (0, utils_1.bytesToUtf8)(pt);
}
}
exports.Cipher = Cipher;