forked from MarcinHoppe/can-you-keep-a-secret-in-js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex_webcrypto.js
107 lines (91 loc) · 2.86 KB
/
index_webcrypto.js
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
const crypto = require('crypto').webcrypto;
(async () => {
// ----------------- HIGH LEVEL FLOW -----------------
async function seal({ encryptionKey, signingKey }, userData) {
const iv = generateIv();
const ciphertext = await encrypt(encryptionKey, iv, userData);
const signature = await signGenerate(signingKey, iv, ciphertext);
return serialize(iv, ciphertext, signature);
}
async function unseal({ encryptionKey, signingKey }, secretToken) {
const [iv, ciphertext, signature] = deserialize(secretToken);
if (!await signVerify(signingKey, iv, ciphertext, signature)) {
throw new Error('Invalid signature');
}
return await decrypt(encryptionKey, iv, ciphertext);
}
// ----------------- PRIMITIVES -----------------
async function generateKeys() {
return {
encryptionKey: await crypto.subtle.generateKey(
{ name: 'AES-CBC', length: 128 },
true,
['encrypt', 'decrypt']
),
signingKey: await crypto.subtle.generateKey(
{ name: 'HMAC', hash: { name: 'SHA-256' } },
true,
['sign', 'verify']
)
};
}
function generateIv() {
const iv = new Uint8Array(16);
return crypto.getRandomValues(iv);
}
async function encrypt(key, iv, userData) {
const userDataBytes = new TextEncoder().encode(userData);
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-CBC', iv },
key,
userDataBytes
);
return new Uint8Array(ciphertext);
}
async function decrypt(key, iv, ciphertext) {
const plaintext = await crypto.subtle.decrypt(
{ name: 'AES-CBC', iv },
key,
ciphertext
);
return new TextDecoder().decode(plaintext);
}
async function signGenerate(key, iv, ciphertext) {
const payload = new Uint8Array([...iv, ...ciphertext]);
const signature = await crypto.subtle.sign(
'HMAC',
key,
payload
);
return new Uint8Array(signature);
}
async function signVerify(key, iv, ciphertext, signature) {
const payload = new Uint8Array([...iv, ...ciphertext]);
return crypto.subtle.verify(
'HMAC',
key,
signature,
payload
);
}
function serialize(iv, ciphertext, signature) {
return new Uint8Array([...iv, ...ciphertext, ...signature]);
}
function deserialize(secretToken) {
const ciphertextLen = secretToken.length - 48;
return [
secretToken.subarray(0,16),
secretToken.subarray(16, 16 + ciphertextLen),
secretToken.subarray(16 + ciphertextLen)
];
}
// ----------------- USAGE -----------------
const keys = await generateKeys();
const secret = 'Hello from JS Poland!';
const secretToken = await seal(keys, secret);
const recoveredSecret = await unseal(keys, secretToken);
console.assert(
secret === recoveredSecret,
'Original secret and recovered secret must be the same.'
);
})();