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