UNPKG

blockstack

Version:

The Blockstack Javascript library for authentication, identity, and storage.

160 lines (141 loc) 4.72 kB
import { getCryptoLib } from './cryptoUtils' export type Pbkdf2Digests = 'sha512' | 'sha256' export interface Pbkdf2 { derive( password: string, salt: Buffer, iterations: number, keyLength: number, digest: Pbkdf2Digests): Promise<Buffer>; } type NodePbkdf2Fn = typeof import('crypto').pbkdf2 export class NodeCryptoPbkdf2 implements Pbkdf2 { nodePbkdf2: NodePbkdf2Fn constructor(nodePbkdf2: NodePbkdf2Fn) { this.nodePbkdf2 = nodePbkdf2 } async derive( password: string, salt: Buffer, iterations: number, keyLength: number, digest: Pbkdf2Digests): Promise<Buffer> { if (digest !== 'sha512' && digest !== 'sha256') { throw new Error(`Unsupported digest "${digest}" for Pbkdf2`) } return new Promise((resolve, reject) => { this.nodePbkdf2(password, salt, iterations, keyLength, digest, (error, result) => { if (error) { reject(error) } resolve(result) }) }) } } export class WebCryptoPbkdf2 implements Pbkdf2 { subtleCrypto: SubtleCrypto constructor(subtleCrypto: SubtleCrypto) { this.subtleCrypto = subtleCrypto } async derive( password: string, salt: Buffer, iterations: number, keyLength: number, digest: Pbkdf2Digests): Promise<Buffer> { let algo: string if (digest === 'sha256') { algo = 'SHA-256' } else if (digest === 'sha512') { algo = 'SHA-512' } else { throw new Error(`Unsupported Pbkdf2 digest algorithm "${digest}"`) } let result: ArrayBuffer const passwordBytes = Buffer.from(password, 'utf8') try { const key = await this.subtleCrypto.importKey( 'raw', passwordBytes, 'PBKDF2', false, ['deriveBits'] ) result = await this.subtleCrypto.deriveBits({ name: 'PBKDF2', salt, iterations, hash: { name: algo } }, key, keyLength * 8) } catch (error) { // Browser appears to support WebCrypto but missing pbkdf2 support. const partialWebCrypto = new WebCryptoPartialPbkdf2(this.subtleCrypto) return partialWebCrypto.derive(password, salt, iterations, keyLength, digest) } return Buffer.from(result) } } export class WebCryptoPartialPbkdf2 implements Pbkdf2 { // An async implementation for browsers that support WebCrypto hmac // but not pbkdf2. Extracted from crypto-browserify/pbkdf2 and modified to // use WebCrypto for hmac operations. // Original: https://github.com/crypto-browserify/pbkdf2/tree/v3.0.17/lib subtleCrypto: SubtleCrypto constructor(subtleCrypto: SubtleCrypto) { this.subtleCrypto = subtleCrypto } async derive( password: string, salt: Buffer, iterations: number, keyLength: number, digest: Pbkdf2Digests): Promise<Buffer> { if (digest !== 'sha512' && digest !== 'sha256') { throw new Error(`Unsupported digest "${digest}" for Pbkdf2`) } const key = Buffer.from(password, 'utf8') const algo = digest === 'sha512' ? 'SHA-512' : 'SHA-256' const algoOpts = { name: 'HMAC', hash: algo } const hmacDigest = (key: ArrayBuffer, data: ArrayBuffer) => this.subtleCrypto .importKey('raw', key, algoOpts, true, ['sign']) .then(cryptoKey => this.subtleCrypto.sign(algoOpts, cryptoKey, data)) .then(result => new Uint8Array(result)) const DK = new Uint8Array(keyLength) const saltLength = salt.length const block1 = new Uint8Array(saltLength + 4) block1.set(salt) let destPos = 0 const hLen = digest === 'sha512' ? 64 : 32 const l = Math.ceil(keyLength / hLen) function writeUInt32BE(data: Uint8Array, value: number, offset: number) { value = +value offset >>>= 0 data[offset] = (value >>> 24) data[offset + 1] = (value >>> 16) data[offset + 2] = (value >>> 8) data[offset + 3] = (value & 0xff) return offset + 4 } for (let i = 1; i <= l; i++) { writeUInt32BE(block1, i, saltLength) // eslint-disable-next-line no-await-in-loop const T = await hmacDigest(key, block1) let U = T for (let j = 1; j < iterations; j++) { // eslint-disable-next-line no-await-in-loop U = await hmacDigest(key, U) for (let k = 0; k < hLen; k++) { T[k] ^= U[k] } } DK.set(T.subarray(0, DK.byteLength - destPos), destPos) destPos += hLen } return Buffer.from(DK.buffer) } } export async function createPbkdf2(): Promise<Pbkdf2> { const cryptoLib = await getCryptoLib() if (cryptoLib.name === 'subtleCrypto') { return new WebCryptoPbkdf2(cryptoLib.lib) } else { return new NodeCryptoPbkdf2(cryptoLib.lib.pbkdf2) } }