Skip to main content

crypto_pbkdf2/
types.rs

1// SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use crypto_core::{CryptoError, KdfAlgorithm, KdfFailureKind, KdfProfile};
6use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
7
8use crate::constants::{
9    PBKDF2_MAX_ITERATIONS, PBKDF2_MAX_PASSWORD_LENGTH, PBKDF2_MAX_SALT_LENGTH,
10    PBKDF2_MIN_PASSWORD_LENGTH, PBKDF2_MIN_SALT_LENGTH, PBKDF2_MODERN_MIN_ITERATIONS,
11    PBKDF2_STANDARD_MIN_ITERATIONS,
12};
13#[cfg(any(feature = "native", feature = "wasm"))]
14use crate::constants::{PBKDF2_MAX_OUTPUT_LENGTH, PBKDF2_MIN_OUTPUT_LENGTH};
15
16/// PRF used by PBKDF2.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Pbkdf2Prf {
19    /// PBKDF2 with HMAC-SHA-256.
20    HmacSha256,
21    /// PBKDF2 with HMAC-SHA-512.
22    HmacSha512,
23}
24
25impl Pbkdf2Prf {
26    pub(crate) fn profile(self) -> KdfProfile {
27        match self {
28            Pbkdf2Prf::HmacSha256 => KdfProfile::Pbkdf2HmacSha256,
29            Pbkdf2Prf::HmacSha512 => KdfProfile::Pbkdf2HmacSha512,
30        }
31    }
32}
33
34/// Password/secret input to PBKDF2.
35#[derive(Zeroize, ZeroizeOnDrop)]
36pub struct Pbkdf2Password {
37    bytes: Zeroizing<Vec<u8>>,
38}
39
40impl Pbkdf2Password {
41    /// Constructs a PBKDF2 password input from raw bytes.
42    pub fn from_slice(input: &[u8], prf: Pbkdf2Prf) -> Result<Self, CryptoError> {
43        if !(PBKDF2_MIN_PASSWORD_LENGTH..=PBKDF2_MAX_PASSWORD_LENGTH).contains(&input.len()) {
44            return Err(kdf_error(prf, KdfFailureKind::InvalidSecretLength));
45        }
46
47        Ok(Self {
48            bytes: Zeroizing::new(input.to_vec()),
49        })
50    }
51
52    /// Borrows the password bytes.
53    pub fn as_bytes(&self) -> &[u8] {
54        &self.bytes
55    }
56}
57
58/// Salt input to PBKDF2.
59#[derive(Zeroize, ZeroizeOnDrop)]
60pub struct Pbkdf2Salt {
61    bytes: Zeroizing<Vec<u8>>,
62}
63
64impl Pbkdf2Salt {
65    /// Constructs a PBKDF2 salt from raw bytes.
66    pub fn from_slice(input: &[u8], prf: Pbkdf2Prf) -> Result<Self, CryptoError> {
67        if !(PBKDF2_MIN_SALT_LENGTH..=PBKDF2_MAX_SALT_LENGTH).contains(&input.len()) {
68            return Err(kdf_error(prf, KdfFailureKind::InvalidSaltLength));
69        }
70
71        Ok(Self {
72            bytes: Zeroizing::new(input.to_vec()),
73        })
74    }
75
76    /// Borrows the salt bytes.
77    pub fn as_bytes(&self) -> &[u8] {
78        &self.bytes
79    }
80}
81
82/// PBKDF2 iteration count.
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub struct Pbkdf2Iterations {
85    value: u32,
86}
87
88impl Pbkdf2Iterations {
89    /// Constructs an iteration count.
90    pub fn from_u32(value: u32, prf: Pbkdf2Prf) -> Result<Self, CryptoError> {
91        if !(PBKDF2_STANDARD_MIN_ITERATIONS..=PBKDF2_MAX_ITERATIONS).contains(&value) {
92            return Err(kdf_error(prf, KdfFailureKind::InvalidIterationCount));
93        }
94        Ok(Self { value })
95    }
96
97    /// Constructs an iteration count for a new public protocol profile.
98    ///
99    /// The standards-level constructor remains available for conformance
100    /// vectors. Public facades use this constructor so a below-policy work
101    /// factor cannot be selected accidentally.
102    pub fn from_u32_modern(value: u32, prf: Pbkdf2Prf) -> Result<Self, CryptoError> {
103        if !(PBKDF2_MODERN_MIN_ITERATIONS..=PBKDF2_MAX_ITERATIONS).contains(&value) {
104            return Err(kdf_error(prf, KdfFailureKind::InvalidIterationCount));
105        }
106        Ok(Self { value })
107    }
108
109    /// Returns the raw iteration count.
110    pub fn as_u32(self) -> u32 {
111        self.value
112    }
113}
114
115/// Derived PBKDF2 output keying material.
116pub struct Pbkdf2Output {
117    bytes: Zeroizing<Vec<u8>>,
118}
119
120impl Zeroize for Pbkdf2Output {
121    fn zeroize(&mut self) {
122        self.bytes.zeroize();
123    }
124}
125
126// The inner owner already wipes the allocation on drop. Keeping this wrapper
127// free of its own `Drop` implementation permits a no-copy ownership transfer.
128impl ZeroizeOnDrop for Pbkdf2Output {}
129
130impl Pbkdf2Output {
131    #[cfg(any(feature = "native", feature = "wasm"))]
132    pub(crate) fn from_zeroizing(bytes: Zeroizing<Vec<u8>>) -> Self {
133        Self { bytes }
134    }
135
136    /// Borrows the derived bytes.
137    pub fn as_bytes(&self) -> &[u8] {
138        &self.bytes
139    }
140
141    /// Returns the derived byte length.
142    pub fn len(&self) -> usize {
143        self.bytes.len()
144    }
145
146    /// Returns whether this output is empty.
147    pub fn is_empty(&self) -> bool {
148        self.bytes.is_empty()
149    }
150
151    /// Consumes the output and returns an owned zeroizing buffer.
152    pub fn into_zeroizing(self) -> Zeroizing<Vec<u8>> {
153        self.bytes
154    }
155}
156
157impl core::fmt::Debug for Pbkdf2Output {
158    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
159        write!(f, "Pbkdf2Output(len={})", self.len())
160    }
161}
162
163#[cfg(any(feature = "native", feature = "wasm"))]
164pub(crate) fn validate_output_len(len: usize, prf: Pbkdf2Prf) -> Result<(), CryptoError> {
165    if !(PBKDF2_MIN_OUTPUT_LENGTH..=PBKDF2_MAX_OUTPUT_LENGTH).contains(&len) {
166        return Err(kdf_error(prf, KdfFailureKind::InvalidOutputLength));
167    }
168    Ok(())
169}
170
171pub(crate) fn kdf_error(prf: Pbkdf2Prf, kind: KdfFailureKind) -> CryptoError {
172    CryptoError::Kdf {
173        algorithm: KdfAlgorithm::Pbkdf2,
174        profile: prf.profile(),
175        kind,
176    }
177}