Skip to content

Commit 2e77948

Browse files
Rust wrapper: implement password-hash traits
1 parent 6074a2d commit 2e77948

6 files changed

Lines changed: 559 additions & 1 deletion

File tree

wrapper/rust/wolfssl-wolfcrypt/Cargo.lock

Lines changed: 41 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wrapper/rust/wolfssl-wolfcrypt/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ aead = ["dep:aead"]
1717
cipher = ["dep:cipher"]
1818
digest = ["dep:digest"]
1919
signature = ["dep:signature"]
20+
password-hash = ["dep:password-hash", "password-hash/phc"]
2021

2122
[dependencies]
2223
rand_core = { version = "0.10", optional = true, default-features = false }
@@ -25,12 +26,14 @@ cipher = { version = "0.5", optional = true, default-features = false }
2526
digest = { version = "0.11", optional = true, default-features = false, features = ["block-api"] }
2627
signature = { version = "2.2", optional = true, default-features = false }
2728
zeroize = { version = "1.3", default-features = false, features = ["derive"] }
29+
password-hash = { version = "0.6.1", optional = true, default-features = false }
2830

2931
[dev-dependencies]
3032
aead = { version = "0.5", features = ["alloc", "dev"] }
3133
cipher = "0.5"
3234
digest = { version = "0.11", features = ["dev"] }
3335
signature = "2.2"
36+
password-hash = { version = "0.6.1", features = ["phc"] }
3437

3538
[build-dependencies]
3639
bindgen = "0.72.1"

wrapper/rust/wolfssl-wolfcrypt/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FEATURES := rand_core,aead,cipher,digest,signature
1+
FEATURES := rand_core,aead,cipher,digest,signature,password-hash
22
CARGO_FEATURE_FLAGS := --features $(FEATURES)
33

44
.PHONY: all

