UNPKG

better-cipher

Version:

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

97 lines (79 loc) 2.5 kB
import type { Encrypted, IEncryption, CreateRotator } from "./types"; import { isValidHexKey } from "./util"; const ALGORITHM = "AES-GCM"; const IV_LENGTH = 12; function chunks(str: string, size: number) { const chunks: string[] = []; for (let i = 0; i < str.length; i += size) { chunks.push(str.slice(i, i + size)); } return chunks; } function hexToBytes(hex: string) { return chunks(hex, 2).map((byte) => parseInt(byte, 16)); } function bytesToHex(bytes: number[]) { return bytes.map((byte) => byte.toString(16).padStart(2, "0")).join(""); } export class Cipher implements IEncryption { private secretKeyHex: string; private secretKey: CryptoKey | null; 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.secretKeyHex = secretKeyHex; this.secretKey = null; } private async getSecretKey(): Promise<CryptoKey> { if (!this.secretKey) { this.secretKey = await crypto.subtle.importKey( "raw", new TextEncoder().encode(this.secretKeyHex), { name: ALGORITHM }, false, ["encrypt", "decrypt"] ); } return this.secretKey; } async encrypt(text: string): Promise<Encrypted> { const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH)); const encoded = new TextEncoder().encode(text); const encrypted = await crypto.subtle.encrypt( { name: ALGORITHM, iv, }, await this.getSecretKey(), encoded ); return { iv: bytesToHex(Array.from(iv)), content: bytesToHex(Array.from(new Uint8Array(encrypted))), authTag: "", }; } async decrypt(encrypted: Encrypted): Promise<string> { const iv = new Uint8Array(hexToBytes(encrypted.iv)); const content = new Uint8Array(hexToBytes(encrypted.content)); const decrypted = await crypto.subtle.decrypt( { name: ALGORITHM, iv, }, await this.getSecretKey(), content ); return new TextDecoder().decode(decrypted); } }