SecureChat / src /static /crypto.js
ausername-12345
reuse register endpoint for google setup
73d7d26
// SecureChat E2EE Crypto Layer
// Uses Web Crypto API: RSA-OAEP for key exchange, AES-GCM for messages
const Crypto = {
// Generate RSA-OAEP key pair for E2EE
async generateKeyPair() {
const keyPair = await window.crypto.subtle.generateKey(
{ name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
true,
["encrypt", "decrypt"]
);
const publicKeyJwk = await window.crypto.subtle.exportKey("jwk", keyPair.publicKey);
const privateKeyJwk = await window.crypto.subtle.exportKey("jwk", keyPair.privateKey);
return { publicKeyJwk, privateKeyJwk, keyPair };
},
// Export public key as JSON string (stored on server)
async exportPublicKey(keyPair) {
return JSON.stringify(await window.crypto.subtle.exportKey("jwk", keyPair.publicKey));
},
// Import a public key from JWK JSON string
async importPublicKey(jwkString) {
const jwk = typeof jwkString === "string" ? JSON.parse(jwkString) : jwkString;
return window.crypto.subtle.importKey("jwk", jwk, { name: "RSA-OAEP", hash: "SHA-256" }, false, ["encrypt"]);
},
// Import a private key from JWK JSON string
async importPrivateKey(jwkString) {
const jwk = typeof jwkString === "string" ? JSON.parse(jwkString) : jwkString;
return window.crypto.subtle.importKey("jwk", jwk, { name: "RSA-OAEP", hash: "SHA-256" }, false, ["decrypt"]);
},
// Encrypt a message with a recipient's RSA public key (returns base64)
async encryptForRecipient(message, publicKey) {
const encoded = new TextEncoder().encode(message);
const encrypted = await window.crypto.subtle.encrypt({ name: "RSA-OAEP" }, publicKey, encoded);
return btoa(String.fromCharCode(...new Uint8Array(encrypted)));
},
// Decrypt a message with own RSA private key
async decryptWithPrivateKey(ciphertextB64, privateKey) {
const ciphertext = Uint8Array.from(atob(ciphertextB64), c => c.charCodeAt(0));
const decrypted = await window.crypto.subtle.decrypt({ name: "RSA-OAEP" }, privateKey, ciphertext);
return new TextDecoder().decode(decrypted);
},
// === Group E2EE: AES-GCM + per-member RSA key wrapping ===
// Generate a random AES-GCM key for a group message
async generateAesKey() {
return window.crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]);
},
// Encrypt message with AES-GCM key, returns {ciphertext: base64, iv: base64}
async encryptWithAes(message, aesKey) {
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const encoded = new TextEncoder().encode(message);
const encrypted = await window.crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, encoded);
return {
ciphertext: btoa(String.fromCharCode(...new Uint8Array(encrypted))),
iv: btoa(String.fromCharCode(...iv))
};
},
// Decrypt message with AES-GCM key
async decryptWithAes(ciphertextB64, ivB64, aesKey) {
const ciphertext = Uint8Array.from(atob(ciphertextB64), c => c.charCodeAt(0));
const iv = Uint8Array.from(atob(ivB64), c => c.charCodeAt(0));
const decrypted = await window.crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, ciphertext);
return new TextDecoder().decode(decrypted);
},
// Wrap (encrypt) AES key with RSA public key for a member
async wrapAesKey(aesKey, recipientPublicKey) {
const exportedAes = await window.crypto.subtle.exportKey("raw", aesKey);
const wrapped = await window.crypto.subtle.encrypt({ name: "RSA-OAEP" }, recipientPublicKey, exportedAes);
return btoa(String.fromCharCode(...new Uint8Array(wrapped)));
},
// Unwrap (decrypt) AES key with own RSA private key
async unwrapAesKey(wrappedB64, privateKey) {
const wrapped = Uint8Array.from(atob(wrappedB64), c => c.charCodeAt(0));
const rawAes = await window.crypto.subtle.decrypt({ name: "RSA-OAEP" }, privateKey, wrapped);
return window.crypto.subtle.importKey("raw", rawAes, { name: "AES-GCM", length: 256 }, false, ["decrypt"]);
},
// Store private key in localStorage (encrypted with a password-derived key would be ideal; for now, store JWK)
storePrivateKey(privateKeyJwk) {
localStorage.setItem("sc_private_key", JSON.stringify(privateKeyJwk));
},
loadPrivateKey() {
const stored = localStorage.getItem("sc_private_key");
return stored ? JSON.parse(stored) : null;
},
clearPrivateKey() {
localStorage.removeItem("sc_private_key");
}
};