Attic is a cozy space with lofty ambitions. attic.social
at main 59 lines 1.6 kB view raw
1import { Buffer } from "node:buffer"; 2 3const { crypto, crypto: { subtle } } = globalThis; 4 5export const sha256Hash = (value: string): Promise<ArrayBuffer> => 6 subtle.digest("SHA-256", new TextEncoder().encode(value)); 7 8export const randomIV = (length: number): Uint8Array<ArrayBuffer> => 9 crypto.getRandomValues(new Uint8Array(length)); 10 11export const importKey = async (password: string): Promise<CryptoKey> => { 12 const key = await subtle.importKey( 13 "raw", 14 await sha256Hash(password), 15 { name: "AES-GCM" }, 16 false, 17 ["encrypt", "decrypt"], 18 ); 19 return key; 20}; 21 22export const encryptText = async ( 23 value: string, 24 key: CryptoKey | string, 25): Promise<string> => { 26 const theKey = key instanceof CryptoKey ? key : await importKey(key); 27 const iv = randomIV(12); 28 const decryptedValue = new TextEncoder().encode(value); 29 const encryptedValue = await subtle.encrypt( 30 { 31 name: "AES-GCM", 32 iv, 33 }, 34 theKey, 35 decryptedValue, 36 ); 37 const ivBase64 = Buffer.from(iv).toString("base64"); 38 const encryptedBase64 = Buffer.from(encryptedValue).toString("base64"); 39 return `${ivBase64}:${encryptedBase64}`; 40}; 41 42export const decryptText = async ( 43 value: string, 44 key: CryptoKey | string, 45): Promise<string> => { 46 const base64 = value.split(":"); 47 const iv = Buffer.from(base64[0], "base64"); 48 const encryptedValue = Buffer.from(base64[1], "base64"); 49 const theKey = key instanceof CryptoKey ? key : await importKey(key); 50 const decryptedValue = await subtle.decrypt( 51 { 52 name: "AES-GCM", 53 iv, 54 }, 55 theKey, 56 encryptedValue, 57 ); 58 return new TextDecoder().decode(decryptedValue); 59};