From 3c1efa2a70fb824572b2d71069af669e25e0ea46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Tue, 26 Mar 2024 15:34:58 +0100 Subject: [PATCH 1/2] feat: Enable partial and full randomised states for WyHash --- Cargo.toml | 3 ++- README.md | 1 + src/hasher/builder.rs | 57 ++++++++++++++++++++++++------------------- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 09cf9bf..b759d2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wyrand" -version = "0.1.5" +version = "0.1.6" edition = "2021" authors = ["Gonçalo Rica Pais da Silva "] description = "A fast & portable non-cryptographic pseudorandom number generator and hashing algorithm" @@ -22,6 +22,7 @@ rand_core = ["dep:rand_core"] serde1 = ["dep:serde"] wyhash = [] randomised_wyhash = ["wyhash", "dep:getrandom"] +fully_randomised_wyhash = ["randomised_wyhash"] v4_2 = [] [dependencies] diff --git a/README.md b/README.md index 8f2f535..c2de2f6 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ The crate will always export `WyRand` and will do so when set as `default-featur - **`hash`** - Enables `core::hash::Hash` implementation for `WyRand`. - **`wyhash`** - Enables `WyHash`, a fast & portable hashing algorithm. Based on the final v4 C implementation. - **`randomised_wyhash`** - Enables `RandomisedWyHashBuilder`, a means to source a randomised state for `WyHash` for use in collections like `HashMap`/`HashSet`. Enables `wyhash` feature if it is not already enabled. +- **`fully_randomised_wyhash`** - Randomises not just the seed for `RandomisedWyHashBuilder`, but also the secret. Incurs a performance hit every time `WyHash` is initialised but it is more secure as a result. Enables `randomised_wyhash` if not already enabled. - **`v4_2`** - Switches the PRNG/Hashing algorithms to use the final v4.2 implementation. ## Building for WASM/Web diff --git a/src/hasher/builder.rs b/src/hasher/builder.rs index b0c9cf4..95999c8 100644 --- a/src/hasher/builder.rs +++ b/src/hasher/builder.rs @@ -1,19 +1,23 @@ -use core::{hash::BuildHasher, mem::MaybeUninit}; +use core::hash::BuildHasher; #[cfg(feature = "debug")] use core::fmt::Debug; -use getrandom::getrandom_uninit; - use crate::WyHash; #[cfg_attr(docsrs, doc(cfg(feature = "randomised_wyhash")))] #[derive(Clone, Copy)] +#[repr(align(8))] /// Randomised state constructor for [`WyHash`]. This builder will source entropy in order /// to provide random seeds for [`WyHash`]. This will yield a hasher with not just a random /// seed, but also a new random secret, granting extra protection against DOS and prediction /// attacks. -pub struct RandomWyHashState(u64, u64); +pub struct RandomWyHashState { + #[cfg(feature = "fully_randomised_wyhash")] + state: [u8; 16], + #[cfg(not(feature = "fully_randomised_wyhash"))] + state: [u8; 8], +} impl RandomWyHashState { /// Create a new [`RandomWyHashState`] instance. Calling this method will attempt to @@ -35,21 +39,17 @@ impl RandomWyHashState { /// ``` #[must_use] pub fn new() -> Self { - // Don't bother zeroing as we will initialise this with random data. If the initialisation fails - // for any reason, we will panic instead of trying to continue with a fully or partially - // uninitialised buffer. This ensures the whole process is safe without the need to use an - // unsafe block. - let mut bytes = [MaybeUninit::::uninit(); core::mem::size_of::() * 2]; - - let bytes = getrandom_uninit(&mut bytes) - .expect("Failed to source entropy for WyHash randomised state"); + #[cfg(feature = "fully_randomised_wyhash")] + const SIZE: usize = core::mem::size_of::() * 2; + #[cfg(not(feature = "fully_randomised_wyhash"))] + const SIZE: usize = core::mem::size_of::(); - let (first, second) = bytes.split_at(core::mem::size_of::()); + let mut state = [0; SIZE]; - let first = u64::from_ne_bytes(first.try_into().unwrap()); - let second = u64::from_ne_bytes(second.try_into().unwrap()); + getrandom::getrandom(&mut state) + .expect("Failed to source entropy for WyHash randomised state"); - Self(first, second) + Self { state } } } @@ -58,7 +58,21 @@ impl BuildHasher for RandomWyHashState { #[inline] fn build_hasher(&self) -> Self::Hasher { - WyHash::new(self.0, self.1) + #[cfg(feature = "fully_randomised_wyhash")] + { + let (first_seed, second_seed) = self.state.split_at(core::mem::size_of::()); + + let first_seed = u64::from_ne_bytes(first_seed.try_into().unwrap()); + let second_seed = u64::from_ne_bytes(second_seed.try_into().unwrap()); + + WyHash::new(first_seed, second_seed) + } + #[cfg(not(feature = "fully_randomised_wyhash"))] + { + let seed = u64::from_ne_bytes(self.state); + + WyHash::new_with_default_secret(seed) + } } } @@ -103,13 +117,6 @@ mod tests { let builder2 = RandomWyHashState::new(); // The two builders' internal states are different to each other - assert_ne!(&builder1.0, &builder2.0); - assert_ne!(&builder1.1, &builder2.1); - - // Each builder's internal state should not be the same (hopefully). - // It is more likely that we have not initialised things correctly than - // to have the entropy source output the same bits for both fields. - assert_ne!(&builder1.0, &builder1.1); - assert_ne!(&builder2.0, &builder2.1); + assert_ne!(&builder1.state, &builder2.state); } } From 58a920b41cadf370e46953cf9ae0f3b5bf8703e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Tue, 26 Mar 2024 16:08:15 +0100 Subject: [PATCH 2/2] chore: Add initialisation benchmarks --- benches/rand_bench.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/benches/rand_bench.rs b/benches/rand_bench.rs index 0164f5a..45aada7 100644 --- a/benches/rand_bench.rs +++ b/benches/rand_bench.rs @@ -66,6 +66,24 @@ fn wyhash_benchmark(c: &mut Criterion) { }, ); }); + + c.bench_function("Hash new with default secret", |b| { + b.iter(|| WyHash::new_with_default_secret(black_box(42))); + }); + + c.bench_function("Hash new with random secret", |b| { + b.iter(|| WyHash::new(black_box(42), black_box(256))); + }); + + #[cfg(feature = "randomised_wyhash")] + c.bench_function("Random Hash new", |b| { + use wyrand::RandomWyHashState; + use std::hash::BuildHasher; + + b.iter(|| { + RandomWyHashState::new().build_hasher() + }); + }); } pub fn benches() {