From 414263200ca80e9c3af43c0b1cdef2a9edef1e81 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 11 Jan 2026 21:43:50 -0700 Subject: [PATCH] pbkdf2: use `Base64::Pbkdf2` Use support for PBKDF2's special Base64 alphabet now natively implemented in the `base64ct` and `mcf` crates in RustCrypto/formats#2168. --- Cargo.lock | 7 +++---- Cargo.toml | 2 ++ pbkdf2/src/lib.rs | 3 --- pbkdf2/src/mcf.rs | 46 ++++++++++++++-------------------------------- 4 files changed, 19 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ca2c6c1..828c71a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,9 +36,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bcrypt-pbkdf" @@ -283,8 +283,7 @@ checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "mcf" version = "0.6.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872721b0f376d4b3b2f4064d69a52aff03e7cddb1fd32c5cb0517edf08faff5e" +source = "git+https://github.com/RustCrypto/formats?branch=base64ct%2Fpbkdf2-alphabet#a2b37fd6975e49ecdee08d640d6258f4b0f488c7" dependencies = [ "base64ct", ] diff --git a/Cargo.toml b/Cargo.toml index 550af354..4c9e55fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,5 @@ opt-level = 2 argon2 = { path = "./argon2" } pbkdf2 = { path = "./pbkdf2" } scrypt = { path = "./scrypt" } + +mcf = { git = "https://github.com/RustCrypto/formats", branch = "base64ct/pbkdf2-alphabet" } diff --git a/pbkdf2/src/lib.rs b/pbkdf2/src/lib.rs index b04e1f9d..2ec3ac8e 100644 --- a/pbkdf2/src/lib.rs +++ b/pbkdf2/src/lib.rs @@ -93,9 +93,6 @@ //! # } //! ``` -#[cfg(feature = "mcf")] -extern crate alloc; - #[cfg(feature = "mcf")] pub mod mcf; #[cfg(feature = "phc")] diff --git a/pbkdf2/src/mcf.rs b/pbkdf2/src/mcf.rs index 7304a441..b6b4e9c1 100644 --- a/pbkdf2/src/mcf.rs +++ b/pbkdf2/src/mcf.rs @@ -10,7 +10,6 @@ pub use mcf::{PasswordHash, PasswordHashRef}; use crate::{Algorithm, Params, Pbkdf2, pbkdf2_hmac}; -use alloc::{string::String, vec::Vec}; use mcf::Base64; use password_hash::{ CustomizedPasswordHasher, Error, PasswordHasher, PasswordVerifier, Result, Version, @@ -55,19 +54,11 @@ impl CustomizedPasswordHasher for Pbkdf2 { f(password, salt, params.rounds(), out); let mut mcf_hash = PasswordHash::from_id(algorithm.to_str()).expect("should have valid ID"); - mcf_hash .push_displayable(params) .map_err(|_| Error::EncodingInvalid)?; - - mcf_hash - .push_str(&base64_encode(salt)) - .map_err(|_| Error::EncodingInvalid)?; - - mcf_hash - .push_str(&base64_encode(out)) - .map_err(|_| Error::EncodingInvalid)?; - + mcf_hash.push_base64(salt, Base64::Pbkdf2); + mcf_hash.push_base64(out, Base64::Pbkdf2); Ok(mcf_hash) } } @@ -97,13 +88,17 @@ impl PasswordVerifier for Pbkdf2 { next = fields.next().ok_or(Error::EncodingInvalid)?; } - let salt = base64_decode(next.as_str())?; + // decode salt + let salt = next + .decode_base64(Base64::Pbkdf2) + .map_err(|_| Error::EncodingInvalid)?; // decode expected password hash let expected = fields .next() - .ok_or(Error::EncodingInvalid) - .and_then(|field| base64_decode(field.as_str()))?; + .ok_or(Error::EncodingInvalid)? + .decode_base64(Base64::Pbkdf2) + .map_err(|_| Error::EncodingInvalid)?; // should be the last field if fields.next().is_some() { @@ -136,25 +131,12 @@ impl PasswordVerifier for Pbkdf2 { } } -// Base64 support: PBKDF2 uses a variant of standard unpadded Base64 which substitutes the `+` -// character for `.` and this is a distinct encoding from the bcrypt and crypt Base64 variants. - -fn base64_decode(base64: &str) -> Result> { - Base64::B64 - .decode_vec(&base64.replace('.', "+")) - .map_err(|_| Error::EncodingInvalid) -} - -fn base64_encode(bytes: &[u8]) -> String { - Base64::B64.encode_string(bytes).replace('+', ".") -} - -// TODO(tarcieri): tests for SHA-1 and SHA-512 +// TODO(tarcieri): tests for SHA-1 #[cfg(test)] mod tests { - use super::{Error, base64_decode}; + use super::Error; use crate::{Params, Pbkdf2}; - use mcf::PasswordHash; + use mcf::{Base64, PasswordHash}; use password_hash::{CustomizedPasswordHasher, PasswordVerifier}; // Example adapted from: @@ -167,7 +149,7 @@ mod tests { const EXAMPLE_HASH: &str = "$pbkdf2-sha256$8000$XAuBMIYQQogxRg$tRRlz8hYn63B9LYiCd6PRo6FMiunY9ozmMMI3srxeRE"; - let salt = base64_decode(EXAMPLE_SALT).unwrap(); + let salt = Base64::Pbkdf2.decode_vec(EXAMPLE_SALT).unwrap(); let params = Params::new(EXAMPLE_ROUNDS).unwrap(); let actual_hash: PasswordHash = Pbkdf2::SHA256 @@ -197,7 +179,7 @@ mod tests { const EXAMPLE_SALT: &str = "O4fwPmdMyRmDUIrx/h9jTA"; const EXAMPLE_HASH: &str = "$pbkdf2-sha512$25000$O4fwPmdMyRmDUIrx/h9jTA$Xlp267ZwEbG4aOpN3Bve/ATo3rFA7WH8iMdS16Xbe9rc6P5welk1yiXEMPy7.BFp0qsncipHumaW1trCWVvq/A"; - let salt = base64_decode(EXAMPLE_SALT).unwrap(); + let salt = Base64::Pbkdf2.decode_vec(EXAMPLE_SALT).unwrap(); let params = Params::new_with_output_len(EXAMPLE_ROUNDS, 64).unwrap(); let actual_hash: PasswordHash = Pbkdf2::SHA512