///|
/// HKDF-SHA256 (RFC 5869). This exists for RFC 9580 §5.5.3: when secret key
/// material is protected with S2K usage octet 253 (AEAD), the S2K-derived
/// key is stretched through HKDF-SHA256 with an empty salt and the packet's
/// own parameters as the `info` string, so one Argon2/iterated derivation
/// can safely serve key and nonce separation.
///|
/// HKDF-Extract: `PRK = HMAC-SHA256(salt, IKM)`. An empty `salt` is treated
/// as `HashLen` zero octets, which is exactly what HMAC's zero-padding of a
/// short key already produces.
fn hkdf_sha256_extract(ikm : BytesView, salt : BytesView) -> Bytes {
Sha256.hmac(salt, ikm)
}
///|
/// HKDF-SHA256 (RFC 5869 §2): extract then expand to `out_len` octets.
///
/// # Example
/// ```mbt check
/// test {
/// let ikm = @primitives.from_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
/// let okm = @primitives.hkdf_sha256(
/// ikm,
/// @primitives.from_hex("000102030405060708090a0b0c"),
/// @primitives.from_hex("f0f1f2f3f4f5f6f7f8f9"),
/// 42,
/// )
/// inspect(
/// @primitives.to_hex(okm),
/// content=(
/// #|3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865
/// ),
/// )
/// }
/// ```
pub fn hkdf_sha256(
ikm : BytesView,
salt : BytesView,
info : BytesView,
out_len : Int32,
) -> Bytes raise {
guard out_len >= 0 && out_len <= 255 * 32 else {
@error.fail("hkdf: output length must be 0..8160 octets")
}
let prk = hkdf_sha256_extract(ikm, salt)
let out : Array[Byte] = Array(capacity=out_len)
let mut block = Bytes::new(0)
let mut counter = 1
while out.length() < out_len {
let input = concat3(block, info, Bytes::from_array([counter.to_byte()]))
block = Sha256.hmac(prk, input)
for b in block {
if out.length() < out_len {
out.push(b)
}
}
counter = counter + 1
}
Bytes::from_array(out)
}