p2panda_encryption/crypto/
hkdf.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Hashed Message Authentication Code (HMAC)-based key derivation function (HKDF) using
4//! "hash-mode" with SHA256.
5//!
6//! <https://www.rfc-editor.org/rfc/rfc5869>
7use hkdf::Hkdf;
8use sha2::Sha256;
9use thiserror::Error;
10
11pub fn hkdf<const N: usize>(
12    salt: &[u8],
13    ikm: &[u8],
14    info: Option<&[u8]>,
15) -> Result<[u8; N], HkdfError> {
16    let salt = if salt.is_empty() { None } else { Some(salt) };
17    let hk = Hkdf::<Sha256>::new(salt, ikm);
18    let mut okm = [0u8; N];
19    hk.expand(info.unwrap_or_default(), &mut okm)
20        .map_err(|_| HkdfError::InvalidArguments)?;
21    Ok(okm)
22}
23
24#[derive(Debug, Error)]
25pub enum HkdfError {
26    #[error("arguments too large for hkdf")]
27    InvalidArguments,
28}
29
30#[cfg(test)]
31mod tests {
32    use super::hkdf;
33
34    #[test]
35    fn key_material_len() {
36        let result_1: [u8; 18] = hkdf(b"salt", b"ikm", None).unwrap();
37        assert_eq!(result_1.len(), 18);
38    }
39
40    #[test]
41    fn info_needs_to_match() {
42        let result_1: [u8; 18] = hkdf(b"salt", b"ikm", Some(b"info")).unwrap();
43        let result_2: [u8; 18] = hkdf(b"salt", b"ikm", Some(b"info")).unwrap();
44        let result_3: [u8; 18] = hkdf(b"salt", b"ikm", Some(b"different info")).unwrap();
45        assert_eq!(result_1, result_2);
46        assert_ne!(result_2, result_3);
47    }
48}