Skip to main content

encryption/kdf/
hkdf_sha256.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! HKDF-SHA256(RFC 5869):从高熵密钥材料派生子密钥。
4
5use hkdf::Hkdf;
6use sha2::Sha256;
7
8use crate::contract::{Identified, KeyDerivation};
9use crate::error::{Error, Result};
10
11/// RFC 5869 的输出上限:255 × HashLen。
12const MAX_OUTPUT_LEN: usize = 255 * 32;
13
14/// HKDF-SHA256 派生器(无状态,可复用)。
15#[derive(Debug, Default, Clone, Copy)]
16pub struct HkdfSha256;
17
18impl HkdfSha256 {
19    pub const IDENTIFIER: &'static str = "hkdf-sha256";
20
21    pub fn new() -> Self {
22        Self
23    }
24}
25
26impl Identified for HkdfSha256 {
27    fn identifier(&self) -> &str {
28        Self::IDENTIFIER
29    }
30}
31
32impl KeyDerivation for HkdfSha256 {
33    /// `salt` 为空时按 RFC 5869 使用全零盐;`info` 可为空。
34    fn derive(&self, ikm: &[u8], salt: &[u8], length: usize, info: &[u8]) -> Result<Vec<u8>> {
35        if length == 0 || length > MAX_OUTPUT_LEN {
36            return Err(Error::InvalidOutputLength {
37                requested: length,
38                max: MAX_OUTPUT_LEN,
39            });
40        }
41
42        let hkdf = if salt.is_empty() {
43            Hkdf::<Sha256>::new(None, ikm)
44        } else {
45            Hkdf::<Sha256>::new(Some(salt), ikm)
46        };
47
48        let mut output = vec![0u8; length];
49        hkdf.expand(info, &mut output)
50            .map_err(|_| Error::InvalidOutputLength {
51                requested: length,
52                max: MAX_OUTPUT_LEN,
53            })?;
54        Ok(output)
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    /// RFC 5869 附录 A.1(SHA-256 版本)的测试向量。
63    #[test]
64    fn matches_rfc5869_test_vector() {
65        let ikm = [0x0bu8; 22];
66        let salt: Vec<u8> = (0x00u8..=0x0c).collect();
67        let info: Vec<u8> = (0xf0u8..=0xf9).collect();
68
69        let okm = HkdfSha256::new().derive(&ikm, &salt, 42, &info).unwrap();
70        assert_eq!(
71            crate::internal::hex_encode(&okm),
72            "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"
73        );
74    }
75
76    #[test]
77    fn empty_salt_and_limits() {
78        let kdf = HkdfSha256::new();
79        assert_eq!(kdf.derive(b"ikm", b"", 16, b"").unwrap().len(), 16);
80        assert_eq!(
81            kdf.derive(b"ikm", b"", 0, b"").unwrap_err(),
82            Error::InvalidOutputLength {
83                requested: 0,
84                max: MAX_OUTPUT_LEN
85            }
86        );
87        assert_eq!(
88            kdf.derive(b"ikm", b"", MAX_OUTPUT_LEN + 1, b"")
89                .unwrap_err(),
90            Error::InvalidOutputLength {
91                requested: MAX_OUTPUT_LEN + 1,
92                max: MAX_OUTPUT_LEN
93            }
94        );
95    }
96}