@@ -9,13 +9,53 @@ import util from '../util';
99
1010const webCrypto = util . getWebCrypto ( ) ;
1111const nodeCrypto = util . getNodeCrypto ( ) ;
12+ const nodeSubtleCrypto = nodeCrypto && nodeCrypto . webcrypto && nodeCrypto . webcrypto . subtle ;
1213
13- export default async function HKDF ( hashAlgo , key , salt , info , length ) {
14+ export default async function HKDF ( hashAlgo , inputKey , salt , info , outLen ) {
1415 const hash = enums . read ( enums . webHash , hashAlgo ) ;
1516 if ( ! hash ) throw new Error ( 'Hash algo not supported with HKDF' ) ;
1617
17- const crypto = webCrypto || nodeCrypto . webcrypto . subtle ;
18- const importedKey = await crypto . importKey ( 'raw' , key , 'HKDF' , false , [ 'deriveBits' ] ) ;
19- const bits = await crypto . deriveBits ( { name : 'HKDF' , hash, salt, info } , importedKey , length * 8 ) ;
20- return new Uint8Array ( bits ) ;
18+ if ( webCrypto || nodeSubtleCrypto ) {
19+ const crypto = webCrypto || nodeSubtleCrypto ;
20+ const importedKey = await crypto . importKey ( 'raw' , inputKey , 'HKDF' , false , [ 'deriveBits' ] ) ;
21+ const bits = await crypto . deriveBits ( { name : 'HKDF' , hash, salt, info } , importedKey , outLen * 8 ) ;
22+ return new Uint8Array ( bits ) ;
23+ }
24+
25+ if ( nodeCrypto ) {
26+ const hashAlgoName = enums . read ( enums . hash , hashAlgo ) ;
27+ // Node-only HKDF implementation based on https://www.rfc-editor.org/rfc/rfc5869
28+
29+ const computeHMAC = ( hmacKey , hmacMessage ) => nodeCrypto . createHmac ( hashAlgoName , hmacKey ) . update ( hmacMessage ) . digest ( ) ;
30+ // Step 1: Extract
31+ // PRK = HMAC-Hash(salt, IKM)
32+ const pseudoRandomKey = computeHMAC ( salt , inputKey ) ;
33+
34+ const hashLen = pseudoRandomKey . length ;
35+
36+ // Step 2: Expand
37+ // HKDF-Expand(PRK, info, L) -> OKM
38+ const n = Math . ceil ( outLen / hashLen ) ;
39+ const outputKeyingMaterial = new Uint8Array ( n * hashLen ) ;
40+
41+ // HMAC input buffer updated at each iteration
42+ const roundInput = new Uint8Array ( hashLen + info . length + 1 ) ;
43+ // T_i and last byte are updated at each iteration, but `info` remains constant
44+ roundInput . set ( info , hashLen ) ;
45+
46+ for ( let i = 0 ; i < n ; i ++ ) {
47+ // T(0) = empty string (zero length)
48+ // T(i) = HMAC-Hash(PRK, T(i-1) | info | i)
49+ roundInput [ roundInput . length - 1 ] = i + 1 ;
50+ // t = T(i+1)
51+ const t = computeHMAC ( pseudoRandomKey , i > 0 ? roundInput : roundInput . subarray ( hashLen ) ) ;
52+ roundInput . set ( t , 0 ) ;
53+
54+ outputKeyingMaterial . set ( t , i * hashLen ) ;
55+ }
56+
57+ return outputKeyingMaterial . subarray ( 0 , outLen ) ;
58+ }
59+
60+ throw new Error ( 'No HKDF implementation available' ) ;
2161}
0 commit comments