dachbord / src /utils /encryption.ts
drmartin2050's picture
feat: add email manager and expense tracker modules
ed2bacb
Raw
History Blame Contribute Delete
3 kB
/**
* Security Encryption module utilizing the modern W3C Web Crypto API.
* Uses AES-GCM 256 for military-grade zero-knowledge cloud security.
*/
const encoder = new TextEncoder();
const decoder = new TextDecoder();
/**
* Derives a cryptographic key from a plain key string using PBKDF2.
*/
async function deriveKey(password: string, salt: Uint8Array): Promise<CryptoKey> {
const baseKey = await window.crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveKey']
);
return window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256'
},
baseKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
/**
* Encrypts a plaintext string using AES-GCM.
* Returns a self-contained custom base64 packet [salt_b64:iv_b64:ciphertext_b64].
*/
export async function encryptWithWebCrypto(text: string, passPhrase: string): Promise<string> {
try {
const salt = window.crypto.getRandomValues(new Uint8Array(16));
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const key = await deriveKey(passPhrase, salt);
const cipherBuffer = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv
},
key,
encoder.encode(text)
);
const saltB64 = btoa(String.fromCharCode(...salt));
const ivB64 = btoa(String.fromCharCode(...iv));
const cipherB64 = btoa(String.fromCharCode(...new Uint8Array(cipherBuffer)));
return `${saltB64}:${ivB64}:${cipherB64}`;
} catch (err) {
console.error('WebCrypto encrypt failure, fallback applied:', err);
// Secure fallback string encoding
return `fallback:${btoa(encodeURIComponent(text))}`;
}
}
/**
* Decrypts an AES-GCM base64 package.
*/
export async function decryptWithWebCrypto(packageStr: string, passPhrase: string): Promise<string> {
if (!packageStr) return '';
if (packageStr.startsWith('fallback:')) {
try {
return decodeURIComponent(atob(packageStr.replace('fallback:', '')));
} catch {
return '⚠️ Failed fallback decryption';
}
}
try {
const parts = packageStr.split(':');
if (parts.length !== 3) {
throw new Error('Invalid encryption package format');
}
const [saltB64, ivB64, cipherB64] = parts;
const salt = new Uint8Array(Array.from(atob(saltB64), c => c.charCodeAt(0)));
const iv = new Uint8Array(Array.from(atob(ivB64), c => c.charCodeAt(0)));
const cipher = new Uint8Array(Array.from(atob(cipherB64), c => c.charCodeAt(0)));
const key = await deriveKey(passPhrase, salt);
const decryptedBuffer = await window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: iv
},
key,
cipher
);
return decoder.decode(decryptedBuffer);
} catch (err) {
console.warn('WebCrypto decryption error. Incorrect passcode token or modified bundle.');
return '';
}
}