UNPKG

better-cipher

Version:

A secure encryption library with browser and Node.js support using AES-GCM

59 lines (49 loc) 1.94 kB
import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto"; import type { Encrypted, IEncryption, CreateRotator } from "./types"; import { isValidHexKey } from "./util"; const ALGORITHM = "aes-256-gcm"; const IV_LENGTH = 12; export class Cipher implements IEncryption { private secretKey: Buffer; static createRotator: CreateRotator = (oldSecretKey, newSecretKey) => { const oldCipher = new Cipher(oldSecretKey); const newCipher = new Cipher(newSecretKey); const rotator = async (prevEncrypted: Encrypted): Promise<Encrypted> => { const deciphered = await oldCipher.decrypt(prevEncrypted); return await newCipher.encrypt(deciphered); }; return rotator; }; constructor(secretKeyHex: string) { if (!isValidHexKey(secretKeyHex)) { throw new Error("Secret key must be a valid hex string"); } this.secretKey = Buffer.from(secretKeyHex, "hex"); } async encrypt(text: string): Promise<Encrypted> { // For empty string, we still need to encrypt something const dataToEncrypt = text || "\0"; const iv = randomBytes(IV_LENGTH); const cipher = createCipheriv(ALGORITHM, this.secretKey, iv); let encrypted = cipher.update(dataToEncrypt, "utf8", "hex"); encrypted += cipher.final("hex"); const authTag = cipher.getAuthTag().toString("hex"); return Promise.resolve({ iv: iv.toString("hex"), content: encrypted, authTag: authTag, }); } async decrypt(encrypted: Encrypted): Promise<string> { const decipher = createDecipheriv( ALGORITHM, this.secretKey, Buffer.from(encrypted.iv, "hex") ); decipher.setAuthTag(Buffer.from(encrypted.authTag, "hex")); let decrypted = decipher.update(encrypted.content, "hex", "utf8"); decrypted += decipher.final("utf8"); // Convert null byte back to empty string return Promise.resolve(decrypted === "\0" ? "" : decrypted); } }