117 lines
2.6 KiB
JavaScript
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
|
|
};
|
|
}
|