actual/packages/loot-core/src/server/encryption-internals.web.js
2022-04-28 22:44:38 -04:00

117 lines
2.6 KiB
JavaScript

let ENCRYPTION_ALGORITHM = 'aes-256-gcm';
function browserAlgorithmName(name) {
switch (name) {
case 'aes-256-gcm':
return 'AES-GCM';
default:
throw new Error('unsupported crypto algorithm: ' + name);
}
}
export async function sha256String(str) {
let inputBuffer = new TextEncoder('utf-8').encode(str).buffer;
let buffer = await crypto.subtle.digest('sha-256', inputBuffer);
let outputStr = Array.from(new Uint8Array(buffer))
.map(n => String.fromCharCode(n))
.join('');
return btoa(outputStr);
}
export function randomBytes(n) {
return Buffer.from(crypto.getRandomValues(new Uint8Array(n)));
}
export async function encrypt(masterKey, value) {
let iv = crypto.getRandomValues(new Uint8Array(12));
let encrypted = await crypto.subtle.encrypt(
{
name: browserAlgorithmName(ENCRYPTION_ALGORITHM),
iv,
tagLength: 128
},
masterKey.getValue().raw,
value
);
encrypted = Buffer.from(encrypted);
// Strip the auth tag off the end
let authTag = encrypted.slice(-16);
encrypted = encrypted.slice(0, -16);
return {
value: encrypted,
meta: {
keyId: masterKey.getId(),
algorithm: ENCRYPTION_ALGORITHM,
iv: Buffer.from(iv).toString('base64'),
authTag: authTag.toString('base64')
}
};
}
export async function decrypt(masterKey, encrypted, meta) {
let { algorithm, iv, authTag } = meta;
let decrypted = await crypto.subtle.decrypt(
{
name: browserAlgorithmName(algorithm),
iv: Buffer.from(iv, 'base64'),
tagLength: 128
},
masterKey.getValue().raw,
Buffer.concat([encrypted, Buffer.from(authTag, 'base64')])
);
return Buffer.from(decrypted);
}
export async function createKey({ secret, salt }) {
let passwordBuffer = Buffer.from(secret);
let saltBuffer = Buffer.from(salt);
let passwordKey = await crypto.subtle.importKey(
'raw',
passwordBuffer,
{ name: 'PBKDF2' },
false,
['deriveBits', 'deriveKey']
);
let derivedKey = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
hash: 'SHA-512',
salt: saltBuffer,
iterations: 10000
},
passwordKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
let exported = await crypto.subtle.exportKey('raw', derivedKey);
return {
raw: derivedKey,
base64: Buffer.from(exported).toString('base64')
};
}
export async function importKey(str) {
let key = await crypto.subtle.importKey(
'raw',
Buffer.from(str, 'base64'),
{ name: 'AES-GCM' },
false,
['encrypt', 'decrypt']
);
return {
raw: key,
base64: str
};
}