@web5/agent
Version:
116 lines (107 loc) • 4.79 kB
text/typescript
// ! TODO : Make sure I remove `@noble/ciphers` from the Agent package.json once this is moved to the `@web5/crypto` package.
import { getWebcryptoSubtle } from '@noble/ciphers/webcrypto';
import type { DeriveKeyBytesParams } from '../types/params-direct.js';
/**
* 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 `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.
*
* 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
* // Key Derivation
* const derivedKeyBytes = await Pbkdf2.deriveKeyBytes({
* baseKeyBytes: new TextEncoder().encode('password'), // The password as a Uint8Array
* hash: 'SHA-256', // The hash function to use ('SHA-256', 'SHA-384', 'SHA-512')
* salt: new Uint8Array([...]), // The salt value
* iterations: 600_000, // 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 derivedKeyBytes = await Pbkdf2.deriveKeyBytes({
* baseKeyBytes: new TextEncoder().encode('password'), // The password as a Uint8Array
* hash: 'SHA-256', // The hash function to use ('SHA-256', 'SHA-384', 'SHA-512')
* salt: new Uint8Array([...]), // The salt value
* iterations: 600_000, // The number of iterations
* length: 256 // The length of the derived key in bits
* });
* ```
*
* @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<Uint8Array> {
// 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, // 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, iterations },
webCryptoKey,
length
);
// Convert from ArrayBuffer to Uint8Array.
const derivedKeyBytes = new Uint8Array(derivedKeyBuffer);
return derivedKeyBytes;
}
}