From 1451d8e6306de66707c640cf94be6e7d94993036 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 15 Oct 2025 18:16:18 -0600 Subject: [PATCH] sha-crypt: switch from `rand` to `rand_core` Replaces the use of `Distribution` by first filling a buffer with random bytes, then encoding it as Base64. It seems the Base64 encoding is directly consumed by the algorithm, or otherwise it would probably make sense to convert all usages of `salt` to be raw bytes. That warrants further investigation. --- Cargo.lock | 51 +------------------------------------------ sha-crypt/Cargo.toml | 6 ++--- sha-crypt/src/defs.rs | 4 ---- sha-crypt/src/lib.rs | 44 ++++++++++--------------------------- 4 files changed, 15 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12e72294..5c4cc698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,15 +334,6 @@ dependencies = [ "streebog", ] -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "proc-macro2" version = "1.0.101" @@ -367,26 +358,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core", -] - [[package]] name = "rand_core" version = "0.9.3" @@ -457,7 +428,7 @@ name = "sha-crypt" version = "0.6.0-pre.1" dependencies = [ "base64ct", - "rand", + "rand_core", "sha2", "subtle", ] @@ -617,26 +588,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "zerocopy" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zeroize" version = "1.8.2" diff --git a/sha-crypt/Cargo.toml b/sha-crypt/Cargo.toml index 291de482..21b1cfe2 100644 --- a/sha-crypt/Cargo.toml +++ b/sha-crypt/Cargo.toml @@ -18,15 +18,15 @@ rust-version = "1.85" [dependencies] sha2 = { version = "0.11.0-rc.2", default-features = false } +base64ct = { version = "1.7.1", default-features = false } # optional dependencies -rand = { version = "0.9", optional = true } +rand_core = { version = "0.9", optional = true, default-features = false, features = ["os_rng"] } subtle = { version = "2", optional = true, default-features = false } -base64ct = "1.7.1" [features] default = ["simple"] -simple = ["rand", "subtle"] +simple = ["base64ct/alloc", "dep:rand_core", "dep:subtle"] [package.metadata.docs.rs] all-features = true diff --git a/sha-crypt/src/defs.rs b/sha-crypt/src/defs.rs index 4865205a..51e2b9d6 100644 --- a/sha-crypt/src/defs.rs +++ b/sha-crypt/src/defs.rs @@ -14,10 +14,6 @@ pub const PW_SIZE_SHA512: usize = 86; #[cfg(feature = "simple")] pub const SALT_MAX_LEN: usize = 16; -/// Encoding table. -#[cfg(feature = "simple")] -pub static TAB: &[u8] = b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - /// Inverse encoding map for SHA512. #[rustfmt::skip] pub const MAP_SHA512: [u8; 64] = [ diff --git a/sha-crypt/src/lib.rs b/sha-crypt/src/lib.rs index abe624e2..8e32c25d 100644 --- a/sha-crypt/src/lib.rs +++ b/sha-crypt/src/lib.rs @@ -56,12 +56,10 @@ use sha2::{Digest, Sha256, Sha512}; #[cfg(feature = "simple")] use { - crate::{ - defs::{SALT_MAX_LEN, TAB}, - errors::CheckError, - }, + crate::{defs::SALT_MAX_LEN, errors::CheckError}, alloc::string::ToString, - rand::{Rng, distr::Distribution}, + base64ct::{Base64ShaCrypt, Encoding}, + rand_core::{OsRng, RngCore, TryRngCore}, }; #[cfg(feature = "simple")] @@ -317,13 +315,7 @@ pub fn sha256_crypt_b64(password: &[u8], salt: &[u8], params: &Sha256Params) -> /// [1]: https://www.akkadia.org/drepper/SHA-crypt.txt #[cfg(feature = "simple")] pub fn sha512_simple(password: &str, params: &Sha512Params) -> String { - let rng = rand::rng(); - - let salt: String = rng - .sample_iter(&ShaCryptDistribution) - .take(SALT_MAX_LEN) - .collect(); - + let salt = random_salt(); let out = sha512_crypt(password.as_bytes(), salt.as_bytes(), params); let mut result = String::new(); @@ -352,13 +344,7 @@ pub fn sha512_simple(password: &str, params: &Sha512Params) -> String { /// [1]: https://www.akkadia.org/drepper/SHA-crypt.txt #[cfg(feature = "simple")] pub fn sha256_simple(password: &str, params: &Sha256Params) -> String { - let rng = rand::rng(); - - let salt: String = rng - .sample_iter(&ShaCryptDistribution) - .take(SALT_MAX_LEN) - .collect(); - + let salt = random_salt(); let out = sha256_crypt(password.as_bytes(), salt.as_bytes(), params); let mut result = String::new(); @@ -528,21 +514,13 @@ pub fn sha256_check(password: &str, hashed_value: &str) -> Result<(), CheckError } } +/// Generate a random salt that is 16-bytes long. #[cfg(feature = "simple")] -#[derive(Debug)] -struct ShaCryptDistribution; - -#[cfg(feature = "simple")] -impl Distribution for ShaCryptDistribution { - fn sample(&self, rng: &mut R) -> char { - const RANGE: u32 = 26 + 26 + 10 + 2; // 2 == "./" - loop { - let var = rng.next_u32() >> (32 - 6); - if var < RANGE { - return TAB[var as usize] as char; - } - } - } +fn random_salt() -> String { + // Create buffer containing raw bytes to encode as Base64 + let mut buf = [0u8; (SALT_MAX_LEN * 3).div_ceil(4)]; + OsRng.unwrap_err().fill_bytes(&mut buf); + Base64ShaCrypt::encode_string(&buf) } fn produce_byte_seq(len: usize, fill_from: &[u8]) -> Vec {