62 lines
2 KiB
JavaScript
62 lines
2 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// E2E Encryption utilities (Web Crypto API)
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
let _encKey = null;
|
||
|
|
|
||
|
|
function bytesToBase64(buf) {
|
||
|
|
const bytes = new Uint8Array(buf);
|
||
|
|
let str = '';
|
||
|
|
for (const b of bytes) str += String.fromCharCode(b);
|
||
|
|
return btoa(str);
|
||
|
|
}
|
||
|
|
|
||
|
|
function base64ToBytes(b64) {
|
||
|
|
const str = atob(b64);
|
||
|
|
const buf = new Uint8Array(str.length);
|
||
|
|
for (let i = 0; i < str.length; i++) buf[i] = str.charCodeAt(i);
|
||
|
|
return buf;
|
||
|
|
}
|
||
|
|
|
||
|
|
function bytesToHex(buf) {
|
||
|
|
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
function hexToBytes(hex) {
|
||
|
|
const arr = new Uint8Array(hex.length / 2);
|
||
|
|
for (let i = 0; i < arr.length; i++) arr[i] = parseInt(hex.slice(i*2, i*2+2), 16);
|
||
|
|
return arr;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function getOrCreateEncKey() {
|
||
|
|
if (_encKey) return _encKey;
|
||
|
|
const storageKey = `diora_enc_key_${window.USER_ID || 0}`;
|
||
|
|
const stored = localStorage.getItem(storageKey);
|
||
|
|
if (stored) {
|
||
|
|
try {
|
||
|
|
const raw = base64ToBytes(stored);
|
||
|
|
_encKey = await crypto.subtle.importKey('raw', raw, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt']);
|
||
|
|
return _encKey;
|
||
|
|
} catch (e) { /* fall through, generate new */ }
|
||
|
|
}
|
||
|
|
// No key found — generate one and store it
|
||
|
|
_encKey = await crypto.subtle.generateKey({name: 'AES-GCM', length: 256}, true, ['encrypt', 'decrypt']);
|
||
|
|
const raw = await crypto.subtle.exportKey('raw', _encKey);
|
||
|
|
localStorage.setItem(storageKey, bytesToBase64(new Uint8Array(raw)));
|
||
|
|
return _encKey;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function encryptBytes(key, plainBytes) {
|
||
|
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
||
|
|
const ct = await crypto.subtle.encrypt({name: 'AES-GCM', iv}, key, plainBytes);
|
||
|
|
return {iv: bytesToHex(iv), ciphertext: bytesToBase64(ct)};
|
||
|
|
}
|
||
|
|
|
||
|
|
async function decryptBytes(key, ivHex, ctB64) {
|
||
|
|
const iv = hexToBytes(ivHex);
|
||
|
|
const ct = base64ToBytes(ctB64);
|
||
|
|
return crypto.subtle.decrypt({name: 'AES-GCM', iv}, key, ct);
|
||
|
|
}
|