mirror of
https://github.com/RustCrypto/password-hashes.git
synced 2026-01-25 04:06:23 +00:00
argon2: add parallelism (#547)
Some checks failed
Security Audit / Security Audit (push) Has been cancelled
argon2 / build (1.85.0, thumbv7em-none-eabi) (push) Has been cancelled
argon2 / build (1.85.0, wasm32-unknown-unknown) (push) Has been cancelled
argon2 / build (stable, thumbv7em-none-eabi) (push) Has been cancelled
argon2 / build (stable, wasm32-unknown-unknown) (push) Has been cancelled
argon2 / minimal-versions (push) Has been cancelled
argon2 / test (1.85.0, x86_64-unknown-linux-gnu) (push) Has been cancelled
argon2 / test (stable, x86_64-unknown-linux-gnu) (push) Has been cancelled
argon2 / test (sudo apt update && sudo apt install gcc-multilib, 1.85.0, i686-unknown-linux-gnu) (push) Has been cancelled
argon2 / test (sudo apt update && sudo apt install gcc-multilib, stable, i686-unknown-linux-gnu) (push) Has been cancelled
argon2 / careful (push) Has been cancelled
argon2 / cross (1.85.0, powerpc-unknown-linux-gnu) (push) Has been cancelled
argon2 / cross (stable, powerpc-unknown-linux-gnu) (push) Has been cancelled
balloon-hash / build (1.85.0, thumbv7em-none-eabi) (push) Has been cancelled
balloon-hash / build (1.85.0, wasm32-unknown-unknown) (push) Has been cancelled
balloon-hash / build (stable, thumbv7em-none-eabi) (push) Has been cancelled
balloon-hash / build (stable, wasm32-unknown-unknown) (push) Has been cancelled
balloon-hash / minimal-versions (push) Has been cancelled
balloon-hash / test (1.85.0) (push) Has been cancelled
balloon-hash / test (stable) (push) Has been cancelled
bcrypt-pbkdf / build (1.85.0, thumbv7em-none-eabi) (push) Has been cancelled
bcrypt-pbkdf / build (1.85.0, wasm32-unknown-unknown) (push) Has been cancelled
bcrypt-pbkdf / build (stable, thumbv7em-none-eabi) (push) Has been cancelled
bcrypt-pbkdf / build (stable, wasm32-unknown-unknown) (push) Has been cancelled
bcrypt-pbkdf / minimal-versions (push) Has been cancelled
bcrypt-pbkdf / test (1.85.0) (push) Has been cancelled
bcrypt-pbkdf / test (stable) (push) Has been cancelled
fuzz-build / build (push) Has been cancelled
password-auth / test (1.85.0) (push) Has been cancelled
password-auth / test (stable) (push) Has been cancelled
password-auth / wasm (1.85.0) (push) Has been cancelled
password-auth / wasm (stable) (push) Has been cancelled
pbkdf2 / build (1.85.0, thumbv7em-none-eabi) (push) Has been cancelled
pbkdf2 / build (1.85.0, wasm32-unknown-unknown) (push) Has been cancelled
pbkdf2 / build (stable, thumbv7em-none-eabi) (push) Has been cancelled
pbkdf2 / build (stable, wasm32-unknown-unknown) (push) Has been cancelled
pbkdf2 / minimal-versions (push) Has been cancelled
pbkdf2 / test (1.85.0) (push) Has been cancelled
pbkdf2 / test (stable) (push) Has been cancelled
readme / test (push) Has been cancelled
scrypt / build (1.85.0, thumbv7em-none-eabi) (push) Has been cancelled
scrypt / build (1.85.0, wasm32-unknown-unknown) (push) Has been cancelled
scrypt / build (stable, thumbv7em-none-eabi) (push) Has been cancelled
scrypt / build (stable, wasm32-unknown-unknown) (push) Has been cancelled
scrypt / minimal-versions (push) Has been cancelled
scrypt / test (1.85.0) (push) Has been cancelled
scrypt / test (stable) (push) Has been cancelled
sha-crypt / build (1.85.0, thumbv7em-none-eabi) (push) Has been cancelled
sha-crypt / build (1.85.0, wasm32-unknown-unknown) (push) Has been cancelled
sha-crypt / build (stable, thumbv7em-none-eabi) (push) Has been cancelled
sha-crypt / build (stable, wasm32-unknown-unknown) (push) Has been cancelled
sha-crypt / minimal-versions (push) Has been cancelled
sha-crypt / test (1.85.0) (push) Has been cancelled
sha-crypt / test (stable) (push) Has been cancelled
Workspace / clippy (push) Has been cancelled
Workspace / rustfmt (push) Has been cancelled
Workspace / typos (push) Has been cancelled
yescrypt / minimal-versions (push) Has been cancelled
yescrypt / test (1.85.0, x86_64-unknown-linux-gnu) (push) Has been cancelled
yescrypt / test (stable, x86_64-unknown-linux-gnu) (push) Has been cancelled
yescrypt / test (sudo apt update && sudo apt install gcc-multilib, 1.85.0, i686-unknown-linux-gnu) (push) Has been cancelled
yescrypt / test (sudo apt update && sudo apt install gcc-multilib, stable, i686-unknown-linux-gnu) (push) Has been cancelled
Some checks failed
Security Audit / Security Audit (push) Has been cancelled
argon2 / build (1.85.0, thumbv7em-none-eabi) (push) Has been cancelled
argon2 / build (1.85.0, wasm32-unknown-unknown) (push) Has been cancelled
argon2 / build (stable, thumbv7em-none-eabi) (push) Has been cancelled
argon2 / build (stable, wasm32-unknown-unknown) (push) Has been cancelled
argon2 / minimal-versions (push) Has been cancelled
argon2 / test (1.85.0, x86_64-unknown-linux-gnu) (push) Has been cancelled
argon2 / test (stable, x86_64-unknown-linux-gnu) (push) Has been cancelled
argon2 / test (sudo apt update && sudo apt install gcc-multilib, 1.85.0, i686-unknown-linux-gnu) (push) Has been cancelled
argon2 / test (sudo apt update && sudo apt install gcc-multilib, stable, i686-unknown-linux-gnu) (push) Has been cancelled
argon2 / careful (push) Has been cancelled
argon2 / cross (1.85.0, powerpc-unknown-linux-gnu) (push) Has been cancelled
argon2 / cross (stable, powerpc-unknown-linux-gnu) (push) Has been cancelled
balloon-hash / build (1.85.0, thumbv7em-none-eabi) (push) Has been cancelled
balloon-hash / build (1.85.0, wasm32-unknown-unknown) (push) Has been cancelled
balloon-hash / build (stable, thumbv7em-none-eabi) (push) Has been cancelled
balloon-hash / build (stable, wasm32-unknown-unknown) (push) Has been cancelled
balloon-hash / minimal-versions (push) Has been cancelled
balloon-hash / test (1.85.0) (push) Has been cancelled
balloon-hash / test (stable) (push) Has been cancelled
bcrypt-pbkdf / build (1.85.0, thumbv7em-none-eabi) (push) Has been cancelled
bcrypt-pbkdf / build (1.85.0, wasm32-unknown-unknown) (push) Has been cancelled
bcrypt-pbkdf / build (stable, thumbv7em-none-eabi) (push) Has been cancelled
bcrypt-pbkdf / build (stable, wasm32-unknown-unknown) (push) Has been cancelled
bcrypt-pbkdf / minimal-versions (push) Has been cancelled
bcrypt-pbkdf / test (1.85.0) (push) Has been cancelled
bcrypt-pbkdf / test (stable) (push) Has been cancelled
fuzz-build / build (push) Has been cancelled
password-auth / test (1.85.0) (push) Has been cancelled
password-auth / test (stable) (push) Has been cancelled
password-auth / wasm (1.85.0) (push) Has been cancelled
password-auth / wasm (stable) (push) Has been cancelled
pbkdf2 / build (1.85.0, thumbv7em-none-eabi) (push) Has been cancelled
pbkdf2 / build (1.85.0, wasm32-unknown-unknown) (push) Has been cancelled
pbkdf2 / build (stable, thumbv7em-none-eabi) (push) Has been cancelled
pbkdf2 / build (stable, wasm32-unknown-unknown) (push) Has been cancelled
pbkdf2 / minimal-versions (push) Has been cancelled
pbkdf2 / test (1.85.0) (push) Has been cancelled
pbkdf2 / test (stable) (push) Has been cancelled
readme / test (push) Has been cancelled
scrypt / build (1.85.0, thumbv7em-none-eabi) (push) Has been cancelled
scrypt / build (1.85.0, wasm32-unknown-unknown) (push) Has been cancelled
scrypt / build (stable, thumbv7em-none-eabi) (push) Has been cancelled
scrypt / build (stable, wasm32-unknown-unknown) (push) Has been cancelled
scrypt / minimal-versions (push) Has been cancelled
scrypt / test (1.85.0) (push) Has been cancelled
scrypt / test (stable) (push) Has been cancelled
sha-crypt / build (1.85.0, thumbv7em-none-eabi) (push) Has been cancelled
sha-crypt / build (1.85.0, wasm32-unknown-unknown) (push) Has been cancelled
sha-crypt / build (stable, thumbv7em-none-eabi) (push) Has been cancelled
sha-crypt / build (stable, wasm32-unknown-unknown) (push) Has been cancelled
sha-crypt / minimal-versions (push) Has been cancelled
sha-crypt / test (1.85.0) (push) Has been cancelled
sha-crypt / test (stable) (push) Has been cancelled
Workspace / clippy (push) Has been cancelled
Workspace / rustfmt (push) Has been cancelled
Workspace / typos (push) Has been cancelled
yescrypt / minimal-versions (push) Has been cancelled
yescrypt / test (1.85.0, x86_64-unknown-linux-gnu) (push) Has been cancelled
yescrypt / test (stable, x86_64-unknown-linux-gnu) (push) Has been cancelled
yescrypt / test (sudo apt update && sudo apt install gcc-multilib, 1.85.0, i686-unknown-linux-gnu) (push) Has been cancelled
yescrypt / test (sudo apt update && sudo apt install gcc-multilib, stable, i686-unknown-linux-gnu) (push) Has been cancelled
Adds a `parallel` feature, with an optional dependency on `rayon`, and parallelizes the filling of blocks. Coordinated shared access in the memory blocks is implemented with a `SegmentViewIter` iterator, which implements either `rayon::iter::ParallelIterator` or `core::iter::Iterator` and returns `SegmentView` views into the Argon2 blocks memory that are safe to be used in parallel. The views alias in the regions that are read-only, but are disjoint in the regions where mutation happens. Effectively, they implement, with a combination of mutable borrowing and runtime checking, the cooperative contract outlined in RFC 9106. This is similar to what was suggested in #380. To avoid aliasing mutable references into the entire buffer of blocks (which would be UB), pointers are used up to the moment where a reference (shared or mutable) into a specific block is returned. At that point, aliasing is no longer possible. The following tests have been tried in and pass Miri (modulo unrelated warnings): reference_argon2i_v0x13_2_8_2 reference_argon2id_v0x13_2_8_2
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -11,6 +11,7 @@ dependencies = [
|
|||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
"password-hash",
|
"password-hash",
|
||||||
|
"rayon",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ base64ct = "1.7"
|
|||||||
blake2 = { version = "0.11.0-rc.0", default-features = false }
|
blake2 = { version = "0.11.0-rc.0", default-features = false }
|
||||||
|
|
||||||
# optional dependencies
|
# optional dependencies
|
||||||
|
rayon = { version = "1.7", optional = true }
|
||||||
password-hash = { version = "0.6.0-rc.1", optional = true }
|
password-hash = { version = "0.6.0-rc.1", optional = true }
|
||||||
zeroize = { version = "1", default-features = false, optional = true }
|
zeroize = { version = "1", default-features = false, optional = true }
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ default = ["alloc", "password-hash", "rand"]
|
|||||||
alloc = ["password-hash?/alloc"]
|
alloc = ["password-hash?/alloc"]
|
||||||
std = ["alloc", "password-hash?/os_rng", "base64ct/std"]
|
std = ["alloc", "password-hash?/os_rng", "base64ct/std"]
|
||||||
|
|
||||||
|
parallel = ["dep:rayon"]
|
||||||
rand = ["password-hash?/rand_core"]
|
rand = ["password-hash?/rand_core"]
|
||||||
simple = ["password-hash"]
|
simple = ["password-hash"]
|
||||||
zeroize = ["dep:zeroize"]
|
zeroize = ["dep:zeroize"]
|
||||||
|
|||||||
@@ -13,8 +13,10 @@
|
|||||||
clippy::cast_sign_loss,
|
clippy::cast_sign_loss,
|
||||||
clippy::checked_conversions,
|
clippy::checked_conversions,
|
||||||
clippy::implicit_saturating_sub,
|
clippy::implicit_saturating_sub,
|
||||||
|
clippy::missing_safety_doc,
|
||||||
clippy::panic,
|
clippy::panic,
|
||||||
clippy::panic_in_result_fn,
|
clippy::panic_in_result_fn,
|
||||||
|
clippy::undocumented_unsafe_blocks,
|
||||||
clippy::unwrap_used,
|
clippy::unwrap_used,
|
||||||
missing_docs,
|
missing_docs,
|
||||||
rust_2018_idioms,
|
rust_2018_idioms,
|
||||||
@@ -153,6 +155,7 @@ mod algorithm;
|
|||||||
mod blake2b_long;
|
mod blake2b_long;
|
||||||
mod block;
|
mod block;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod memory;
|
||||||
mod params;
|
mod params;
|
||||||
mod version;
|
mod version;
|
||||||
|
|
||||||
@@ -173,6 +176,7 @@ pub use {
|
|||||||
use crate::blake2b_long::blake2b_long;
|
use crate::blake2b_long::blake2b_long;
|
||||||
use blake2::{Blake2b512, Digest, digest};
|
use blake2::{Blake2b512, Digest, digest};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
use memory::Memory;
|
||||||
|
|
||||||
#[cfg(all(feature = "alloc", feature = "password-hash"))]
|
#[cfg(all(feature = "alloc", feature = "password-hash"))]
|
||||||
use password_hash::{Decimal, Ident, ParamsString, Salt};
|
use password_hash::{Decimal, Ident, ParamsString, Salt};
|
||||||
@@ -347,7 +351,7 @@ impl<'key> Argon2<'key> {
|
|||||||
mut initial_hash: digest::Output<Blake2b512>,
|
mut initial_hash: digest::Output<Blake2b512>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let block_count = self.params.block_count();
|
let block_count = self.params.block_count();
|
||||||
let memory_blocks = memory_blocks
|
let mut memory_blocks = memory_blocks
|
||||||
.get_mut(..block_count)
|
.get_mut(..block_count)
|
||||||
.ok_or(Error::MemoryTooLittle)?;
|
.ok_or(Error::MemoryTooLittle)?;
|
||||||
|
|
||||||
@@ -381,31 +385,59 @@ impl<'key> Argon2<'key> {
|
|||||||
|
|
||||||
// Run passes on blocks
|
// Run passes on blocks
|
||||||
for pass in 0..iterations {
|
for pass in 0..iterations {
|
||||||
for slice in 0..SYNC_POINTS {
|
memory_blocks.for_each_segment(lanes, |mut memory_view, slice, lane| {
|
||||||
let data_independent_addressing = self.algorithm == Algorithm::Argon2i
|
let data_independent_addressing = self.algorithm == Algorithm::Argon2i
|
||||||
|| (self.algorithm == Algorithm::Argon2id
|
|| (self.algorithm == Algorithm::Argon2id
|
||||||
&& pass == 0
|
&& pass == 0
|
||||||
&& slice < SYNC_POINTS / 2);
|
&& slice < SYNC_POINTS / 2);
|
||||||
|
|
||||||
for lane in 0..lanes {
|
let mut address_block = Block::default();
|
||||||
let mut address_block = Block::default();
|
let mut input_block = Block::default();
|
||||||
let mut input_block = Block::default();
|
let zero_block = Block::default();
|
||||||
let zero_block = Block::default();
|
|
||||||
|
|
||||||
|
if data_independent_addressing {
|
||||||
|
input_block.as_mut()[..6].copy_from_slice(&[
|
||||||
|
pass as u64,
|
||||||
|
lane as u64,
|
||||||
|
slice as u64,
|
||||||
|
block_count as u64,
|
||||||
|
iterations as u64,
|
||||||
|
self.algorithm as u64,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let first_block = if pass == 0 && slice == 0 {
|
||||||
if data_independent_addressing {
|
if data_independent_addressing {
|
||||||
input_block.as_mut()[..6].copy_from_slice(&[
|
// Generate first set of addresses
|
||||||
pass as u64,
|
self.update_address_block(
|
||||||
lane as u64,
|
&mut address_block,
|
||||||
slice as u64,
|
&mut input_block,
|
||||||
memory_blocks.len() as u64,
|
&zero_block,
|
||||||
iterations as u64,
|
);
|
||||||
self.algorithm as u64,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let first_block = if pass == 0 && slice == 0 {
|
// The first two blocks of each lane are already initialized
|
||||||
if data_independent_addressing {
|
2
|
||||||
// Generate first set of addresses
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cur_index = lane * lane_length + slice * segment_length + first_block;
|
||||||
|
let mut prev_index = if slice == 0 && first_block == 0 {
|
||||||
|
// Last block in current lane
|
||||||
|
cur_index + lane_length - 1
|
||||||
|
} else {
|
||||||
|
// Previous block
|
||||||
|
cur_index - 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fill blocks in the segment
|
||||||
|
for block in first_block..segment_length {
|
||||||
|
// Extract entropy
|
||||||
|
let rand = if data_independent_addressing {
|
||||||
|
let address_index = block % ADDRESSES_IN_BLOCK;
|
||||||
|
|
||||||
|
if address_index == 0 {
|
||||||
self.update_address_block(
|
self.update_address_block(
|
||||||
&mut address_block,
|
&mut address_block,
|
||||||
&mut input_block,
|
&mut input_block,
|
||||||
@@ -413,101 +445,73 @@ impl<'key> Argon2<'key> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The first two blocks of each lane are already initialized
|
address_block.as_ref()[address_index]
|
||||||
2
|
} else {
|
||||||
|
memory_view.get_block(prev_index).as_ref()[0]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate source block index for compress function
|
||||||
|
let ref_lane = if pass == 0 && slice == 0 {
|
||||||
|
// Cannot reference other lanes yet
|
||||||
|
lane
|
||||||
|
} else {
|
||||||
|
(rand >> 32) as usize % lanes
|
||||||
|
};
|
||||||
|
|
||||||
|
let reference_area_size = if pass == 0 {
|
||||||
|
// First pass
|
||||||
|
if slice == 0 {
|
||||||
|
// First slice
|
||||||
|
block - 1 // all but the previous
|
||||||
|
} else if ref_lane == lane {
|
||||||
|
// The same lane => add current segment
|
||||||
|
slice * segment_length + block - 1
|
||||||
|
} else {
|
||||||
|
slice * segment_length - if block == 0 { 1 } else { 0 }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Second pass
|
||||||
|
if ref_lane == lane {
|
||||||
|
lane_length - segment_length + block - 1
|
||||||
|
} else {
|
||||||
|
lane_length - segment_length - if block == 0 { 1 } else { 0 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1.2.4. Mapping rand to 0..<reference_area_size-1> and produce
|
||||||
|
// relative position
|
||||||
|
let mut map = rand & 0xFFFFFFFF;
|
||||||
|
map = (map * map) >> 32;
|
||||||
|
let relative_position = reference_area_size
|
||||||
|
- 1
|
||||||
|
- ((reference_area_size as u64 * map) >> 32) as usize;
|
||||||
|
|
||||||
|
// 1.2.5 Computing starting position
|
||||||
|
let start_position = if pass != 0 && slice != SYNC_POINTS - 1 {
|
||||||
|
(slice + 1) * segment_length
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut cur_index = lane * lane_length + slice * segment_length + first_block;
|
let lane_index = (start_position + relative_position) % lane_length;
|
||||||
let mut prev_index = if slice == 0 && first_block == 0 {
|
let ref_index = ref_lane * lane_length + lane_index;
|
||||||
// Last block in current lane
|
|
||||||
cur_index + lane_length - 1
|
// Calculate new block
|
||||||
|
let result = self.compress(
|
||||||
|
memory_view.get_block(prev_index),
|
||||||
|
memory_view.get_block(ref_index),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.version == Version::V0x10 || pass == 0 {
|
||||||
|
*memory_view.get_block_mut(cur_index) = result;
|
||||||
} else {
|
} else {
|
||||||
// Previous block
|
*memory_view.get_block_mut(cur_index) ^= &result;
|
||||||
cur_index - 1
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fill blocks in the segment
|
prev_index = cur_index;
|
||||||
for block in first_block..segment_length {
|
cur_index += 1;
|
||||||
// Extract entropy
|
|
||||||
let rand = if data_independent_addressing {
|
|
||||||
let address_index = block % ADDRESSES_IN_BLOCK;
|
|
||||||
|
|
||||||
if address_index == 0 {
|
|
||||||
self.update_address_block(
|
|
||||||
&mut address_block,
|
|
||||||
&mut input_block,
|
|
||||||
&zero_block,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
address_block.as_ref()[address_index]
|
|
||||||
} else {
|
|
||||||
memory_blocks[prev_index].as_ref()[0]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate source block index for compress function
|
|
||||||
let ref_lane = if pass == 0 && slice == 0 {
|
|
||||||
// Cannot reference other lanes yet
|
|
||||||
lane
|
|
||||||
} else {
|
|
||||||
(rand >> 32) as usize % lanes
|
|
||||||
};
|
|
||||||
|
|
||||||
let reference_area_size = if pass == 0 {
|
|
||||||
// First pass
|
|
||||||
if slice == 0 {
|
|
||||||
// First slice
|
|
||||||
block - 1 // all but the previous
|
|
||||||
} else if ref_lane == lane {
|
|
||||||
// The same lane => add current segment
|
|
||||||
slice * segment_length + block - 1
|
|
||||||
} else {
|
|
||||||
slice * segment_length - if block == 0 { 1 } else { 0 }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Second pass
|
|
||||||
if ref_lane == lane {
|
|
||||||
lane_length - segment_length + block - 1
|
|
||||||
} else {
|
|
||||||
lane_length - segment_length - if block == 0 { 1 } else { 0 }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 1.2.4. Mapping rand to 0..<reference_area_size-1> and produce
|
|
||||||
// relative position
|
|
||||||
let mut map = rand & 0xFFFFFFFF;
|
|
||||||
map = (map * map) >> 32;
|
|
||||||
let relative_position = reference_area_size
|
|
||||||
- 1
|
|
||||||
- ((reference_area_size as u64 * map) >> 32) as usize;
|
|
||||||
|
|
||||||
// 1.2.5 Computing starting position
|
|
||||||
let start_position = if pass != 0 && slice != SYNC_POINTS - 1 {
|
|
||||||
(slice + 1) * segment_length
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
let lane_index = (start_position + relative_position) % lane_length;
|
|
||||||
let ref_index = ref_lane * lane_length + lane_index;
|
|
||||||
|
|
||||||
// Calculate new block
|
|
||||||
let result =
|
|
||||||
self.compress(&memory_blocks[prev_index], &memory_blocks[ref_index]);
|
|
||||||
|
|
||||||
if self.version == Version::V0x10 || pass == 0 {
|
|
||||||
memory_blocks[cur_index] = result;
|
|
||||||
} else {
|
|
||||||
memory_blocks[cur_index] ^= &result;
|
|
||||||
};
|
|
||||||
|
|
||||||
prev_index = cur_index;
|
|
||||||
cur_index += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -523,6 +527,7 @@ impl<'key> Argon2<'key> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.cpu_feat_avx2.get() {
|
if self.cpu_feat_avx2.get() {
|
||||||
|
// SAFETY: checked that AVX2 was detected.
|
||||||
return unsafe { compress_avx2(rhs, lhs) };
|
return unsafe { compress_avx2(rhs, lhs) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
156
argon2/src/memory.rs
Normal file
156
argon2/src/memory.rs
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
//! Views into Argon2 memory that can be processed in parallel.
|
||||||
|
//!
|
||||||
|
//! This module implements, with a combination of compile-time borrowing and runtime checking, the
|
||||||
|
//! cooperative contract described in section 3.4 (Indexing) of RFC 9106:
|
||||||
|
//!
|
||||||
|
//! > To enable parallel block computation, we further partition the memory matrix into SL = 4
|
||||||
|
//! > vertical slices. The intersection of a slice and a lane is called a segment, which has a
|
||||||
|
//! > length of q/SL. Segments of the same slice can be computed in parallel and do not reference
|
||||||
|
//! > blocks from each other. All other blocks can be referenced.
|
||||||
|
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
use core::ptr::NonNull;
|
||||||
|
|
||||||
|
#[cfg(feature = "parallel")]
|
||||||
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||||
|
|
||||||
|
use crate::{Block, SYNC_POINTS};
|
||||||
|
|
||||||
|
/// Extension trait for Argon2 memory blocks.
|
||||||
|
pub(crate) trait Memory<'a> {
|
||||||
|
/// Compute each Argon2 segment.
|
||||||
|
///
|
||||||
|
/// By default computation is single threaded. Parallel computation can be enabled with the
|
||||||
|
/// `parallel` feature, in which case [rayon] is used to compute as many lanes in parallel as
|
||||||
|
/// possible.
|
||||||
|
fn for_each_segment<F>(&mut self, lanes: usize, f: F)
|
||||||
|
where
|
||||||
|
F: Fn(SegmentView<'_>, usize, usize) + Sync + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Memory<'_> for &mut [Block] {
|
||||||
|
#[cfg(not(feature = "parallel"))]
|
||||||
|
fn for_each_segment<F>(&mut self, lanes: usize, f: F)
|
||||||
|
where
|
||||||
|
F: Fn(SegmentView<'_>, usize, usize) + Sync + Send,
|
||||||
|
{
|
||||||
|
let inner = MemoryInner::new(self, lanes);
|
||||||
|
for slice in 0..SYNC_POINTS {
|
||||||
|
for lane in 0..lanes {
|
||||||
|
// SAFETY: `self` exclusively borrows the blocks, and we sequentially process
|
||||||
|
// slices and segments.
|
||||||
|
let segment = unsafe { SegmentView::new(inner, slice, lane) };
|
||||||
|
f(segment, slice, lane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "parallel")]
|
||||||
|
fn for_each_segment<F>(&mut self, lanes: usize, f: F)
|
||||||
|
where
|
||||||
|
F: Fn(SegmentView<'_>, usize, usize) + Sync + Send,
|
||||||
|
{
|
||||||
|
let inner = MemoryInner::new(self, lanes);
|
||||||
|
for slice in 0..SYNC_POINTS {
|
||||||
|
(0..lanes).into_par_iter().for_each(|lane| {
|
||||||
|
// SAFETY: `self` exclusively borrows the blocks, we sequentially process slices,
|
||||||
|
// and we create exactly one segment view per lane in a slice.
|
||||||
|
let segment = unsafe { SegmentView::new(inner, slice, lane) };
|
||||||
|
f(segment, slice, lane);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Low-level pointer and metadata for an Argon2 memory region.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct MemoryInner<'a> {
|
||||||
|
blocks: NonNull<Block>,
|
||||||
|
block_count: usize,
|
||||||
|
lane_length: usize,
|
||||||
|
phantom: PhantomData<&'a mut Block>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemoryInner<'_> {
|
||||||
|
fn new(memory_blocks: &mut [Block], lanes: usize) -> Self {
|
||||||
|
let block_count = memory_blocks.len();
|
||||||
|
let lane_length = block_count / lanes;
|
||||||
|
|
||||||
|
// SAFETY: the pointer needs to be derived from a mutable reference because (later)
|
||||||
|
// mutating the blocks through a pointer derived from a shared reference would be UB.
|
||||||
|
let blocks = NonNull::from(memory_blocks);
|
||||||
|
|
||||||
|
MemoryInner {
|
||||||
|
blocks: blocks.cast(),
|
||||||
|
block_count,
|
||||||
|
lane_length,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lane_of(&self, index: usize) -> usize {
|
||||||
|
index / self.lane_length
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slice_of(&self, index: usize) -> usize {
|
||||||
|
index / (self.lane_length / SYNC_POINTS) % SYNC_POINTS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: private type, and just a pointer with some metadata.
|
||||||
|
unsafe impl Send for MemoryInner<'_> {}
|
||||||
|
|
||||||
|
// SAFETY: private type, and just a pointer with some metadata.
|
||||||
|
unsafe impl Sync for MemoryInner<'_> {}
|
||||||
|
|
||||||
|
/// A view into Argon2 memory for a particular segment (i.e. slice × lane).
|
||||||
|
pub(crate) struct SegmentView<'a> {
|
||||||
|
inner: MemoryInner<'a>,
|
||||||
|
slice: usize,
|
||||||
|
lane: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SegmentView<'a> {
|
||||||
|
/// Create a view into Argon2 memory for a particular segment (i.e. slice × lane).
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// At any time, there can be at most one view for a given Argon2 segment. Additionally, all
|
||||||
|
/// concurrent segment views must be for the same slice.
|
||||||
|
unsafe fn new(inner: MemoryInner<'a>, slice: usize, lane: usize) -> Self {
|
||||||
|
SegmentView { inner, slice, lane }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a shared reference to a block.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the index is out of bounds or if the desired block *could* be mutably aliased (if
|
||||||
|
/// it is on the current slice but on a different lane/segment).
|
||||||
|
pub fn get_block(&self, index: usize) -> &Block {
|
||||||
|
assert!(index < self.inner.block_count);
|
||||||
|
assert!(self.inner.lane_of(index) == self.lane || self.inner.slice_of(index) != self.slice);
|
||||||
|
|
||||||
|
// SAFETY: by construction, the base pointer is valid for reads, and we assert that the
|
||||||
|
// index is in bounds. We also assert that the index either lies on this lane, or is on
|
||||||
|
// another slice. Finally, we're the only view into this segment, and mutating through it
|
||||||
|
// requires `&mut self` and is restricted to blocks within the segment.
|
||||||
|
unsafe { self.inner.blocks.add(index).as_ref() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to a block.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the index is out of bounds or if the desired block lies outside this segment.
|
||||||
|
pub fn get_block_mut(&mut self, index: usize) -> &mut Block {
|
||||||
|
assert!(index < self.inner.block_count);
|
||||||
|
assert_eq!(self.inner.lane_of(index), self.lane);
|
||||||
|
assert_eq!(self.inner.slice_of(index), self.slice);
|
||||||
|
|
||||||
|
// SAFETY: by construction, the base pointer is valid for reads and writes, and we assert
|
||||||
|
// that the index is in bounds. We also assert that the index lies on this segment, and
|
||||||
|
// we're the only view for it, taking `&mut self`.
|
||||||
|
unsafe { self.inner.blocks.add(index).as_mut() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,10 +8,17 @@ publish = false
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
argon2 = { path = "../argon2" }
|
argon2 = { path = "../argon2" }
|
||||||
criterion = { version = "0.4", features = ["html_reports"] }
|
criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
pprof = { version = "0.14", features = ["flamegraph", "criterion"] }
|
pprof = { version = "0.14", features = ["flamegraph", "criterion"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
parallel = ["argon2/parallel"]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "argon2"
|
name = "argon2"
|
||||||
path = "src/argon2.rs"
|
path = "src/argon2.rs"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
password-hash = { git = "https://github.com/RustCrypto/traits.git" }
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use argon2::*;
|
use argon2::*;
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
use pprof::criterion::{Output, PProfProfiler};
|
use pprof::criterion::{Output, PProfProfiler};
|
||||||
@@ -26,46 +28,26 @@ fn bench_default_params(c: &mut Criterion) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_vary_m(c: &mut Criterion) {
|
fn bench_vary_params(c: &mut Criterion) {
|
||||||
let t_cost = 4;
|
let mut tests = BTreeSet::new();
|
||||||
let p_cost = 4;
|
// Vary `m_cost`.
|
||||||
for m_cost in [2 * 1024, 16 * 1024, 64 * 1024, 256 * 1024] {
|
for m_cost in [2 * 1024, 16 * 1024, 32 * 1024, 64 * 1024, 256 * 1024] {
|
||||||
let test_name = format!("argon2id V0x13 m={m_cost} t={t_cost} p={p_cost}");
|
tests.insert((m_cost, 4, 4));
|
||||||
c.bench_function(&test_name, |b| {
|
|
||||||
let mut out = [0u8; 32];
|
|
||||||
let params = Params::new(m_cost, t_cost, p_cost, Some(32)).unwrap();
|
|
||||||
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
|
|
||||||
b.iter(|| {
|
|
||||||
argon2
|
|
||||||
.hash_password_into(black_box(BENCH_PASSWORD), black_box(BENCH_SALT), &mut out)
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
// Vary `t_cost`.
|
||||||
|
for t_cost in [1, 2, 4, 8, 16] {
|
||||||
fn bench_vary_t(c: &mut Criterion) {
|
tests.insert((32 * 1024, t_cost, 4));
|
||||||
let m_cost = 32 * 1024;
|
|
||||||
let p_cost = 4;
|
|
||||||
for t_cost in [2, 8, 16, 24] {
|
|
||||||
let test_name = format!("argon2id V0x13 m={m_cost} t={t_cost} p={p_cost}");
|
|
||||||
c.bench_function(&test_name, |b| {
|
|
||||||
let mut out = [0u8; 32];
|
|
||||||
let params = Params::new(m_cost, t_cost, p_cost, Some(32)).unwrap();
|
|
||||||
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
|
|
||||||
b.iter(|| {
|
|
||||||
argon2
|
|
||||||
.hash_password_into(black_box(BENCH_PASSWORD), black_box(BENCH_SALT), &mut out)
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
// Vary `p_cost`.
|
||||||
|
for p_cost in [1, 2, 4, 8, 16] {
|
||||||
fn bench_vary_p(c: &mut Criterion) {
|
for m_mib in [256 * 1024, 1024 * 1024] {
|
||||||
let m_cost = 32 * 1024;
|
tests.insert((m_mib, 1, p_cost));
|
||||||
let t_cost = 4;
|
}
|
||||||
for p_cost in [2, 8, 16, 64] {
|
for t_cost in [1, 2, 4] {
|
||||||
|
tests.insert((32 * 1024, t_cost, p_cost));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (m_cost, t_cost, p_cost) in tests {
|
||||||
let test_name = format!("argon2id V0x13 m={m_cost} t={t_cost} p={p_cost}");
|
let test_name = format!("argon2id V0x13 m={m_cost} t={t_cost} p={p_cost}");
|
||||||
c.bench_function(&test_name, |b| {
|
c.bench_function(&test_name, |b| {
|
||||||
let mut out = [0u8; 32];
|
let mut out = [0u8; 32];
|
||||||
@@ -85,8 +67,6 @@ criterion_group!(
|
|||||||
config = Criterion::default().with_profiler(PProfProfiler::new(300, Output::Flamegraph(None)));
|
config = Criterion::default().with_profiler(PProfProfiler::new(300, Output::Flamegraph(None)));
|
||||||
targets =
|
targets =
|
||||||
bench_default_params,
|
bench_default_params,
|
||||||
bench_vary_m,
|
bench_vary_params,
|
||||||
bench_vary_t,
|
|
||||||
bench_vary_p,
|
|
||||||
);
|
);
|
||||||
criterion_main!(benches);
|
criterion_main!(benches);
|
||||||
|
|||||||
Reference in New Issue
Block a user