Skip to content

Commit 8bb5e0c

Browse files
committed
Implement HKDF in base provider using HMAC
TODO: add tests
1 parent ea754fb commit 8bb5e0c

File tree

1 file changed

+84
-0
lines changed
  • cryptography-providers/base/src/commonMain/kotlin/algorithms

1 file changed

+84
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2024 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.whyoleg.cryptography.providers.base.algorithms
6+
7+
import dev.whyoleg.cryptography.*
8+
import dev.whyoleg.cryptography.algorithms.*
9+
import dev.whyoleg.cryptography.materials.key.*
10+
import dev.whyoleg.cryptography.operations.*
11+
import dev.whyoleg.cryptography.providers.base.*
12+
import kotlin.math.*
13+
14+
@CryptographyProviderApi
15+
public abstract class BaseHkdf(provider: CryptographyProvider) : HKDF {
16+
private val hmac = provider.get(HMAC)
17+
18+
override fun secretDerivation(
19+
digest: CryptographyAlgorithmId<Digest>,
20+
outputSize: BinarySize,
21+
salt: ByteArray,
22+
info: ByteArray?,
23+
): SecretDerivation = HkdfSecretDerivation(
24+
decoder = hmac.keyDecoder(digest),
25+
digestSize = digestSize(digest),
26+
outputSize = outputSize,
27+
salt = salt,
28+
info = info
29+
)
30+
31+
protected abstract fun digestSize(digest: CryptographyAlgorithmId<Digest>): Int
32+
33+
private class HkdfSecretDerivation(
34+
private val decoder: KeyDecoder<HMAC.Key.Format, HMAC.Key>,
35+
private val digestSize: Int,
36+
private val outputSize: BinarySize,
37+
private val salt: ByteArray,
38+
private val info: ByteArray?,
39+
) : SecretDerivation {
40+
41+
override fun deriveSecretToByteArrayBlocking(input: ByteArray): ByteArray {
42+
val iterations = ceil(outputSize.inBytes.toDouble() / digestSize).toInt()
43+
require(iterations <= 255) { "out length must be maximal 255 * hash-length; requested: $outputSize" }
44+
45+
val pseudoRandomKey = decoder.decodeFromByteArrayBlocking(HMAC.Key.Format.RAW, salt)
46+
.signatureGenerator()
47+
.generateSignatureBlocking(input)
48+
49+
val function =
50+
decoder.decodeFromByteArrayBlocking(HMAC.Key.Format.RAW, pseudoRandomKey)
51+
.signatureGenerator()
52+
.createSignFunction()
53+
54+
/**
55+
* The output `OKM` is calculated as follows:
56+
*
57+
* N = ceil(L/HashLen) (iterations)
58+
* T = T(1) | T(2) | T(3) | ... | T(N) (block)
59+
* OKM = first L octets of T (output)
60+
*
61+
* where:
62+
* T(0) = empty string (zero length)
63+
* T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
64+
* T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
65+
* T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
66+
* ...
67+
*/
68+
var output = EmptyByteArray
69+
var t = EmptyByteArray
70+
val iterationArray = ByteArray(1)
71+
repeat(iterations) { iteration ->
72+
function.update(t)
73+
info?.let(function::update)
74+
iterationArray[0] = (iteration + 1).toByte()
75+
function.update(iterationArray)
76+
77+
t = function.signToByteArray()
78+
output += t
79+
}
80+
81+
return output.ensureSizeExactly(outputSize.inBytes)
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)