transient.chat/static/assets/js/crypto.js

98 lines
3 KiB
JavaScript
Raw Normal View History

/**
* Transient.chat - Encryption First Engine
* Zero-Knowledge End-to-End Encryption (E2EE)
* All chat payloads (text & voice audio) are encrypted client-side using AES-256-GCM.
* Server memory stores strictly ephemeral encrypted ciphertext and IVs.
*/
(function() {
// Helper: string to ArrayBuffer (UTF-8)
function strToBuf(str) {
return new TextEncoder().encode(str);
}
// Helper: ArrayBuffer to string (UTF-8)
function bufToStr(buf) {
return new TextDecoder().decode(buf);
}
// Helper: ArrayBuffer to hex string
function bufToHex(buf) {
return Array.from(new Uint8Array(buf))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// Helper: hex string to ArrayBuffer
function hexToBuf(hex) {
if (!hex) return new Uint8Array(0).buffer;
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
}
return bytes.buffer;
}
/**
* Derives a 256-bit AES-GCM key from the room password and room name.
* Uses PBKDF2 with SHA-256 and 100,000 iterations with a room-bound salt.
*/
window.deriveRoomKey = async function(password, roomName) {
const passwordBuffer = strToBuf(password);
const saltBuffer = strToBuf("transient.chat.room." + roomName);
const baseKey = await window.crypto.subtle.importKey(
"raw",
passwordBuffer,
"PBKDF2",
false,
["deriveKey"]
);
return await window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: saltBuffer,
iterations: 100000,
hash: "SHA-256"
},
baseKey,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
};
/**
* Encrypts a message payload object (text + optional audio) using the derived room key.
* Returns hex-encoded ciphertext and 96-bit (12-byte) initialization vector (IV).
*/
window.encryptRoomPayload = async function(cryptoKey, payloadObj) {
const jsonStr = JSON.stringify(payloadObj);
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const encryptedBuffer = await window.crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv },
cryptoKey,
strToBuf(jsonStr)
);
return {
ciphertext: bufToHex(encryptedBuffer),
iv: bufToHex(iv)
};
};
/**
* Decrypts ciphertext and IV hex strings back to the original payload object.
*/
window.decryptRoomPayload = async function(cryptoKey, ciphertextHex, ivHex) {
const decryptedBuffer = await window.crypto.subtle.decrypt(
{ name: "AES-GCM", iv: hexToBuf(ivHex) },
cryptoKey,
hexToBuf(ciphertextHex)
);
return JSON.parse(bufToStr(decryptedBuffer));
};
})();