north-star / mobile / files / src / core / crypto.ts
Branch mobile · default branch · all branches
crypto.ts
| 1 | const AES_PARAMS = { name: 'AES-GCM', length: 256 }; |
| 2 | const IV_BYTES = 12; |
| 3 | |
| 4 | export const generateRoomKey = () => |
| 5 | crypto.subtle.generateKey(AES_PARAMS, true, ['encrypt', 'decrypt']); |
| 6 | |
| 7 | export const exportRoomKey = async (key: CryptoKey) => { |
| 8 | const raw = await crypto.subtle.exportKey('raw', key); |
| 9 | return btoa(String.fromCharCode(...new Uint8Array(raw))) |
| 10 | .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); |
| 11 | }; |
| 12 | |
| 13 | export const importRoomKey = async (base64url: string) => { |
| 14 | const b64 = base64url.replace(/-/g, '+').replace(/_/g, '/'); |
| 15 | const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4); |
| 16 | const raw = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)); |
| 17 | return crypto.subtle.importKey('raw', raw, AES_PARAMS, false, ['encrypt', 'decrypt']); |
| 18 | }; |
| 19 | |
| 20 | export const encryptJson = async (key: CryptoKey, value: unknown) => { |
| 21 | const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES)); |
| 22 | const plaintext = new TextEncoder().encode(JSON.stringify(value)); |
| 23 | const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plaintext); |
| 24 | const ivB64 = btoa(String.fromCharCode(...iv)); |
| 25 | const ctB64 = btoa(String.fromCharCode(...new Uint8Array(ciphertext))); |
| 26 | return `${ivB64}:${ctB64}`; |
| 27 | }; |
| 28 | |
| 29 | export const decryptJson = async (key: CryptoKey, encryptedValue: string) => { |
| 30 | const [ivB64, ctB64] = encryptedValue.split(':'); |
| 31 | const iv = Uint8Array.from(atob(ivB64), (c) => c.charCodeAt(0)); |
| 32 | const ciphertext = Uint8Array.from(atob(ctB64), (c) => c.charCodeAt(0)); |
| 33 | const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext); |
| 34 | return JSON.parse(new TextDecoder().decode(plaintext)); |
| 35 | }; |