-
Notifications
You must be signed in to change notification settings - Fork 0
/
lib.ts
97 lines (89 loc) · 2.12 KB
/
lib.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
const arrayBufferToBase64 = (bytes: Uint8Array) => {
return btoa(String.fromCharCode.apply(null, bytes as unknown as number[]));
};
const base64ToArrayBuffer = (base64: string): Uint8Array => {
const binaryString = atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
};
export interface EncryptedData {
iv: string;
salt: string;
cipherText: string;
}
export interface EncryptionKey {
key: CryptoKey;
salt: Uint8Array;
}
export const generateKey = async (
secret: string,
opts?: { saltAsBase64?: string }
): Promise<EncryptionKey> => {
const material = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'PBKDF2' },
false,
['deriveBits', 'deriveKey']
);
const salt = opts?.saltAsBase64
? base64ToArrayBuffer(opts.saltAsBase64)
: crypto.getRandomValues(new Uint8Array(64 / 2));
return {
salt,
key: await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: 100_000,
hash: 'SHA-256',
},
material,
{
name: 'AES-GCM',
length: 256,
},
false,
['encrypt', 'decrypt']
),
};
};
export const encrypt = async (
data: string,
// could accept union with secret
opts: { key: CryptoKey; salt: Uint8Array }
): Promise<EncryptedData> => {
const iv = crypto.getRandomValues(new Uint8Array(32 / 2));
const cipherText = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv,
},
opts.key,
new TextEncoder().encode(data)
);
return {
iv: arrayBufferToBase64(iv),
salt: arrayBufferToBase64(opts.salt),
cipherText: arrayBufferToBase64(new Uint8Array(cipherText)),
};
};
export const decrypt = async (
data: EncryptedData,
opts: {
key: CryptoKey;
}
): Promise<string> => {
const decrypted = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: base64ToArrayBuffer(data.iv),
},
opts.key,
base64ToArrayBuffer(data.cipherText)
);
return new TextDecoder().decode(decrypted);
};