import { Buffer } from '@stacks/common' ; import { getCryptoLib } from './cryptoUtils' ; export type Pbkdf2Digests = 'sha512' | 'sha256' ; export interface Pbkdf2 { derive ( password , salt , iterations , keyLength , digest ) : Promise < Buffer > ; } type NodePbkdf2Fn = typeof import ( 'crypto' ) . pbkdf2 ; export class NodeCryptoPbkdf2 implements Pbkdf2 { nodePbkdf2 : NodePbkdf2Fn ; constructor ( nodePbkdf2 ) { this . nodePbkdf2 = nodePbkdf2 ; } async derive ( password , salt , iterations , keyLength , digest ) { 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 ) { this . subtleCrypto = subtleCrypto ; } async derive ( password , salt , iterations , keyLength , digest ) { let algo ; if ( digest === 'sha256' ) { algo = 'SHA-256' ; } else if ( digest === 'sha512' ) { algo = 'SHA-512' ; } else { throw new Error ( `Unsupported Pbkdf2 digest algorithm "${ digest }"` ) ; } let result ; 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 ) { const partialWebCrypto = new WebCryptoPartialPbkdf2 ( this . subtleCrypto ) ; return partialWebCrypto . derive ( password , salt , iterations , keyLength , digest ) ; } return Buffer . from ( result ) ; } } export class WebCryptoPartialPbkdf2 implements Pbkdf2 { subtleCrypto : SubtleCrypto ; constructor ( subtleCrypto ) { this . subtleCrypto = subtleCrypto ; } async derive ( password , salt , iterations , keyLength , digest ) { 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 , data ) => 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 , value , offset ) { 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 ) ; const T = await hmacDigest ( key , block1 ) ; let U = T ; for ( let j = 1 ; j < iterations ; j ++ ) { 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 ( ) { const cryptoLib = await getCryptoLib ( ) ; if ( cryptoLib . name === 'subtleCrypto' ) { return new WebCryptoPbkdf2 ( cryptoLib . lib ) ; } else { return new NodeCryptoPbkdf2 ( cryptoLib . lib . pbkdf2 ) ; } }