aes.ts

2.2 KB · raw

1const VERSION_BYTE = new Uint8Array([1]);23function hex(buf: ArrayBuffer | Uint8Array): string {4  const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);5  return Array.from(bytes)6    .map((byte) => byte.toString(16).padStart(2, "0"))7    .join("");8}910function unhex(value: string): Uint8Array {11  const out = new Uint8Array(value.length / 2);12  for (let i = 0; i < value.length; i += 2) {13    out[i / 2] = parseInt(value.slice(i, i + 2), 16);14  }15  return out;16}1718export async function encrypt(19  keyBytes: Uint8Array,20  plaintext: string21): Promise<{ nonce: string; ciphertext: string; tag: string }> {22  const key = await crypto.subtle.importKey(23    "raw",24    keyBytes.buffer as ArrayBuffer,25    { name: "AES-GCM" },26    false,27    ["encrypt"]28  );2930  const nonce = crypto.getRandomValues(new Uint8Array(12));31  const encoded = new TextEncoder().encode(plaintext);32  const raw = await crypto.subtle.encrypt(33    {34      name: "AES-GCM",35      iv: nonce,36      additionalData: VERSION_BYTE.buffer as ArrayBuffer,37      tagLength: 128,38    },39    key,40    encoded41  );4243  const full = new Uint8Array(raw);44  const ciphertext = full.slice(0, full.length - 16);45  const tag = full.slice(full.length - 16);4647  return {48    nonce: hex(nonce.buffer),49    ciphertext: hex(ciphertext.buffer),50    tag: hex(tag.buffer),51  };52}5354export async function decrypt(55  keyBytes: Uint8Array,56  nonce: string,57  ciphertext: string,58  tag: string59): Promise<string> {60  const key = await crypto.subtle.importKey(61    "raw",62    keyBytes.buffer as ArrayBuffer,63    { name: "AES-GCM" },64    false,65    ["decrypt"]66  );6768  const cipherBytes = unhex(ciphertext);69  const tagBytes = unhex(tag);70  const combined = new Uint8Array(cipherBytes.length + tagBytes.length);71  combined.set(cipherBytes);72  combined.set(tagBytes, cipherBytes.length);7374  const raw = await crypto.subtle.decrypt(75    {76      name: "AES-GCM",77      iv: unhex(nonce).buffer as ArrayBuffer,78      additionalData: VERSION_BYTE.buffer as ArrayBuffer,79      tagLength: 128,80    },81    key,82    combined.buffer as ArrayBuffer83  );8485  return new TextDecoder().decode(raw);86}8788export function zeroKey(keyBytes: Uint8Array): void {89  keyBytes.fill(0);90}