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-HMAC-SHA256 派生器。
14#[derive(Debug, Clone, Copy)]
15pub struct Pbkdf2Sha256 {
16    iterations: u32,
17}
18
19impl Pbkdf2Sha256 {
20    pub const IDENTIFIER: &'static str = "pbkdf2-sha256";
21
22    /// 默认迭代次数(与原版一致:OWASP 建议量级,请按环境调整)。
23    pub const DEFAULT_ITERATIONS: u32 = 310_000;
24
25    /// 迭代次数必须为正。
26    pub fn new(iterations: u32) -> Result<Self> {
27        if iterations == 0 {
28            return Err(Error::InvalidIterations);
29        }
30        Ok(Self { iterations })
31    }
32
33    /// 使用 [`Pbkdf2Sha256::DEFAULT_ITERATIONS`]。
34    pub fn with_default_iterations() -> Self {
35        Self {
36            iterations: Self::DEFAULT_ITERATIONS,
37        }
38    }
39
40    pub fn iterations(&self) -> u32 {
41        self.iterations
42    }
43}
44
45impl Default for Pbkdf2Sha256 {
46    fn default() -> Self {
47        Self::with_default_iterations()
48    }
49}
50
51impl Identified for Pbkdf2Sha256 {
52    fn identifier(&self) -> &str {
53        Self::IDENTIFIER
54    }
55}
56
57impl PasswordBasedKdf for Pbkdf2Sha256 {
58    fn derive_from_password(&self, password: &[u8], salt: &[u8], length: usize) -> Result<Vec<u8>> {
59        if length == 0 {
60            return Err(Error::InvalidOutputLength {
61                requested: 0,
62                max: usize::MAX,
63            });
64        }
65
66        let mut output = vec![0u8; length];
67        pbkdf2::pbkdf2_hmac::<Sha256>(password, salt, self.iterations, &mut output);
68        Ok(output)
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    /// RFC 6070 风格的公开测试向量(PBKDF2-HMAC-SHA256,c=1)。
77    #[test]
78    fn matches_known_vector() {
79        let kdf = Pbkdf2Sha256::new(1).unwrap();
80        let derived = kdf.derive_from_password(b"password", b"salt", 32).unwrap();
81        assert_eq!(
82            crate::internal::hex_encode(&derived),
83            "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"
84        );
85    }
86
87    #[test]
88    fn same_inputs_same_output_and_length_validation() {
89        let kdf = Pbkdf2Sha256::with_default_iterations();
90        assert_eq!(kdf.iterations(), 310_000);
91        let first = kdf.derive_from_password(b"pw", b"salt", 16).unwrap();
92        let second = kdf.derive_from_password(b"pw", b"salt", 16).unwrap();
93        assert_eq!(first, second);
94        assert_eq!(
95            kdf.derive_from_password(b"pw", b"salt", 0).unwrap_err(),
96            Error::InvalidOutputLength {
97                requested: 0,
98                max: usize::MAX
99            }
100        );
101    }
102
103    #[test]
104    fn zero_iterations_is_rejected() {
105        assert_eq!(Pbkdf2Sha256::new(0).unwrap_err(), Error::InvalidIterations);
106    }
107}