Skip to main content

encryption/kdf/
pbkdf2_sha256.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! PBKDF2-HMAC-SHA256:从人类可读口令派生密钥。
4//!
5//! 存储用户密码请改用 Argon2 / `password_hash` 等专用 API,本实现面向的
6//! 是从口令派生加解密密钥的场景。
7
8use sha2::Sha256;
9
10use crate::contract::{Identified, PasswordBasedKdf};
11use crate::error::{Error, Result};
12
13/// 输出长度上限:PBKDF2 的块序号是 32 位量(最多 (2^32−1) 块 × 32 字节),
14/// 取 `u32::MAX` 与 [`crate::kdf::Argon2Kdf`] 同一约定;上限把「先按请求分配、
15/// 再必然失败」的巨量请求拦在 `vec![0u8; length]` 之前。
16const MAX_OUTPUT_LEN: usize = u32::MAX as usize;
17
18/// PBKDF2-HMAC-SHA256 派生器。
19#[derive(Debug, Clone, Copy)]
20pub struct Pbkdf2Sha256 {
21    iterations: u32,
22}
23
24impl Pbkdf2Sha256 {
25    pub const IDENTIFIER: &'static str = "pbkdf2-sha256";
26
27    /// 默认迭代次数(与原版一致:OWASP 建议量级,请按环境调整)。
28    pub const DEFAULT_ITERATIONS: u32 = 310_000;
29
30    /// 迭代次数必须为正。
31    pub fn new(iterations: u32) -> Result<Self> {
32        if iterations == 0 {
33            return Err(Error::InvalidIterations);
34        }
35        Ok(Self { iterations })
36    }
37
38    /// 使用 [`Pbkdf2Sha256::DEFAULT_ITERATIONS`]。
39    pub fn with_default_iterations() -> Self {
40        Self {
41            iterations: Self::DEFAULT_ITERATIONS,
42        }
43    }
44
45    pub fn iterations(&self) -> u32 {
46        self.iterations
47    }
48}
49
50impl Default for Pbkdf2Sha256 {
51    fn default() -> Self {
52        Self::with_default_iterations()
53    }
54}
55
56impl Identified for Pbkdf2Sha256 {
57    fn identifier(&self) -> &str {
58        Self::IDENTIFIER
59    }
60}
61
62impl PasswordBasedKdf for Pbkdf2Sha256 {
63    fn derive_from_password(&self, password: &[u8], salt: &[u8], length: usize) -> Result<Vec<u8>> {
64        if length == 0 || length > MAX_OUTPUT_LEN {
65            return Err(Error::InvalidOutputLength {
66                requested: length,
67                max: MAX_OUTPUT_LEN,
68            });
69        }
70
71        let mut output = vec![0u8; length];
72        pbkdf2::pbkdf2_hmac::<Sha256>(password, salt, self.iterations, &mut output);
73        Ok(output)
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    /// RFC 6070 风格的公开测试向量(PBKDF2-HMAC-SHA256,c=1)。
82    #[test]
83    fn matches_known_vector() {
84        let kdf = Pbkdf2Sha256::new(1).unwrap();
85        let derived = kdf.derive_from_password(b"password", b"salt", 32).unwrap();
86        assert_eq!(
87            crate::internal::hex_encode(&derived),
88            "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"
89        );
90    }
91
92    #[test]
93    fn same_inputs_same_output_and_length_validation() {
94        let kdf = Pbkdf2Sha256::with_default_iterations();
95        assert_eq!(kdf.iterations(), 310_000);
96        let first = kdf.derive_from_password(b"pw", b"salt", 16).unwrap();
97        let second = kdf.derive_from_password(b"pw", b"salt", 16).unwrap();
98        assert_eq!(first, second);
99        assert_eq!(
100            kdf.derive_from_password(b"pw", b"salt", 0).unwrap_err(),
101            Error::InvalidOutputLength {
102                requested: 0,
103                max: MAX_OUTPUT_LEN
104            }
105        );
106        // 上限之上:必须在 `vec![0u8; length]` 之前干净报错,而不是先申请
107        // 2^63 字节撞上 abort/OOM(修复前的行为)。
108        assert_eq!(
109            kdf.derive_from_password(b"pw", b"salt", usize::MAX / 2)
110                .unwrap_err(),
111            Error::InvalidOutputLength {
112                requested: usize::MAX / 2,
113                max: MAX_OUTPUT_LEN
114            }
115        );
116        // 边界:上限 + 1 即拒(上限本身会真分配 4 GiB,不在此断言)。
117        assert_eq!(
118            kdf.derive_from_password(b"pw", b"salt", MAX_OUTPUT_LEN + 1)
119                .unwrap_err(),
120            Error::InvalidOutputLength {
121                requested: MAX_OUTPUT_LEN + 1,
122                max: MAX_OUTPUT_LEN
123            }
124        );
125    }
126
127    #[test]
128    fn zero_iterations_is_rejected() {
129        assert_eq!(Pbkdf2Sha256::new(0).unwrap_err(), Error::InvalidIterations);
130    }
131}