UNPKG

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
"use strict"; 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;