wrapper/rust/wolfssl-wolfcrypt/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ pub mod rsa;
6464
#[cfg(feature = "signature")]
6565
pub mod rsa_pkcs1v15;
6666
pub mod sha;
67+
#[cfg(feature = "password-hash")]
68+
pub mod pbkdf2_password_hash;
6769
#[cfg(feature = "digest")]
6870
pub mod sha_digest;
6971

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/*
2+
* Copyright (C) 2006-2026 wolfSSL Inc.
3+
*
4+
* This file is part of wolfSSL.
5+
*
6+
* wolfSSL is free software; you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation; either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* wolfSSL is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
19+
*/
20+
21+
/*!
22+
RustCrypto `password-hash` trait implementations for wolfCrypt PBKDF2.
23+
24+
This module provides [`Pbkdf2`], a type that implements the
25+
[`PasswordHasher`] and [`CustomizedPasswordHasher`] traits from the
26+
`password-hash` crate, backed by the wolfCrypt PBKDF2 implementation.
27+
The blanket [`PasswordVerifier`] implementation is also available,
28+
allowing verification of existing password hashes.
29+
30+
Password hashes are represented in the
31+
[PHC string format](https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md):
32+
33+
```text
34+
$pbkdf2-sha256$i=600000$<salt>$<hash>
35+
```
36+
37+
# Supported algorithms
38+
39+
| Algorithm ID | Hash function |
40+
|-----------------|---------------|
41+
| `pbkdf2-sha256` | HMAC-SHA-256 |
42+
| `pbkdf2-sha384` | HMAC-SHA-384 |
43+
| `pbkdf2-sha512` | HMAC-SHA-512 |
44+
45+
[`PasswordHasher`]: password_hash::PasswordHasher
46+
[`CustomizedPasswordHasher`]: password_hash::CustomizedPasswordHasher
47+
[`PasswordVerifier`]: password_hash::PasswordVerifier
48+
*/
49+
50+
#![cfg(all(feature = "password-hash", hmac, kdf_pbkdf2))]
51+
52+
use password_hash::phc::{Ident, Output, ParamsString, PasswordHash, Salt};
53+
use password_hash::{CustomizedPasswordHasher, Error, Result, Version};
54+
55+
use crate::hmac::HMAC;
56+
use crate::kdf;
57+
58+
const PBKDF2_SHA256_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha256");
59+
const PBKDF2_SHA384_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha384");
60+
const PBKDF2_SHA512_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha512");
61+
62+
/// Minimum number of PBKDF2 rounds.
63+
pub const MIN_ROUNDS: u32 = 1_000;
64+
65+
/// Default number of PBKDF2 rounds (OWASP recommendation for SHA-256).
66+
pub const DEFAULT_ROUNDS: u32 = 600_000;
67+
68+
/// Default output length in bytes.
69+
pub const DEFAULT_OUTPUT_LEN: usize = 32;
70+
71+
/// PBKDF2 algorithm variant.
72+
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
73+
pub enum Algorithm {
74+
/// PBKDF2 with HMAC-SHA-256.
75+
#[default]
76+
Pbkdf2Sha256,
77+
/// PBKDF2 with HMAC-SHA-384.
78+
Pbkdf2Sha384,
79+
/// PBKDF2 with HMAC-SHA-512.
80+
Pbkdf2Sha512,
81+
}
82+
83+
impl Algorithm {
84+
/// Get the PHC string format identifier for this algorithm.
85+
pub fn ident(self) -> Ident {
86+
match self {
87+
Algorithm::Pbkdf2Sha256 => PBKDF2_SHA256_IDENT,
88+
Algorithm::Pbkdf2Sha384 => PBKDF2_SHA384_IDENT,
89+
Algorithm::Pbkdf2Sha512 => PBKDF2_SHA512_IDENT,
90+
}
91+
}
92+
93+
fn hmac_type(self) -> i32 {
94+
match self {
95+
Algorithm::Pbkdf2Sha256 => HMAC::TYPE_SHA256,
96+
Algorithm::Pbkdf2Sha384 => HMAC::TYPE_SHA384,
97+
Algorithm::Pbkdf2Sha512 => HMAC::TYPE_SHA512,
98+
}
99+
}
100+
}
101+
102+
impl TryFrom<Ident> for Algorithm {
103+
type Error = Error;
104+
105+
fn try_from(ident: Ident) -> Result<Self> {
106+
if ident == PBKDF2_SHA256_IDENT {
107+
Ok(Algorithm::Pbkdf2Sha256)
108+
} else if ident == PBKDF2_SHA384_IDENT {
109+
Ok(Algorithm::Pbkdf2Sha384)
110+
} else if ident == PBKDF2_SHA512_IDENT {
111+
Ok(Algorithm::Pbkdf2Sha512)
112+
} else {
113+
Err(Error::Algorithm)
114+
}
115+
}
116+
}
117+
118+
/// PBKDF2 parameters.
119+
#[derive(Clone, Debug)]
120+
pub struct Params {
121+
/// Number of iterations (rounds).
122+
pub rounds: u32,
123+
/// Desired output hash length in bytes.
124+
pub output_len: usize,
125+
}
126+
127+
impl Default for Params {
128+
fn default() -> Self {
129+
Params {
130+
rounds: DEFAULT_ROUNDS,
131+
output_len: DEFAULT_OUTPUT_LEN,
132+
}
133+
}
134+
}
135+
136+
impl TryFrom<&PasswordHash> for Params {
137+
type Error = Error;
138+
139+
fn try_from(hash: &PasswordHash) -> Result<Self> {
140+
let rounds = hash
141+
.params
142+
.get_decimal("i")
143+
.ok_or(Error::ParamInvalid { name: "i" })?;
144+
145+
if rounds < MIN_ROUNDS {
146+
return Err(Error::ParamInvalid { name: "i" });
147+
}
148+
149+
let output_len = if let Some(ref h) = hash.hash {
150+
h.len()
151+
} else if let Some(l) = hash.params.get_decimal("l") {
152+
l as usize
153+
} else {
154+
return Err(Error::ParamInvalid { name: "l" });
155+
};
156+
157+
Ok(Params { rounds, output_len })
158+
}
159+
}
160+
161+
/// PBKDF2 password hasher backed by wolfCrypt.
162+
///
163+
/// Implements the [`PasswordHasher`](password_hash::PasswordHasher) and
164+
/// [`CustomizedPasswordHasher`] traits. A blanket
165+
/// [`PasswordVerifier`](password_hash::PasswordVerifier) implementation is
166+
/// provided by the `password-hash` crate.
167+
///
168+
/// # Example
169+
///
170+
/// ```rust
171+
/// #[cfg(all(hmac, kdf_pbkdf2))]
172+
/// {
173+
/// use password_hash::PasswordHasher;
174+
/// use wolfssl_wolfcrypt::pbkdf2_password_hash::Pbkdf2;
175+
///
176+
/// let hasher = Pbkdf2::default();
177+
/// let salt = b"0123456789abcdef"; // 16 bytes
178+
/// let hash = hasher.hash_password_with_salt(b"password", salt)
179+
/// .expect("hashing failed");
180+
/// }
181+
/// ```
182+
#[derive(Clone, Debug, Default)]
183+
pub struct Pbkdf2 {
184+
/// Algorithm to use for hashing.
185+
pub algorithm: Algorithm,
186+
/// Default parameters.
187+
pub params: Params,
188+
}
189+
190+
impl password_hash::PasswordHasher<PasswordHash> for Pbkdf2 {
191+
fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result<PasswordHash> {
192+
self.hash_password_customized(password, salt, None, None, self.params.clone())
193+
}
194+
}
195+
196+
impl password_hash::CustomizedPasswordHasher<PasswordHash> for Pbkdf2 {
197+
type Params = Params;
198+
199+
fn hash_password_customized(
200+
&self,
201+
password: &[u8],
202+
salt: &[u8],
203+
algorithm: Option<&str>,
204+
version: Option<Version>,
205+
params: Params,
206+
) -> Result<PasswordHash> {
207+
if version.is_some() {
208+
return Err(Error::Version);
209+
}
210+
211+
let algorithm = match algorithm {
212+
Some(s) => {
213+
let ident = Ident::new(s).map_err(|_| Error::Algorithm)?;
214+
Algorithm::try_from(ident)?
215+
}
216+
None => self.algorithm,
217+
};
218+
219+
if params.rounds < MIN_ROUNDS {
220+
return Err(Error::ParamInvalid { name: "i" });
221+
}
222+
223+
let iterations = i32::try_from(params.rounds)
224+
.map_err(|_| Error::ParamInvalid { name: "i" })?;
225+
226+
let salt = Salt::new(salt)?;
227+
228+
let mut out_buf = [0u8; Output::MAX_LENGTH];
229+
let out_slice = &mut out_buf[..params.output_len];
230+
kdf::pbkdf2(password, salt.as_ref(), iterations, algorithm.hmac_type(), out_slice)
231+
.map_err(|_| Error::Crypto)?;
232+
let output = Output::new(out_slice)?;
233+
234+
let mut phc_params = ParamsString::new();
235+
phc_params.add_decimal("i", params.rounds)?;
236+
237+
Ok(PasswordHash {
238+
algorithm: algorithm.ident(),
239+
version: None,
240+
params: phc_params,
241+
salt: Some(salt),
242+
hash: Some(output),
243+
})
244+
}
245+
}

0 commit comments

Comments
 (0)