import type { DeriveKeyBytesParams } from '../types/params-direct.js'; import { getWebcryptoSubtle } from '@noble/ciphers/webcrypto'; import { crypto } from '@noble/hashes/crypto'; /** * The object that should be passed into `Pbkdf2.deriveKeyBytes()`, when using the PBKDF2 algorithm. */ export interface Pbkdf2Params { /** * A string representing the digest algorithm to use. This may be one of: * - 'SHA-256' * - 'SHA-384' * - 'SHA-512' */ hash: 'SHA-256' | 'SHA-384' | 'SHA-512'; /** * The salt value to use in the derivation process, as a Uint8Array. This should be a random or * pseudo-random value of at least 16 bytes. Unlike the `password`, `salt` does not need to be * kept secret. */ salt: Uint8Array; /** * A `Number` representing the number of iterations the hash function will be executed in * `deriveKey()`. This impacts the computational cost of the `deriveKey()` operation, making it * more resistant to dictionary attacks. The higher the number, the more secure, but also slower, * the operation. Choose a value that balances security needs and performance for your * application. */ iterations: number; } /** * The object that should be passed into `Pbkdf2.deriveKey()`, when using the PBKDF2 algorithm. */ export type Pbkdf2DeriveKeyParams = { /** * A string representing the digest algorithm to use. This may be one of: * - 'SHA-256' * - 'SHA-384' * - 'SHA-512' */ hash: 'SHA-256' | 'SHA-384' | 'SHA-512'; /** * The password from which to derive the key, represented as a Uint8Array. */ password: Uint8Array; /** * The salt value to use in the derivation process, as a Uint8Array. This should be a random or * pseudo-random value of at least 16 bytes. Unlike the `password`, `salt` does not need to be * kept secret. */ salt: Uint8Array; /** * A `Number` representing the number of iterations the hash function will be executed in * `deriveKey()`. This impacts the computational cost of the `deriveKey()` operation, making it * more resistant to dictionary attacks. The higher the number, the more secure, but also slower, * the operation. Choose a value that balances security needs and performance for your * application. */ iterations: number; /** * The desired length of the derived key in bits. To be compatible with all browsers, the number * should be a multiple of 8. */ length: number; }; /** * The `Pbkdf2` class provides a secure way to derive cryptographic keys from a password * using the PBKDF2 (Password-Based Key Derivation Function 2) algorithm. * * The PBKDF2 algorithm is widely used for generating keys from passwords, as it applies * a pseudorandom function to the input password along with a salt value and iterates the * process multiple times to increase the key's resistance to brute-force attacks. * * This class offers a single static method `deriveKey` to perform key derivation. * * @example * ```ts * // Key Derivation * const derivedKey = await Pbkdf2.deriveKey({ * hash: 'SHA-256', // The hash function to use ('SHA-256', 'SHA-384', 'SHA-512') * password: new TextEncoder().encode('password'), // The password as a Uint8Array * salt: new Uint8Array([...]), // The salt value * iterations: 1000, // The number of iterations * length: 256 // The length of the derived key in bits * }); * ``` * * @remarks * This class relies on the availability of the Web Crypto API. */ export class Pbkdf2 { /** * Derives a cryptographic key from a password using the PBKDF2 algorithm. * * @remarks * This method applies the PBKDF2 algorithm to the provided password along with * a salt value and iterates the process a specified number of times. It uses * a cryptographic hash function to enhance security and produce a key of the * desired length. The method is capable of utilizing either the Web Crypto API * or the Node.js Crypto module, depending on the environment's support. * * @example * ```ts * const derivedKey = await Pbkdf2.deriveKey({ * hash: 'SHA-256', * password: new TextEncoder().encode('password'), * salt: new Uint8Array([...]), * iterations: 1000, * length: 256 * }); * ``` * * @param params - The parameters for key derivation. * @param params.hash - The hash function to use, such as 'SHA-256', 'SHA-384', or 'SHA-512'. * @param params.password - The password from which to derive the key, represented as a Uint8Array. * @param params.salt - The salt value to use in the derivation process, as a Uint8Array. * @param params.iterations - The number of iterations to apply in the PBKDF2 algorithm. * @param params.length - The desired length of the derived key in bits. * * @returns A Promise that resolves to the derived key as a Uint8Array. */ public static async deriveKey({ hash, password, salt, iterations, length }: Pbkdf2DeriveKeyParams ): Promise { // Import the password as a raw key for use with the Web Crypto API. const webCryptoKey = await crypto.subtle.importKey( 'raw', password, { name: 'PBKDF2' }, false, ['deriveBits'] ); const derivedKeyBuffer = await crypto.subtle.deriveBits( { name: 'PBKDF2', hash, salt, iterations }, webCryptoKey, length ); // Convert from ArrayBuffer to Uint8Array. const derivedKey = new Uint8Array(derivedKeyBuffer); return derivedKey; } /** * Derives cryptographic key bytes from base key material using the PBKDF2 algorithm. * * @remarks * This method is similar to {@link Pbkdf2.deriveKey | `deriveKey()`} but accepts * raw key bytes (`baseKeyBytes`) instead of a password. It is intended for use cases * where the input key material is already available as a byte array. * * Notes: * - The `baseKeyBytes` that will be the input key material for PBKDF2 is expected to be a * low-entropy value, such as a password or passphrase. It should be kept confidential. * - In 2023, * {@link https://web.archive.org/web/20230123232056/https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 * | OWASP recommended} * a minimum of 600,000 iterations for PBKDF2-HMAC-SHA256 and 210,000 for PBKDF2-HMAC-SHA512. * * @example * ```ts * const derivedKeyBytes = await Pbkdf2.deriveKeyBytes({ * baseKeyBytes: new TextEncoder().encode('password'), * hash: 'SHA-256', * salt: new Uint8Array([...]), * iterations: 600_000, * length: 256 * }); * ``` * * @param params - The parameters for key derivation. * @returns A Promise that resolves to the derived key as a byte array. */ public static async deriveKeyBytes({ baseKeyBytes, hash, salt, iterations, length }: DeriveKeyBytesParams & Pbkdf2Params ): Promise { // Get the Web Crypto API interface. const webCrypto = getWebcryptoSubtle() as SubtleCrypto; // Import the password as a raw key for use with the Web Crypto API. const webCryptoKey = await webCrypto.importKey( 'raw', // key format is raw bytes baseKeyBytes as BufferSource, // key data to import { name: 'PBKDF2' }, // algorithm identifier false, // key is not extractable ['deriveBits'] // key usages ); // Derive the bytes using the Web Crypto API. const derivedKeyBuffer = await webCrypto.deriveBits( { name: 'PBKDF2', hash, salt: salt as BufferSource, iterations }, webCryptoKey, length ); // Convert from ArrayBuffer to Uint8Array. const derivedKeyBytes = new Uint8Array(derivedKeyBuffer); return derivedKeyBytes; } }