Encryption Keys (new)

hash & encryption blocks from ORG

The Database Encryption Key encryptionKey is a 32-byte hexadecimal string (length 64).

const encryptionKey: string = '0101010101010101010101010101010101010101010101010101010101010101';

This key is used to safeguard the user’s mnemonic and wallet keys from attackers. We recommend an encryption key that is generated with entropy or pbkdf2 hash from a strong user-provided password or PIN.

Keep the encryption key extremely safe, as it grants access to the user's wallets and mnemonics. On Mobile iOS, consider storing the Encryption Key in Secure Enclave.

Example: Create and store encryption key to local storage

// hash-service.ts
import { pbkdf2 } from "@railgun-community/wallet";
import { Pbkdf2Response } from "@railgun-community/shared-models";

/**
 * Type definition for hashed password data.
 *
 * @property {string} secret - The hashed password or secret.
 * @property {string} salt - The salt used in the password hashing process.
 * @property {number} iterations - The number of iterations used in the hashing algorithm.
 */
type HashPasswordString = {
  secret: string;
  salt: string;
  iterations: number;
};

/**
 * Generates a salted hash from a secret string using PBKDF2 algorithm.
 *
 * @param {object} params - The parameters for hashing
 * @param {string} params.secret - The secret string to be hashed
 * @param {string} params.salt - The salt used in the hashing process
 * @param {number} params.iterations - Number of iterations for the PBKDF2 algorithm
 * @returns {Promise<Pbkdf2Response>} A promise that resolves to the hashing result
 */
export const hashPasswordString = async ({
  secret,
  salt,
  iterations,
}: HashPasswordString): Promise<Pbkdf2Response> => {
  return pbkdf2(secret, salt, iterations);
};

Example: Get encryption key from local storage

// encryption.ts
import { getRandomBytes } from "@railgun-community/wallet";
import { hashPasswordString } from "./hash-service";
import fs from "fs";

/**
 * Asynchronously stores data in a JSON file.
 *
 * This function takes an object and persists it to the filesystem
 * as a JSON file with the provided filename with a `.json` extension.
 * The data is wrapped in an object with a `data` property before being stored.
 *
 * @param filename - The name of the file where the data will be stored (without extension)
 * @param data - The data to be stored, typically an object like {hashPasswordStored, salt}
 * @returns A Promise that resolves when the data has been written to the file
 *
 * @example
 * ```typescript
 * await storeData('credentials', { hashPasswordStored: '123abc', salt: 'xyz789' });
 * // Creates credentials.json with the content: {"data":{"hashPasswordStored":"123abc","salt":"xyz789"}}
 * ```
 */
export const storeData = async (filename: string, data: any): Promise<void> => {
  // incoming object will be
  // {hashPasswordStored, salt}
  fs.writeFileSync(`${filename}.json`, JSON.stringify({ data }));
};

/**
 * Asynchronously reads and parses JSON data from a file.
 *
 * @param filename - The name of the file to read (without the .json extension)
 * @returns A Promise that resolves to the parsed data property from the JSON file
 * @throws Will throw an error if the file cannot be read or if the JSON is invalid
 *
 * @example
 * ```typescript
 * const data = await getData('config');
 * console.log(data); // Outputs the 'data' property from config.json
 * ```
 */
export const getData = async (filename: string): Promise<any> => {
  return JSON.parse(fs.readFileSync(`${filename}.json`, "utf8")).data;
};

/**
 * Generates and stores encryption keys based on user-provided password.
 *
 * This function creates a secure encryption key from the user's password by:
 * 1. Generating a random salt
 * 2. Deriving an encryption key from the password and salt using 100,000 iterations
 * 3. Generating a separate password hash for storage using 1,000,000 iterations for extra security
 * 4. Storing the salt and the hashed password in local storage
 *
 * @param password - The user-provided password to generate encryption keys from
 * @returns A Promise that resolves with the derived encryption key
 *
 * @example
 * const encryptionKey = await setEncryptionKeyFromPassword("userPassword123");
 */
export const setEncryptionKeyFromPassword = async (
  password: string
): Promise<string> => {
  // Desired `password` comes from user input

  const salt = getRandomBytes(16); // Generate salt
  const [encryptionKey, hashPasswordStored] = await Promise.all([
    hashPasswordString({ secret: password, salt, iterations: 100000 }), // Generate hash from password and salt
    hashPasswordString({ secret: password, salt, iterations: 1000000 }), // Generate hash for stored password. Use more iterations for the stored value.
  ]);

  await Promise.all([
    await storeData("hashPasswordStored", hashPasswordStored), // Save `hashPasswordStored` to local storage
    await storeData("salt", salt), // Save `salt` to local storage
  ]);

  return encryptionKey;
};

/**
 * Validates the user's password and returns an encryption key if valid.
 *
 * This function performs two primary operations:
 * 1. Validates the provided password against a previously stored hash
 * 2. Generates an encryption key based on the password if validation succeeds
 *
 * @param {string} password - The user-provided password to validate
 * @returns {Promise<string>} A promise that resolves to the encryption key if the password is correct
 * @throws {Error} Throws an error if the password is incorrect
 *
 * @remarks
 * The function uses different iteration counts for generating the encryption key (100,000)
 * versus validating the password (1,000,000), which provides a balance between
 * security and performance.
 */
export const getEncryptionKeyFromPassword = async (
  password: string
): Promise<string> => {
  //   `password` comes from user input
  const [storedPasswordHash, storedSalt] = await Promise.all([
    getData("hashPasswordStored"), // Fetch the previously stored password hash from local storage
    getData("salt"), //   ..., // Fetch the previously stored `salt` from local storage
  ]);
  console.log("STORED DATA", storedPasswordHash, storedSalt);
  const [encryptionKey, hashPassword] = await Promise.all([
    hashPasswordString({
      secret: password,
      salt: storedSalt,
      iterations: 100000,
    }), // Same iterations as when generated, i.e. 100000
    hashPasswordString({
      secret: password,
      salt: storedSalt,
      iterations: 1000000,
    }), // Same iterations as when generated, i.e. 1000000
  ]);
  if (storedPasswordHash !== hashPassword) {
    throw new Error("Incorrect password.");
  }
  return encryptionKey;
};

Last updated