north-star / mobile / files / src / core / crypto.ts
Branch mobile · default branch · all branches
crypto.ts
36d64b37 2026-03-15 Pavel Safronov | 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 | ||
b4e7f682 2026-03-16 Pavel Safronov | 7 | export const exportRoomKey = async (key: CryptoKey) => { |
36d64b37 2026-03-15 Pavel Safronov | 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 | ||
b4e7f682 2026-03-16 Pavel Safronov | 13 | export const importRoomKey = async (base64url: string) => { |
36d64b37 2026-03-15 Pavel Safronov | 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 | ||
b4e7f682 2026-03-16 Pavel Safronov | 20 | export const encryptJson = async (key: CryptoKey, value: unknown) => { |
36d64b37 2026-03-15 Pavel Safronov | 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 | ||
b4e7f682 2026-03-16 Pavel Safronov | 29 | export const decryptJson = async (key: CryptoKey, encryptedValue: string) => { |
36d64b37 2026-03-15 Pavel Safronov | 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 | }; |