Skip to content

Commit 84da9de

Browse files
committed
refactor: more readability in ecdhes.ts
1 parent 475a3ed commit 84da9de

File tree

1 file changed

+64
-26
lines changed

1 file changed

+64
-26
lines changed

src/lib/ecdhes.ts

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,51 @@ function lengthAndInput(input: Uint8Array) {
77
return concat(uint32be(input.length), input)
88
}
99

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)
1938
}
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)
2142
}
2243

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+
*/
2355
export async function deriveKey(
2456
publicKey: types.CryptoKey,
2557
privateKey: types.CryptoKey,
@@ -31,33 +63,39 @@ export async function deriveKey(
3163
checkEncCryptoKey(publicKey, 'ECDH')
3264
checkEncCryptoKey(privateKey, 'ECDH', 'deriveBits')
3365

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)
4072

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)
4874

49-
const sharedSecret = new Uint8Array(
75+
// Perform ECDH to get the shared secret Z
76+
const Z = new Uint8Array(
5077
await crypto.subtle.deriveBits(
5178
{
5279
name: publicKey.algorithm.name,
5380
public: publicKey,
5481
},
5582
privateKey,
56-
length,
83+
getEcdhBitLength(publicKey),
5784
),
5885
)
5986

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+
)
6199
}
62100

63101
export function allowed(key: types.CryptoKey) {

0 commit comments

Comments
 (0)