-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmod.ts
193 lines (174 loc) · 5.5 KB
/
mod.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import { crypto } from "https://deno.land/[email protected]/crypto/crypto.ts";
import { encodeHex } from "https://deno.land/[email protected]/encoding/hex.ts";
import {
assertEquals,
assertNotEquals,
} from "https://deno.land/[email protected]/assert/mod.ts";
import {
bytesToHex,
bytesToString,
hexToBytes,
stringToBytes,
} from "https://deno.land/x/[email protected]/mod.ts";
/**
* produce the hex-encoded string of the sha256 hash of string s
* @param {string} s - the string to hash
* @returns {Promise<string>} the hex-encoded sha256 of s
*/
const sha256Hex = async (s: string): Promise<string> =>
encodeHex(await crypto.subtle.digest("SHA-256", stringToBytes(s)));
/**
* test a string to see if it is a uuid (as generated by randomUUID)
* @param {string} s - the string to test
* @returns {boolean} if s is a uuid
*/
const isUUID = (s: string): boolean => {
const re = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
return re.test(s);
};
/**
* re-export crypto.randomUUID in this namespace
* @returns {string} a random v4 uuid
*/
const randomUUID = (): string => crypto.randomUUID();
const AES_GCM = "AES-GCM";
/**
* @class Key provides a simple wrapper for AES CryptoKeys allowing for serialization to hex.
*/
class Key {
static readonly Params: AesKeyGenParams = { name: AES_GCM, length: 256 };
static readonly Extractable = true;
static readonly Usages: KeyUsage[] = ["encrypt", "decrypt"];
readonly cryptoKey: CryptoKey; // internal representation
/**
* @constructor
* @param {CryptoKey} cryptoKey - caller provided key
*/
constructor(cryptoKey: CryptoKey) {
assertEquals(cryptoKey.algorithm, Key.Params, "key algorithm");
assertEquals(cryptoKey.extractable, Key.Extractable, "key extractable");
assertEquals(cryptoKey.usages, Key.Usages, "key usages");
this.cryptoKey = cryptoKey;
}
/**
* construct a new Key based on the exported Key hex representation
* @param {string} hexKey - previously exported Key hex (from toHex())
* @returns {Promise<Key>} - a new Key constructed from the hexKey
*/
static async fromHex(hexKey: string): Promise<Key> {
return new Key(
await crypto.subtle.importKey(
"raw",
hexToBytes(hexKey),
Key.Params,
Key.Extractable,
Key.Usages,
),
);
}
/**
* construct a new Key based on a new random seed
* @returns {Promise<Key>} - a new Key constructed from the random seed
*/
static async generate(): Promise<Key> {
return new Key(
await crypto.subtle.generateKey(
Key.Params,
Key.Extractable,
Key.Usages,
),
);
}
/**
* export the hex serialization of the Key
* @returns {Promise<string>} - hex serialization of the Key
*/
async toHex(): Promise<string> {
const keyBytes = new Uint8Array(
await crypto.subtle.exportKey("raw", this.cryptoKey),
);
return bytesToHex(keyBytes);
}
}
/**
* @class IV provides a simple wrapper for AES IVs allowing for serialization to hex.
*/
class IV {
static readonly Length = 16;
readonly bytes: Uint8Array; // internal representation
/**
* @constructor
* @param {Uint8Array} bytes - caller provided iv (len 16 Uint8Array)
*/
constructor(bytes: Uint8Array) {
assertEquals(bytes.length, IV.Length, "iv length");
this.bytes = bytes;
}
/**
* construct a new Key based on the exported IV hex representation
* @param {string} hexIV - previously exported IV hex (from toHex())
* @returns {Promise<IV>} - a new IV constructed from the hexIV
*/
static fromHex(hexIV: string): IV {
return new IV(hexToBytes(hexIV));
}
/**
* construct a new IV using a provided string as the seed
* @param {string} s - the seed for the IV
* @returns {Promise<IV>} a 16-byte long Uint8Array suitable as an AEC-CBC IV
*/
static async fromString(s: string): Promise<IV> {
assertNotEquals(s.length, 0, "empty iv input");
return new IV(stringToBytes(await sha256Hex(s)).slice(0, IV.Length));
}
/**
* construct a new IV based on a new random seed
* @returns {Promise<IV>} - a new IV constructed from the random seed
*/
static generate(): IV {
return new IV(crypto.getRandomValues(new Uint8Array(IV.Length)));
}
/**
* export the hex serialization of the IV
* @returns {Promise<string>} - hex serialization of the IV
*/
toHex(): string {
return bytesToHex(this.bytes);
}
}
/**
* encrypt a clear text and hex-encode the resulting encrypted value
* @param {string} clearText - the string to encrypt
* @param {Key} key - Key instance
* @returns {Promise<string>} - the hex-encoding of the encrypted iv + clearText
*/
async function encryptToHex(clearText: string, key: Key): Promise<string> {
const iv = IV.generate();
return iv.toHex() + bytesToHex(
new Uint8Array(
await crypto.subtle.encrypt(
{ name: AES_GCM, iv: iv.bytes },
key.cryptoKey,
stringToBytes(clearText),
),
),
);
}
/**
* @param {string} encrypted - the output of encryptToHex
* @param {Key} key - Key instance
* @returns {Promise<string>} - the decrypted clear text
*/
async function decryptFromHex(encrypted: string, key: Key): Promise<string> {
const iv = IV.fromHex(encrypted.substring(0, 2 * IV.Length));
return bytesToString(
new Uint8Array(
await crypto.subtle.decrypt(
{ name: AES_GCM, iv: iv.bytes },
key.cryptoKey,
hexToBytes(encrypted.substring(2 * IV.Length)),
),
),
);
}
export { decryptFromHex, encryptToHex, isUUID, IV, Key, randomUUID, sha256Hex };