better-cipher
Version:
A secure encryption library with browser and Node.js support using AES-GCM
97 lines (79 loc) • 2.5 kB
text/typescript
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);
}
}