@@ -7,19 +7,51 @@ function lengthAndInput(input: Uint8Array) {
7
7
return concat ( uint32be ( input . length ) , input )
8
8
}
9
9
10
- async function concatKdf ( secret : Uint8Array , bits : number , value : Uint8Array ) {
11
- const iterations = Math . ceil ( ( bits >> 3 ) / 32 )
12
- const res = new Uint8Array ( iterations * 32 )
13
- for ( let iter = 0 ; iter < iterations ; iter ++ ) {
14
- const buf = new Uint8Array ( 4 + secret . length + value . length )
15
- buf . set ( uint32be ( iter + 1 ) )
16
- buf . set ( secret , 4 )
17
- buf . set ( value , 4 + secret . length )
18
- res . set ( await digest ( 'sha256' , buf ) , iter * 32 )
10
+ /**
11
+ * Concat KDF implementation
12
+ *
13
+ * @param Z - Shared secret from key-agreement scheme
14
+ * @param L - Length of derived keying material in bits
15
+ * @param OtherInfo - Context and application specific data
16
+ */
17
+ async function concatKdf ( Z : Uint8Array , L : number , OtherInfo : Uint8Array ) {
18
+ // dkLen = L (in bits), converted to bytes for output length
19
+ const dkLen = L >> 3
20
+ // Hash output length in bytes (SHA-256 produces 32 bytes)
21
+ const hashLen = 32
22
+ // Number of hash function calls needed
23
+ const reps = Math . ceil ( dkLen / hashLen )
24
+ // Initialize output buffer for concatenated hash results
25
+ const dk = new Uint8Array ( reps * hashLen )
26
+
27
+ // Perform reps iterations of the hash function
28
+ for ( let i = 1 ; i <= reps ; i ++ ) {
29
+ // Construct Hash_i input: Counter || Z || OtherInfo
30
+ const hashInput = new Uint8Array ( 4 + Z . length + OtherInfo . length )
31
+ hashInput . set ( uint32be ( i ) , 0 ) // 32-bit big-endian counter
32
+ hashInput . set ( Z , 4 ) // Shared secret Z
33
+ hashInput . set ( OtherInfo , 4 + Z . length ) // OtherInfo
34
+
35
+ // Hash_i = Hash(Counter || Z || OtherInfo)
36
+ const hashResult = await digest ( 'sha256' , hashInput )
37
+ dk . set ( hashResult , ( i - 1 ) * hashLen )
19
38
}
20
- return res . slice ( 0 , bits >> 3 )
39
+
40
+ // Return leading L bits of dk (truncate to exact length needed)
41
+ return dk . slice ( 0 , dkLen )
21
42
}
22
43
44
+ /**
45
+ * ECDH-ES Key Agreement with Concat KDF
46
+ *
47
+ * @param publicKey
48
+ * @param privateKey
49
+ * @param algorithm - AlgorithmID: For Direct Key Agreement (ECDH-ES), this is the "enc" value. For
50
+ * Key Agreement with Key Wrapping, this is the "alg" value
51
+ * @param keyLength - Keydatalen: Number of bits in the desired output key
52
+ * @param apu - PartyUInfo: Agreement PartyUInfo value (information about the producer)
53
+ * @param apv - PartyVInfo: Agreement PartyVInfo value (information about the recipient)
54
+ */
23
55
export async function deriveKey (
24
56
publicKey : types . CryptoKey ,
25
57
privateKey : types . CryptoKey ,
@@ -31,33 +63,39 @@ export async function deriveKey(
31
63
checkEncCryptoKey ( publicKey , 'ECDH' )
32
64
checkEncCryptoKey ( privateKey , 'ECDH' , 'deriveBits' )
33
65
34
- const value = concat (
35
- lengthAndInput ( encoder . encode ( algorithm ) ) ,
36
- lengthAndInput ( apu ) ,
37
- lengthAndInput ( apv ) ,
38
- uint32be ( keyLength ) ,
39
- )
66
+ // Construct OtherInfo
67
+ const algorithmID = lengthAndInput ( encoder . encode ( algorithm ) )
68
+ const partyUInfo = lengthAndInput ( apu )
69
+ const partyVInfo = lengthAndInput ( apv )
70
+ const suppPubInfo = uint32be ( keyLength )
71
+ const suppPrivInfo = new Uint8Array ( 0 )
40
72
41
- let length : number
42
- if ( publicKey . algorithm . name === 'X25519' ) {
43
- length = 256
44
- } else {
45
- length =
46
- Math . ceil ( parseInt ( ( publicKey . algorithm as EcKeyAlgorithm ) . namedCurve . slice ( - 3 ) , 10 ) / 8 ) << 3
47
- }
73
+ const otherInfo = concat ( algorithmID , partyUInfo , partyVInfo , suppPubInfo , suppPrivInfo )
48
74
49
- const sharedSecret = new Uint8Array (
75
+ // Perform ECDH to get the shared secret Z
76
+ const Z = new Uint8Array (
50
77
await crypto . subtle . deriveBits (
51
78
{
52
79
name : publicKey . algorithm . name ,
53
80
public : publicKey ,
54
81
} ,
55
82
privateKey ,
56
- length ,
83
+ getEcdhBitLength ( publicKey ) ,
57
84
) ,
58
85
)
59
86
60
- return concatKdf ( sharedSecret , keyLength , value )
87
+ // Apply Concat KDF to derive the final key material
88
+ return concatKdf ( Z , keyLength , otherInfo )
89
+ }
90
+
91
+ function getEcdhBitLength ( publicKey : CryptoKey ) {
92
+ if ( publicKey . algorithm . name === 'X25519' ) {
93
+ return 256
94
+ }
95
+
96
+ return (
97
+ Math . ceil ( parseInt ( ( publicKey . algorithm as EcKeyAlgorithm ) . namedCurve . slice ( - 3 ) , 10 ) / 8 ) << 3
98
+ )
61
99
}
62
100
63
101
export function allowed ( key : types . CryptoKey ) {
0 commit comments