// Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt import base64 from "base64-arraybuffer"; import { toHex, fromHex } from "./"; // ----------------------------------------------------------------------------- // SHA256 // ----------------------------------------------------------------------------- /** * Utility function to return the hexadecimal SHA-256 digest of an input string. * * @param input - The input string to hash. * @returns The hexadecimal SHA-256 digest of <code>input</code>. */ export async function sha256(input: string): Promise<string> { const inputUtf8 = new TextEncoder().encode(input); // Convert input to UTF-8 return toHex(await crypto.subtle.digest("SHA-256", inputUtf8)); } /** * Utility function to return the double hexadecimal SHA-256 digest of an input string. * This is equivalent to <code>sha256(sha256(input))</code>. * * @param input - The input string to hash. * @returns The double hexadecimal SHA-256 digest of <code>input</code>. */ export async function doubleSHA256(input: string): Promise<string> { return sha256(await sha256(input)); } // ----------------------------------------------------------------------------- // AES-GCM // ----------------------------------------------------------------------------- export type AESEncryptedString = string; /** * Encrypts the given input string with the AES-GCM cipher, deriving a key from * the given password with SHA-256. * * Implementation mostly sourced from: * {@link https://gist.github.com/chrisveness/43bcda93af9f646d083fad678071b90a} * * @param input - The plain text input to be encrypted. * @param password - The password used to encrypt the input data. * @returns The encrypted cipher text (`IV (12 bytes hex) + CT (base64)`) */ export async function aesGcmEncrypt(input: string, password: string): Promise<AESEncryptedString> { // Hash the password as UTF-8 const passwordHash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(password)); // Generate a 96-bit random IV const iv = crypto.getRandomValues(new Uint8Array(12)); // Generate the key from the password const algorithm = { name: "AES-GCM", iv }; const key = await crypto.subtle.importKey("raw", passwordHash, algorithm, false, ["encrypt"]); // Encrypt the UTF-8-encoded input const cipherText = await crypto.subtle.encrypt(algorithm, key, new TextEncoder().encode(input)); // Return the IV (as hex) + cipher text (as base64) together return toHex(iv) + base64.encode(cipherText); } /** * Decrypts the given input cipher text with the AES-GCM cipher, deriving a key * from the given password with SHA-256. The input must be of the form * `IV (12 bytes hex) + CT (base64)`. * * Implementation mostly sourced from: * {@link https://gist.github.com/chrisveness/43bcda93af9f646d083fad678071b90a} * * @param input - The IV and cipher text to decrypt. * @param password - The password used to decrypt the input cipher text. * @returns The decrypted plain text data. */ export async function aesGcmDecrypt(input: AESEncryptedString, password: string): Promise<AESEncryptedString> { // Hash the password as UTF-8 const passwordHash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(password)); // Get the IV from the encrypted string (first 96 bits/12 bytes/24 hex chars) const iv = fromHex(input.slice(0, 24)); // Generate the key from the password const algorithm = { name: "AES-GCM", iv }; const key = await crypto.subtle.importKey("raw", passwordHash, algorithm, false, ["decrypt"]); // Decode the base64 cipher text to a UTF-8 Uint8Array const cipherText = base64.decode(input.slice(24)); // Decrypt the cipher text const dec = await crypto.subtle.decrypt(algorithm, key, cipherText); // Decode from UTF-8 return new TextDecoder().decode(dec); } // ----------------------------------------------------------------------------- // CryptoJS // ----------------------------------------------------------------------------- /** Polyfill for decrypting CryptoJS AES strings. This is used to migrate * local storage from KristWeb v1. */ export { decryptCryptoJS } from "./CryptoJS";