112 lines
2.4 KiB
TypeScript
112 lines
2.4 KiB
TypeScript
/**
|
|
* Zero-Knowledge Cryptography Utility
|
|
* Uses the Web Crypto API for hardware-accelerated AES-GCM.
|
|
*/
|
|
|
|
export async function importKey(keyData: Uint8Array): Promise<CryptoKey> {
|
|
return await self.crypto.subtle.importKey(
|
|
"raw",
|
|
keyData.buffer as ArrayBuffer,
|
|
{ name: "AES-GCM" },
|
|
false,
|
|
["decrypt", "encrypt"]
|
|
);
|
|
}
|
|
|
|
export async function decryptLog(
|
|
encryptedData: Uint8Array,
|
|
key: CryptoKey
|
|
): Promise<string> {
|
|
// Our Go implementation sends [Nonce (12 bytes)][Ciphertext]
|
|
const nonce = encryptedData.slice(0, 12);
|
|
const ciphertext = encryptedData.slice(12);
|
|
|
|
const decrypted = await self.crypto.subtle.decrypt(
|
|
{
|
|
name: "AES-GCM",
|
|
iv: nonce,
|
|
},
|
|
key,
|
|
ciphertext
|
|
);
|
|
|
|
return new TextDecoder().decode(decrypted);
|
|
}
|
|
|
|
export async function encryptData(
|
|
plaintext: string,
|
|
key: CryptoKey
|
|
): Promise<Uint8Array> {
|
|
const enc = new TextEncoder();
|
|
const data = enc.encode(plaintext);
|
|
const nonce = self.crypto.getRandomValues(new Uint8Array(12));
|
|
|
|
const encrypted = await self.crypto.subtle.encrypt(
|
|
{
|
|
name: "AES-GCM",
|
|
iv: nonce,
|
|
},
|
|
key,
|
|
data
|
|
);
|
|
|
|
const result = new Uint8Array(nonce.length + encrypted.byteLength);
|
|
result.set(nonce);
|
|
result.set(new Uint8Array(encrypted), nonce.length);
|
|
return result;
|
|
}
|
|
|
|
export async function generateBlindIndex(
|
|
value: string,
|
|
key: Uint8Array
|
|
): Promise<string> {
|
|
const enc = new TextEncoder();
|
|
const data = enc.encode(value);
|
|
|
|
const hmacKey = await self.crypto.subtle.importKey(
|
|
"raw",
|
|
key,
|
|
{ name: "HMAC", hash: "SHA-256" },
|
|
false,
|
|
["sign"]
|
|
);
|
|
|
|
const signature = await self.crypto.subtle.sign(
|
|
"HMAC",
|
|
hmacKey,
|
|
data
|
|
);
|
|
|
|
return Array.from(new Uint8Array(signature))
|
|
.map(b => b.toString(16).padStart(2, '0'))
|
|
.join('');
|
|
}
|
|
|
|
/**
|
|
* Derives a 256-bit key from a password and salt using PBKDF2.
|
|
* (Used if WebAuthn is not providing a raw key directly)
|
|
*/
|
|
export async function deriveKey(password: string, salt: string): Promise<Uint8Array> {
|
|
const enc = new TextEncoder();
|
|
const keyMaterial = await self.crypto.subtle.importKey(
|
|
"raw",
|
|
enc.encode(password),
|
|
"PBKDF2",
|
|
false,
|
|
["deriveBits", "deriveKey"]
|
|
);
|
|
|
|
const keyBits = await self.crypto.subtle.deriveBits(
|
|
{
|
|
name: "PBKDF2",
|
|
salt: enc.encode(salt),
|
|
iterations: 100000,
|
|
hash: "SHA-256",
|
|
},
|
|
keyMaterial,
|
|
256
|
|
);
|
|
|
|
return new Uint8Array(keyBits);
|
|
}
|