-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathencryption-internals.web.ts
120 lines (102 loc) · 2.78 KB
/
encryption-internals.web.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// @ts-strict-ignore
const 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) {
// @ts-expect-error TextEncoder might not accept an argument
const inputBuffer = new TextEncoder('utf-8').encode(str).buffer;
const buffer = await crypto.subtle.digest('sha-256', inputBuffer);
const 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) {
const 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
const 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'),
// @ts-expect-error base64 argument is valid only on NodeJS
authTag: authTag.toString('base64'),
},
};
}
export async function decrypt(masterKey, encrypted, meta) {
const { algorithm, iv, authTag } = meta;
const 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 }) {
const passwordBuffer = Buffer.from(secret);
const saltBuffer = Buffer.from(salt);
const passwordKey = await crypto.subtle.importKey(
'raw',
passwordBuffer,
{ name: 'PBKDF2' },
false,
['deriveBits', 'deriveKey'],
);
const derivedKey = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
hash: 'SHA-512',
salt: saltBuffer,
iterations: 10000,
},
passwordKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt'],
);
const exported = await crypto.subtle.exportKey('raw', derivedKey);
return {
raw: derivedKey,
base64: Buffer.from(exported).toString('base64'),
};
}
export async function importKey(str) {
const key = await crypto.subtle.importKey(
'raw',
Buffer.from(str, 'base64'),
{ name: 'AES-GCM' },
false,
['encrypt', 'decrypt'],
);
return {
raw: key,
base64: str,
};
}