crypto-helper.ts raw
1 import { Buffer } from 'buffer';
2
3 export class CryptoHelper {
4 /**
5 * Generate a base64 encoded IV.
6 */
7 static generateIV(): string {
8 const iv = crypto.getRandomValues(new Uint8Array(12));
9 return Buffer.from(iv).toString('base64');
10 }
11
12 /**
13 * Hash (SHA-256) a text string.
14 */
15 static async hash(text: string): Promise<string> {
16 const textUint8 = new TextEncoder().encode(text); // encode as (utf-8) Uint8Array
17 const hashBuffer = await crypto.subtle.digest('SHA-256', textUint8); // hash the message
18 const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
19 const hashHex = hashArray
20 .map((b) => b.toString(16).padStart(2, '0'))
21 .join(''); // convert bytes to hex string
22 return hashHex;
23 }
24
25 static v4(): string {
26 return crypto.randomUUID();
27 }
28
29 static async deriveKey(password: string): Promise<CryptoKey> {
30 const algo = {
31 name: 'PBKDF2',
32 hash: 'SHA-256',
33 salt: new TextEncoder().encode('3e7cdebd-3b4c-4125-a18c-05750cad8ec3'),
34 iterations: 1000,
35 };
36 return crypto.subtle.deriveKey(
37 algo,
38 await crypto.subtle.importKey(
39 'raw',
40 new TextEncoder().encode(password),
41 {
42 name: algo.name,
43 },
44 false,
45 ['deriveKey']
46 ),
47 {
48 name: 'AES-GCM',
49 length: 256,
50 },
51 false,
52 ['encrypt', 'decrypt']
53 );
54 }
55
56 static async encrypt(
57 text: string,
58 ivBase64String: string,
59 password: string
60 ): Promise<string> {
61 const algo = {
62 name: 'AES-GCM',
63 length: 256,
64 iv: Buffer.from(ivBase64String, 'base64'),
65 };
66
67 const cipherText = await crypto.subtle.encrypt(
68 algo,
69 await CryptoHelper.deriveKey(password),
70 new TextEncoder().encode(text)
71 );
72 return Buffer.from(cipherText).toString('base64');
73 }
74
75 static async decrypt(
76 encryptedBase64String: string,
77 ivBase64String: string,
78 password: string
79 ): Promise<string> {
80 const algo = {
81 name: 'AES-GCM',
82 length: 256,
83 iv: Buffer.from(ivBase64String, 'base64'),
84 };
85 return new TextDecoder().decode(
86 await crypto.subtle.decrypt(
87 algo,
88 await CryptoHelper.deriveKey(password),
89 Buffer.from(encryptedBase64String, 'base64')
90 )
91 );
92 }
93 }
94