argon2_rust/core.rs
1//! The Argon2 driver: pre-hashing, the fill loop, finalisation, and the public
2//! [`Argon2`] entry points.
3//!
4//! Ported line by line from `phc-winner-argon2/src/core.c` (`initial_hash`,
5//! `fill_first_blocks`, `initialize`, `index_alpha`, `fill_memory_blocks`,
6//! `fill_memory_blocks_st`, `fill_memory_blocks_mt`, `finalize`) and
7//! `phc-winner-argon2/src/argon2.c` (`argon2_ctx`, `argon2_hash`,
8//! `argon2_verify`, `argon2_verify_ctx`, `argon2_compare`).
9
10use alloc::string::String;
11use alloc::vec::Vec;
12
13use crate::blake2b::{Blake2b, blake2b_long};
14use crate::block::{Block, Instance, Position};
15use crate::error::Error;
16use crate::fill_block::{Backend, FillSegmentFn};
17use crate::memory::{Arena, Workspace, clear_internal_memory, clear_internal_memory_u64};
18use crate::params::{
19 Algorithm, BLOCK_SIZE, MAX_PWD_LENGTH, PREHASH_DIGEST_LENGTH, PREHASH_SEED_LENGTH, Params,
20 SYNC_POINTS, Version,
21};
22
23/// The KAT trace hook: `internal_kat(instance, pass)` from `src/genkat.c`.
24///
25/// Invoked after every pass with `(pass_index, whole_arena)`, at a point where
26/// every helper is parked at the pass boundary and cannot touch the arena.
27/// `tests/kat.rs` uses it to dump the arena the way `genkat` does.
28pub type PassTrace<'a> = &'a mut dyn FnMut(u32, &[Block]);
29
30// Structural regression hook for the zeroization boundary: stable hashing must
31// never ask `hash_in_arena` to copy H0 out of the blockhash it already wipes.
32// Thread-local keeps parallel libtest cases from charging one another.
33#[cfg(all(test, feature = "std"))]
34std::thread_local! {
35 static H0_COPY_COUNT: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
36}
37
38// ---------------------------------------------------------------------------
39// index_alpha (contract: called by every fill_block backend)
40// ---------------------------------------------------------------------------
41
42/// `index_alpha()` from `src/core.c`: the absolute index of the reference block.
43///
44/// (contract) The `fill_block` backends call this once per block.
45///
46/// # The two traps
47///
48/// **1. Deliberate wrapping subtraction.** The C writes
49///
50/// ```c
51/// reference_area_size = position->slice * segment_length
52/// + ((position->index == 0) ? (-1) : 0);
53/// ```
54///
55/// `-1` added to a `uint32_t` wraps, so when `slice * segment_length` is 0 the
56/// result is `0xFFFFFFFF`, not a negative number. The same shape appears in the
57/// `pass > 0` branch:
58///
59/// ```c
60/// reference_area_size = lane_length - segment_length
61/// + ((position->index == 0) ? (-1) : 0);
62/// ```
63///
64/// In Rust these **must** be [`u32::wrapping_sub`] / [`u32::wrapping_add`].
65/// A plain `-` panics in debug and silently differs in release. This is the
66/// single most common way an Argon2 port breaks, and it only shows up for
67/// lane-crossing references at `index == 0` — which means the official
68/// single-lane vectors will not catch it. The `p > 1` vectors and KATs will.
69///
70/// **2. Exact integer widths in the position mapping.** All of this is `u64`:
71///
72/// ```text
73/// let mut rel = pseudo_rand as u64; // the LOW u32 of the pseudo-random value
74/// rel = (rel * rel) >> 32;
75/// rel = (ras as u64) - 1 - (((ras as u64) * rel) >> 32);
76/// abs = ((start_position as u64 + rel) % lane_length as u64) as u32;
77/// ```
78///
79/// with one refinement the summary above glosses over and the C source settles:
80/// `reference_area_size - 1` is evaluated in **`uint32_t`** (both operands are
81/// 32-bit; the `int` literal converts to `unsigned int`) and only *then* widened
82/// for the outer subtraction. The two readings differ exactly when
83/// `reference_area_size == 0`: 32-bit-first gives `0x0000_0000_FFFF_FFFF`,
84/// 64-bit-first gives `0xFFFF_FFFF_FFFF_FFFF`. This port does it the C way.
85///
86/// # Starting position
87///
88/// ```text
89/// start_position = 0;
90/// if pass != 0 {
91/// start_position = if slice == SYNC_POINTS - 1 { 0 }
92/// else { (slice + 1) * segment_length };
93/// }
94/// ```
95#[must_use]
96pub fn index_alpha(
97 instance: &Instance,
98 position: &Position,
99 pseudo_rand: u32,
100 same_lane: bool,
101) -> u32 {
102 // The C ends with `% instance->lane_length`. For any instance built from
103 // validated `Params`, `lane_length == segment_length * SYNC_POINTS >= 8`.
104 // This guard exists only so a hand-built degenerate `Instance` cannot turn
105 // that `%` into a division-by-zero panic; the crate must never panic.
106 if instance.lane_length == 0 {
107 return 0;
108 }
109
110 let reference_area_size: u32 = if position.pass == 0 {
111 // First pass.
112 if position.slice == 0 {
113 // core.c:210-211 `reference_area_size = position->index - 1;`
114 // `fill_segment` starts at index 2 on pass 0 / slice 0, so this is
115 // >= 1; `wrapping_sub` only keeps the function panic-free.
116 position.index.wrapping_sub(1)
117 } else if same_lane {
118 // core.c:215-217
119 // position->slice * instance->segment_length + position->index - 1
120 position
121 .slice
122 .wrapping_mul(instance.segment_length)
123 .wrapping_add(position.index)
124 // core.c:217 `+ position->index - 1`, uint32_t arithmetic.
125 .wrapping_sub(1)
126 } else {
127 // core.c:219-221
128 // position->slice * instance->segment_length
129 // + ((position->index == 0) ? (-1) : 0)
130 //
131 // Adding the `int` -1 to a uint32_t is a DELIBERATE wrapping
132 // subtraction. `wrapping_sub(1)` is the same value as
133 // `wrapping_add(0xFFFF_FFFF)`; a plain `-` would panic in debug.
134 position
135 .slice
136 .wrapping_mul(instance.segment_length)
137 .wrapping_sub(u32::from(position.index == 0))
138 }
139 } else if same_lane {
140 // core.c:227-229
141 // instance->lane_length - instance->segment_length
142 // + position->index - 1
143 instance
144 .lane_length
145 // core.c:227 `lane_length - segment_length`, uint32_t arithmetic.
146 .wrapping_sub(instance.segment_length)
147 .wrapping_add(position.index)
148 // core.c:229 `+ position->index - 1`, uint32_t arithmetic.
149 .wrapping_sub(1)
150 } else {
151 // core.c:231-233
152 // instance->lane_length - instance->segment_length
153 // + ((position->index == 0) ? (-1) : 0)
154 instance
155 .lane_length
156 // core.c:231-232 `lane_length - segment_length`, uint32_t arithmetic.
157 .wrapping_sub(instance.segment_length)
158 // core.c:233, the same deliberate wrapping subtraction as above.
159 .wrapping_sub(u32::from(position.index == 0))
160 };
161
162 // core.c:239-242. 1.2.4. Mapping pseudo_rand to 0..<reference_area_size-1>
163 // and producing the relative position.
164 //
165 // Neither multiplication can overflow: `relative_position` is bounded by
166 // `u32::MAX` on entry and by `2^32 - 1` after the shift, and both factors of
167 // each product are therefore < 2^32.
168 let mut relative_position = u64::from(pseudo_rand);
169 relative_position = (relative_position * relative_position) >> 32;
170 // core.c:241 `reference_area_size - 1` is uint32_t (see the doc comment),
171 // hence the 32-bit `wrapping_sub` inside `u64::from(..)`.
172 relative_position = u64::from(reference_area_size.wrapping_sub(1))
173 // core.c:241-242, the outer subtraction is uint64_t. It cannot underflow
174 // — `(ras * rel) >> 32 <= ras - 1` for every `rel < 2^32` — but
175 // `wrapping_sub` keeps that a property rather than an assumption.
176 .wrapping_sub((u64::from(reference_area_size) * relative_position) >> 32);
177
178 // core.c:245-251. 1.2.5 Computing the starting position.
179 let mut start_position: u32 = 0;
180 if position.pass != 0 {
181 start_position = if position.slice == SYNC_POINTS - 1 {
182 0
183 } else {
184 position
185 .slice
186 .wrapping_add(1)
187 .wrapping_mul(instance.segment_length)
188 };
189 }
190
191 // core.c:254-255. 1.2.6. Computing the absolute position. `start_position`
192 // is uint32_t and `relative_position` uint64_t, so the sum and the `%` are
193 // evaluated in uint64_t and only the result is truncated back to uint32_t.
194 ((u64::from(start_position).wrapping_add(relative_position)) % u64::from(instance.lane_length))
195 as u32
196}
197
198// ---------------------------------------------------------------------------
199// Pre-hashing and the fill loop
200// ---------------------------------------------------------------------------
201
202/// `initial_hash()` from `src/core.c`, returning the 72-byte `H0` buffer.
203///
204/// BLAKE2b-512 over, in this order, each `u32` little-endian:
205/// `lanes`, `outlen`, `m_cost`, `t_cost`, `version`, `type`,
206/// then `pwdlen` and `pwd`, `saltlen` and `salt`, `secretlen` and `secret`,
207/// `adlen` and `ad`.
208///
209/// The first 64 bytes of the result are `H0`; the trailing 8 bytes are left
210/// zero and are filled in by [`fill_first_blocks`] with the block index and the
211/// lane index. (`initialize()` in the C zeroes them explicitly right after
212/// calling `initial_hash`; here they are never written in the first place.)
213///
214/// The four buffer lengths are hashed as `u32`, exactly as the C hashes
215/// `context->pwdlen` and friends. A caller that has run
216/// [`Params::validate_for`] first — which every entry point in this module
217/// does — cannot reach the truncating cast.
218///
219/// # Errors
220///
221/// Only a BLAKE2b parameter error, which cannot happen here: the digest length
222/// is the constant 64.
223pub fn initial_hash(
224 algorithm: Algorithm,
225 version: Version,
226 params: &Params,
227 pwd: &[u8],
228 salt: &[u8],
229 secret: &[u8],
230 ad: &[u8],
231) -> Result<[u8; PREHASH_SEED_LENGTH], Error> {
232 let mut blockhash = [0u8; PREHASH_SEED_LENGTH];
233 initial_hash_into(
234 algorithm,
235 version,
236 params,
237 pwd,
238 salt,
239 secret,
240 ad,
241 &mut blockhash,
242 )?;
243 Ok(blockhash)
244}
245
246/// [`initial_hash`] written directly into its caller's wipe-owned buffer.
247///
248/// The stable hash path uses this form so returning a 72-byte `Result` cannot
249/// make the optimiser leave an intermediate H0 copy behind on the stack.
250#[allow(clippy::too_many_arguments)]
251fn initial_hash_into(
252 algorithm: Algorithm,
253 version: Version,
254 params: &Params,
255 pwd: &[u8],
256 salt: &[u8],
257 secret: &[u8],
258 ad: &[u8],
259 blockhash: &mut [u8; PREHASH_SEED_LENGTH],
260) -> Result<(), Error> {
261 /// `store32(&value, len); blake2b_update(&BlakeHash, &value, 4);`
262 #[inline]
263 fn le32(len: usize) -> [u8; 4] {
264 (len as u32).to_le_bytes()
265 }
266
267 // core.c:547 `blake2b_init(&BlakeHash, ARGON2_PREHASH_DIGEST_LENGTH);`
268 let mut state = Blake2b::new(PREHASH_DIGEST_LENGTH)?;
269
270 // core.c:549-565. Six u32 parameters, in this exact order.
271 state.update(¶ms.lanes().to_le_bytes());
272 state.update(&le32(params.output_len()));
273 state.update(¶ms.m_cost().to_le_bytes());
274 state.update(¶ms.t_cost().to_le_bytes());
275 state.update(&version.as_u32().to_le_bytes());
276 state.update(&algorithm.as_u32().to_le_bytes());
277
278 // core.c:567-607. Four length-prefixed buffers, in this exact order.
279 // The C guards each `blake2b_update` with a NULL check; feeding an empty
280 // slice is the same no-op. `ARGON2_FLAG_CLEAR_PASSWORD` /
281 // `ARGON2_FLAG_CLEAR_SECRET` have no analogue here: this port never takes
282 // ownership of the caller's buffers, so it cannot wipe them.
283 state.update(&le32(pwd.len()));
284 state.update(pwd);
285
286 state.update(&le32(salt.len()));
287 state.update(salt);
288
289 state.update(&le32(secret.len()));
290 state.update(secret);
291
292 state.update(&le32(ad.len()));
293 state.update(ad);
294
295 // core.c:609 `blake2b_final(&BlakeHash, blockhash, ARGON2_PREHASH_DIGEST_LENGTH);`
296 state.finalize(&mut blockhash[..PREHASH_DIGEST_LENGTH])?;
297 Ok(())
298}
299
300/// `fill_first_blocks()` from `src/core.c`.
301///
302/// For each lane `l`, writes `LE32(0)` then `LE32(l)` at
303/// `blockhash[64..72]`, expands the 72 bytes to 1024 with `blake2b_long`, and
304/// loads that into block `l * lane_length + 0`; then repeats with `LE32(1)` for
305/// block `l * lane_length + 1`.
306///
307/// # Errors
308///
309/// [`Error::IncorrectParameter`] if `arena` is too small for
310/// `lanes * lane_length` blocks — unreachable from this module, which always
311/// sizes the arena from the same [`Params`]. Otherwise only a BLAKE2b parameter
312/// error, which cannot happen for the constant length 1024.
313pub fn fill_first_blocks(
314 blockhash: &mut [u8; PREHASH_SEED_LENGTH],
315 arena: &mut [Block],
316 lanes: u32,
317 lane_length: u32,
318) -> Result<(), Error> {
319 let mut blockhash_bytes = [0u8; BLOCK_SIZE];
320
321 // Unlike the C helper, this safe internal-api entry point reports a short
322 // arena instead of indexing unchecked. Keep every fallible exit inside the
323 // closure so the derived block bytes are wiped before the error escapes.
324 let result = (|| {
325 for lane in 0..lanes {
326 // core.c:522-523
327 // store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 0);
328 // store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH + 4, l);
329 blockhash[PREHASH_DIGEST_LENGTH..PREHASH_DIGEST_LENGTH + 4]
330 .copy_from_slice(&0u32.to_le_bytes());
331 blockhash[PREHASH_DIGEST_LENGTH + 4..PREHASH_SEED_LENGTH]
332 .copy_from_slice(&lane.to_le_bytes());
333
334 // core.c:524-525 `blake2b_long(blockhash_bytes, ARGON2_BLOCK_SIZE,
335 // blockhash, ARGON2_PREHASH_SEED_LENGTH);`
336 blake2b_long(&mut blockhash_bytes, blockhash)?;
337
338 // core.c:526-527 `load_block(&instance->memory[l * lane_length + 0], ..)`
339 let base = (lane as usize)
340 .checked_mul(lane_length as usize)
341 .ok_or(Error::IncorrectParameter)?;
342 match arena.get_mut(base) {
343 Some(block) => block.load_le(&blockhash_bytes),
344 None => return Err(Error::IncorrectParameter),
345 }
346
347 // core.c:529 `store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 1);`
348 blockhash[PREHASH_DIGEST_LENGTH..PREHASH_DIGEST_LENGTH + 4]
349 .copy_from_slice(&1u32.to_le_bytes());
350 blake2b_long(&mut blockhash_bytes, blockhash)?;
351
352 // core.c:532-533 `load_block(&instance->memory[l * lane_length + 1], ..)`
353 let second = base.checked_add(1).ok_or(Error::IncorrectParameter)?;
354 match arena.get_mut(second) {
355 Some(block) => block.load_le(&blockhash_bytes),
356 None => return Err(Error::IncorrectParameter),
357 }
358 }
359 Ok(())
360 })();
361
362 // core.c:535 `clear_internal_memory(blockhash_bytes, ARGON2_BLOCK_SIZE);`
363 clear_internal_memory(&mut blockhash_bytes);
364 result
365}
366
367/// `fill_memory_blocks()` from `src/core.c`, with the backend resolved from
368/// runtime CPU detection.
369///
370/// Safe, and the reason it can be: it never lets a caller name the backend. The
371/// value comes from [`crate::fill_block::backend`], which only ever returns a
372/// backend whose instruction set this CPU was *detected* to have. Every entry
373/// point that does take a [`Backend`] is `unsafe` — see
374/// [`fill_memory_blocks_traced`].
375///
376/// # Errors
377///
378/// [`Error::IncorrectParameter`] if `instance.lanes == 0` (`core.c:377`).
379pub fn fill_memory_blocks(instance: &Instance) -> Result<(), Error> {
380 // SAFETY: `backend()` is the cached result of the `is_*_feature_detected!`
381 // cascade in `fill_block::detect`, so this CPU can execute it. The other two
382 // obligations — a valid arena and no concurrent writer — are `Instance`'s
383 // own contract, discharged by whoever called `Instance::new`.
384 unsafe { fill_memory_blocks_traced(instance, crate::fill_block::backend(), None) }
385}
386
387/// `fill_memory_blocks()` with an explicit [`Backend`] and a KAT trace hook.
388///
389/// The function pointer is resolved **once**, here, before any loop:
390///
391/// ```text
392/// let fill = crate::fill_block::fill_segment_fn(backend);
393/// for pass in 0..instance.passes {
394/// for slice in 0..SYNC_POINTS {
395/// for lane in 0..instance.lanes {
396/// fill(instance, Position::new(pass, lane, slice, 0));
397/// }
398/// }
399/// }
400/// ```
401///
402/// Nothing detects or dispatches inside the per-block loop; the cost is one
403/// indirect call per *segment*, which is `segment_length` blocks.
404///
405/// `trace` is `internal_kat()` from `src/genkat.c`: it is invoked after every
406/// pass with `(pass_index, whole_arena)`, at a point where every helper is
407/// parked at the barrier and cannot touch the arena.
408///
409/// With the `parallel` feature, `instance.threads > 1` and
410/// `instance.lanes > 1`, one
411/// [`std::thread::scope`] owns the helpers for the whole fill. They meet at an
412/// atomic barrier after each slice, matching the C's sync points without
413/// respawning. The single-threaded path holds no raw-pointer sharing at all, so
414/// it stays checkable under Miri.
415///
416/// # Safety
417///
418/// `backend` selects a `fill_segment` carrying
419/// `#[target_feature(enable = ...)]`. Calling one whose feature this CPU lacks
420/// is undefined behaviour — in practice `SIGILL` — so the caller must guarantee
421/// the CPU can execute it. Either of these discharges that:
422///
423/// * `backend.is_available()` returned `true`, or
424/// * `backend` came from [`crate::fill_block::backend`] / `detect`.
425///
426/// The one other way to satisfy it is a host *measured* to execute the
427/// instructions while hiding them from `cpuid`, which is what the deliberately
428/// forced AVX2 tests rely on under Rosetta 2. That is a test-only affordance and
429/// never a library path.
430///
431/// `instance` must also uphold [`Instance::new`]'s contract, and no other thread
432/// may be filling this arena.
433///
434/// # Errors
435///
436/// [`Error::IncorrectParameter`] if `instance.lanes == 0`.
437pub unsafe fn fill_memory_blocks_traced(
438 instance: &Instance,
439 backend: Backend,
440 mut trace: Option<PassTrace<'_>>,
441) -> Result<(), Error> {
442 // core.c:377-379 `if (instance == NULL || instance->lanes == 0)`.
443 if instance.lanes == 0 {
444 return Err(Error::IncorrectParameter);
445 }
446
447 // Resolve the backend ONCE, before every loop. See `fill_block/mod.rs` for
448 // why the `#[target_feature]` boundary sits on `fill_segment`.
449 let fill = crate::fill_block::fill_segment_fn(backend);
450
451 // Multi-lane: hand the whole pass/slice/lane nest to the worker pool, which
452 // owns its threads for the entire fill instead of for one slice.
453 #[cfg(feature = "parallel")]
454 if instance.threads > 1 && instance.lanes > 1 {
455 // SAFETY: forwarded verbatim from this function's own contract.
456 unsafe { fill_pooled(instance, fill, trace) };
457 return Ok(());
458 }
459
460 for pass in 0..instance.passes {
461 for slice in 0..SYNC_POINTS {
462 // SAFETY: `fill` is `fill_segment_fn(backend)`, and the caller
463 // guarantees this CPU can execute `backend`; `instance` and the
464 // absence of a concurrent filler are the caller's obligations too.
465 unsafe { fill_slice_st(instance, fill, pass, slice) };
466 }
467
468 // genkat.c `internal_kat(instance, r)` — printed after each pass.
469 if let Some(callback) = trace.as_mut() {
470 // SAFETY: three obligations, and none of them is "the arena is zero".
471 //
472 // 1. Valid for `memory_len()` `Block`s: that is `Instance::new`'s
473 // own contract, discharged by whoever built `instance`.
474 // 2. Every block is *initialised*, which is what makes a `&[Block]`
475 // over them a valid reference. `Arena` guarantees this for its
476 // whole capacity from birth (`alloc_zeroed`) and never gives it
477 // up — initialised memory stays initialised when a `Workspace`
478 // parks and re-lends it. NOTE for the next reader: this used to
479 // be justified by "the arena was zero-initialised by
480 // `Arena::new`". That premise is false for a pooled arena with
481 // `zeroize-memory` off, and it was never the load-bearing one;
482 // *initialised* is.
483 // 3. No live `&mut Block`: the parallel path returned above, and
484 // every sequential `fill_slice_st` call has returned before
485 // this shared slice is formed.
486 //
487 // Separately from soundness, the callback can never observe a
488 // previous tenant's bytes even on a reused arena: it fires only
489 // after all four slices of a pass have completed, and pass 0 writes
490 // every block of every lane exactly once before this point.
491 let blocks = unsafe {
492 core::slice::from_raw_parts(
493 instance.memory_ptr().cast_const(),
494 instance.memory_len(),
495 )
496 };
497 callback(pass, blocks);
498 }
499 }
500
501 Ok(())
502}
503
504/// `fill_memory_blocks_st()`'s innermost loop (`core.c:265-268`).
505///
506/// Deliberately free of any cross-thread pointer sharing: the only `unsafe` is
507/// the call to `fill`, which every backend requires. That keeps the whole
508/// single-threaded path checkable under Miri.
509///
510/// # Safety
511///
512/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute whatever
513/// instruction set `fill` needs.
514unsafe fn fill_slice_st(instance: &Instance, fill: FillSegmentFn, pass: u32, slice: u32) {
515 for lane in 0..instance.lanes {
516 let position = Position::new(pass, lane, slice, 0);
517 // SAFETY: three obligations from `FillSegmentFn`.
518 // 1. `fill` came from `fill_segment_fn(backend)`, and this function's
519 // caller guarantees the CPU can execute that backend — an
520 // obligation that is now carried in the type system all the way out
521 // to `fill_memory_blocks_traced`, `hash_traced` and
522 // `hash_with_backend`, every one of which is an `unsafe fn`.
523 // 2. `instance`'s arena is valid for `memory_len()` blocks — that is
524 // `Instance::new`'s own safety contract, discharged by the caller.
525 // 3. `pass < passes`, `slice < SYNC_POINTS` and `lane < lanes` by
526 // construction, and this thread is the only one running, so nothing
527 // else can be writing this segment.
528 unsafe { fill(instance, position) };
529 }
530}
531
532// ---------------------------------------------------------------------------
533// Parallel fill
534// ---------------------------------------------------------------------------
535
536/// A `&Instance` that may be handed to another thread.
537///
538/// `Instance` holds the arena as a `*mut Block`, which makes it neither `Send`
539/// nor `Sync`, so sharing it across lanes needs this newtype and the safety
540/// argument below.
541#[cfg(feature = "parallel")]
542#[derive(Clone, Copy)]
543struct SharedInstance<'a>(&'a Instance);
544
545// SAFETY: sending a `SharedInstance` hands another thread a `&Instance`, and
546// through it the arena's `*mut Block`. That is sound for the one way
547// `fill_slice_mt` uses it, and only that way:
548//
549// * At each sync point, every worker in the whole-fill `std::thread::scope`
550// runs the SAME `(pass, slice)` and pairwise DISTINCT `lane`s: lanes are
551// claimed from a single `fetch_add` counter, so every index is handed out
552// exactly once.
553// * Within one slice, `fill_segment(instance, {pass, lane, slice, ..})`
554// WRITES only blocks `lane * lane_length + slice * segment_length + i` for
555// `i in 0..segment_length` — precisely the one segment that lane owns in
556// this slice. Two different lanes therefore never write the same block, so
557// no two `&mut Block` ever alias.
558// * It READS `prev_offset`, which always stays inside its own lane (the block
559// it just wrote, or the last block of the lane on the wrap-around), and
560// `ref_lane * lane_length + index_alpha(..)`. When `ref_lane != lane`,
561// `index_alpha`'s reference area is `slice * segment_length` blocks from the
562// start of the lane (pass 0) or the `lane_length - segment_length` blocks of
563// the *other three* slices (pass > 0, `start_position` skips the current
564// one). Either way it never lands in another lane's current segment, which
565// is the only region being written concurrently. So no read races a write.
566// * The scope spans the whole fill. At each slice boundary, every helper
567// publishes its writes with `Release` on `arrived`; the leader acquires them,
568// resets the work counters, then releases the next `generation`, which every
569// helper acquires before proceeding. Thus every write of slice `s`
570// happens-before every read in slice `s + 1`, mirroring the C's sync points.
571// * The `Instance` struct itself is only ever read; the interior mutability is
572// confined to the arena it points at, via `Instance::block_mut`.
573//
574// `Sync` is deliberately NOT implemented: the workers each take their own copy
575// of this `Copy` newtype, so a shared reference to it never crosses a thread.
576#[cfg(feature = "parallel")]
577unsafe impl Send for SharedInstance<'_> {}
578
579/// The sync point, and the state the workers share across one whole fill.
580///
581/// # Why this exists instead of one `thread::scope` per slice
582///
583/// The four sync points per pass are algorithmic and cannot be weakened: every
584/// lane must finish slice `N` before any lane starts `N + 1`. What is *not*
585/// algorithmic is destroying and recreating the worker set at each of them,
586/// which is what `std::thread::scope` per slice did — `passes * 4 *
587/// (workers - 1)` thread creations for one hash, each a `clone()` plus a stack
588/// mapping. Measured on the target, four lanes over 4096-block segments:
589///
590/// ```text
591/// scope + 3 spawns, per slice 63.8 us
592/// live pool on std::sync::Barrier 20.7 us (Mutex + Condvar: a syscall)
593/// live pool on this barrier 0.6 us
594/// ```
595///
596/// The threads now live for the whole fill and meet at a barrier instead. That
597/// is a 12-to-3 reduction in thread creations at `t = 1, p = 4`, and 48-to-3 at
598/// `t = 4`. The barrier is hand-rolled rather than `std::sync::Barrier` for the
599/// 20 us in that table: `Barrier` is a `Mutex` + `Condvar`, so every one of the
600/// `4 * passes` sync points is a pair of futex round trips.
601///
602/// # The barrier, and why the ordering is enough
603///
604/// Sense-reversing, with the *leader* — the thread that called into the hash —
605/// always doing the release. Every helper, having finished its lanes:
606///
607/// ```text
608/// arrived.fetch_add(1, Release) publishes that helper's writes
609/// spin until generation != mine (Acquire)
610/// ```
611///
612/// and the leader, having finished its own lanes:
613///
614/// ```text
615/// spin until arrived + lost >= helpers (Acquire)
616/// acquires every live helper's writes
617/// ... KAT trace here, if any: everyone is parked ...
618/// next_lane = 0; arrived = 0
619/// generation.store(next, Release) publishes all of it to everyone
620/// ```
621///
622/// The transitivity is the point. A helper's block writes happen-before its
623/// `Release` on `arrived`; the leader's `Acquire` on `arrived` makes them
624/// happen-before everything it does next, which includes its `Release` on
625/// `generation`; and every *other* helper's `Acquire` on `generation` therefore
626/// sees them. So lane 0's slice-`N` writes are visible to lane 3 in slice
627/// `N + 1`, which is exactly the guarantee `thread::scope`'s join gave for free.
628///
629/// # Panics, and why `lost` exists
630///
631/// A spin barrier turns a worker that never arrives into a hang, and a hang is
632/// a far worse failure than a panic. `Bail`'s `Drop` marks a helper lost on the
633/// way out of an unwind; the leader stops waiting, sets `stop`, and returns, at
634/// which point `thread::scope` joins and re-raises the original panic. Nothing
635/// in `fill_segment` is supposed to panic — but "supposed to" is not a
636/// scheduling primitive.
637#[cfg(feature = "parallel")]
638struct FillSync {
639 /// Lanes handed out for the current slice. Reset by the leader.
640 next_lane: core::sync::atomic::AtomicU32,
641 /// Helpers that have finished the current slice.
642 arrived: core::sync::atomic::AtomicU32,
643 /// Bumped once per sync point; helpers wait for their own count to match.
644 generation: core::sync::atomic::AtomicU32,
645 /// Helpers that unwound out of the fill and will never arrive again.
646 lost: core::sync::atomic::AtomicU32,
647 /// Tells parked helpers to give up so the scope can join and propagate.
648 stop: core::sync::atomic::AtomicBool,
649 /// Helpers the OS actually gave us, which is what the leader waits for.
650 helpers: u32,
651}
652
653/// Iterations of `pause` before falling back to `yield_now`.
654///
655/// A segment is thousands of blocks, so an arriving worker is normally a few
656/// microseconds ahead of the last one and spinning wins outright. Past that the
657/// box is oversubscribed — 4 vCPU here is 2 physical cores plus SMT — and
658/// yielding the slot to the worker we are waiting for is strictly better than
659/// stealing issue bandwidth from its SMT sibling.
660#[cfg(feature = "parallel")]
661const SPIN_LIMIT: u32 = 1024;
662
663#[cfg(feature = "parallel")]
664impl FillSync {
665 /// Wait until `cond()` holds, spinning then yielding.
666 #[inline]
667 fn park_until(mut cond: impl FnMut() -> bool) {
668 let mut spins = 0u32;
669 while !cond() {
670 if spins < SPIN_LIMIT {
671 spins += 1;
672 core::hint::spin_loop();
673 } else {
674 std::thread::yield_now();
675 }
676 }
677 }
678}
679
680/// Fill every lane of one `(pass, slice)` this worker can claim.
681///
682/// # Safety
683///
684/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute whatever
685/// instruction set `fill` needs.
686#[cfg(feature = "parallel")]
687unsafe fn drain_lanes(
688 shared: SharedInstance<'_>,
689 sync: &FillSync,
690 fill: FillSegmentFn,
691 pass: u32,
692 slice: u32,
693 lanes: u32,
694) {
695 use core::sync::atomic::Ordering;
696
697 loop {
698 // Relaxed is enough: the counter only partitions work, and the
699 // happens-before the *data* needs comes from the barrier below.
700 let lane = sync.next_lane.fetch_add(1, Ordering::Relaxed);
701 if lane >= lanes {
702 return;
703 }
704 // SAFETY: as in `fill_slice_st`, plus the cross-lane argument written
705 // out at `unsafe impl Send for SharedInstance`: this worker owns lane
706 // `lane` of slice `slice` for the whole call, and no other worker was
707 // handed the same index.
708 unsafe { fill(shared.0, Position::new(pass, lane, slice, 0)) };
709 }
710}
711
712/// The whole pass/slice/lane nest, on a worker pool that outlives every slice.
713///
714/// Replaces `fill_memory_blocks_mt()` (`core.c:311-357`), which spawns one
715/// thread per lane *per slice* and caps concurrency by joining
716/// `thread[l - threads]` before creating thread `l`. This spawns
717/// `min(threads, lanes)` workers **once for the entire hash**; they pull lanes
718/// off a counter and meet at [`FillSync`]'s barrier at each of the `4 * passes`
719/// sync points. Same bound on live threads, same (absent) ordering requirement
720/// between lanes of one slice, same sync points — and `threads` never affects
721/// the tag, only `lanes` does, so the schedules are interchangeable.
722///
723/// # Safety
724///
725/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute whatever
726/// instruction set `fill` needs.
727#[cfg(feature = "parallel")]
728unsafe fn fill_pooled(instance: &Instance, fill: FillSegmentFn, mut trace: Option<PassTrace<'_>>) {
729 use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
730
731 let lanes = instance.lanes;
732 let passes = instance.passes;
733 // `Instance::new` already clamps to `min(threads, lanes)`; re-clamp so a
734 // hand-built `Instance` cannot ask for more workers than there are lanes.
735 let workers = instance.threads.min(lanes);
736
737 let shared = SharedInstance(instance);
738 let sync = FillSync {
739 next_lane: AtomicU32::new(0),
740 arrived: AtomicU32::new(0),
741 generation: AtomicU32::new(0),
742 lost: AtomicU32::new(0),
743 stop: AtomicBool::new(false),
744 // Optimistic: the shortfall from any thread the OS refuses is charged
745 // to `lost` right after the spawn loop, which the leader's wait
746 // condition already accounts for.
747 helpers: workers.saturating_sub(1),
748 };
749 let sync = &sync;
750
751 std::thread::scope(|scope| {
752 /// Releases every parked helper when the leader leaves the scope,
753 /// however it leaves.
754 ///
755 /// Without this the crate deadlocks on any leader unwind — a panicking
756 /// KAT trace callback is enough to reach it. The helpers would be
757 /// spinning inside `park_until` for a `generation` bump that is never
758 /// coming, and `Scope`'s own `Drop` would block for ever trying to join
759 /// them. `thread::scope` propagating a panic is only useful if the
760 /// threads it is joining can still finish.
761 ///
762 /// Firing on the normal path too is harmless and is why this is a guard
763 /// rather than a `catch_unwind`: by then every helper has completed its
764 /// last slice and is on its way out, so `stop` only shortens a wait
765 /// whose answer has already been decided.
766 struct ReleaseHelpers<'a>(&'a FillSync);
767 impl Drop for ReleaseHelpers<'_> {
768 fn drop(&mut self) {
769 self.0.stop.store(true, Ordering::Relaxed);
770 self.0.generation.fetch_add(1, Ordering::Release);
771 }
772 }
773 let _release = ReleaseHelpers(sync);
774
775 let mut spawned = 0u32;
776
777 for _ in 1..workers {
778 // `Builder::spawn_scoped` returns an error where `scope.spawn`
779 // would panic, and this crate must not panic. A refused thread just
780 // means fewer workers: every lane is still claimed from `next_lane`
781 // by whoever does exist, and the tag does not depend on the count.
782 let handle = std::thread::Builder::new().spawn_scoped(scope, move || {
783 // Marks this helper lost if it unwinds, so the leader stops
784 // waiting for a barrier arrival that will never come.
785 struct Bail<'a>(&'a AtomicU32, bool);
786 impl Drop for Bail<'_> {
787 fn drop(&mut self) {
788 if self.1 {
789 self.0.fetch_add(1, Ordering::Release);
790 }
791 }
792 }
793 let mut bail = Bail(&sync.lost, true);
794
795 let mut generation = 0u32;
796 'outer: for pass in 0..passes {
797 for slice in 0..SYNC_POINTS {
798 // SAFETY: forwarded from this function's contract — the
799 // caller guarantees the CPU can execute `fill`. The
800 // cross-thread half is the `unsafe impl Send for
801 // SharedInstance` argument above.
802 unsafe { drain_lanes(shared, sync, fill, pass, slice, lanes) };
803
804 // Release: publishes this helper's block writes to the
805 // leader's acquire below.
806 sync.arrived.fetch_add(1, Ordering::Release);
807 generation += 1;
808 FillSync::park_until(|| {
809 sync.generation.load(Ordering::Acquire) == generation
810 || sync.stop.load(Ordering::Relaxed)
811 });
812 if sync.stop.load(Ordering::Relaxed) {
813 break 'outer;
814 }
815 }
816 }
817 bail.1 = false;
818 });
819 if handle.is_ok() {
820 spawned += 1;
821 }
822 }
823
824 // A thread the OS refused must not be waited for. `helpers` was set
825 // optimistically; charge the shortfall to `lost`, which the leader's
826 // wait condition already accounts for.
827 sync.lost
828 .fetch_add(sync.helpers - spawned, Ordering::Relaxed);
829
830 let mut generation = 0u32;
831 'outer: for pass in 0..passes {
832 for slice in 0..SYNC_POINTS {
833 // SAFETY: as in the spawned workers; the leader is just one
834 // more of them.
835 unsafe { drain_lanes(shared, sync, fill, pass, slice, lanes) };
836
837 // Acquire: makes every helper's slice writes visible here, and
838 // therefore — through the release on `generation` below — to
839 // every other helper in the next slice.
840 FillSync::park_until(|| {
841 sync.arrived.load(Ordering::Acquire) + sync.lost.load(Ordering::Acquire)
842 >= sync.helpers
843 });
844 if sync.lost.load(Ordering::Relaxed) > sync.helpers - spawned {
845 // A helper unwound. Stop cleanly so `thread::scope` can
846 // join it and re-raise the panic, rather than spinning for
847 // ever on an arrival that is never coming.
848 sync.stop.store(true, Ordering::Relaxed);
849 sync.generation.fetch_add(1, Ordering::Release);
850 break 'outer;
851 }
852
853 // genkat.c `internal_kat(instance, r)`, printed after each
854 // pass. This is the one place it can go: every helper is parked
855 // on `generation`, holding no reference into the arena, and
856 // none of them can move until the release below.
857 if slice == SYNC_POINTS - 1
858 && let Some(callback) = trace.as_mut()
859 {
860 // SAFETY: three obligations, and none of them is "the arena
861 // is zero".
862 //
863 // 1. Valid for `memory_len()` `Block`s: that is
864 // `Instance::new`'s own contract, discharged by whoever
865 // built `instance`.
866 // 2. Every block is *initialised*, which is what makes a
867 // `&[Block]` over them a valid reference. `Arena`
868 // guarantees that for its whole capacity from birth —
869 // `alloc_zeroed`, or a kernel-zeroed `MAP_ANONYMOUS`
870 // mapping — and never gives it up.
871 // 3. No live `&mut Block`: every helper has arrived at the
872 // barrier for the last slice of this pass and is parked
873 // inside `park_until`, so every `&mut Block` any of
874 // them formed is dead. This shared slice is the only
875 // live reference into the arena.
876 let blocks = unsafe {
877 core::slice::from_raw_parts(
878 instance.memory_ptr().cast_const(),
879 instance.memory_len(),
880 )
881 };
882 callback(pass, blocks);
883 }
884
885 sync.next_lane.store(0, Ordering::Relaxed);
886 sync.arrived.store(0, Ordering::Relaxed);
887 generation += 1;
888 // Release: hands every helper everything acquired above.
889 sync.generation.store(generation, Ordering::Release);
890 }
891 }
892 });
893 // Leaving the scope joins every worker, and re-raises a helper's panic.
894}
895
896// ---------------------------------------------------------------------------
897// finalize
898// ---------------------------------------------------------------------------
899
900/// `finalize()` from `src/core.c`.
901///
902/// XORs the last block of every lane together, stores it little-endian, and runs
903/// `blake2b_long` over the 1024 bytes into `out`.
904///
905/// Freeing the arena — the `free_memory()` at the end of the C's `finalize` — is
906/// [`Arena`]'s `Drop`, which wipes before it deallocates. On the pooled path
907/// (`Hasher`) the same wipe happens in [`Workspace::release`] instead, and
908/// only the `dealloc` is deferred; either way the arena is wiped by the time the
909/// call that owned it returns.
910///
911/// # Errors
912///
913/// [`Error::IncorrectParameter`] for a degenerate instance whose last blocks are
914/// out of bounds; otherwise only a BLAKE2b parameter error, which cannot happen
915/// for a validated `outlen`.
916pub fn finalize(instance: &Instance, out: &mut [u8]) -> Result<(), Error> {
917 let lane_length = instance.lane_length as usize;
918 if lane_length == 0 || instance.lanes == 0 {
919 return Err(Error::IncorrectParameter);
920 }
921
922 // SAFETY: `Instance`'s contract guarantees `memory_ptr()` is valid for
923 // `memory_len()` initialised `Block`s. `fill_memory_blocks` has returned, so
924 // every worker is joined and no `&mut Block` into the arena is live; this
925 // shared slice is the only live reference to it.
926 let blocks = unsafe {
927 core::slice::from_raw_parts(instance.memory_ptr().cast_const(), instance.memory_len())
928 };
929
930 // core.c:160 `copy_block(&blockhash, instance->memory + instance->lane_length - 1);`
931 let Some(&last_of_lane_0) = blocks.get(lane_length - 1) else {
932 return Err(Error::IncorrectParameter);
933 };
934 let mut blockhash = last_of_lane_0;
935
936 // core.c:163-167. XOR the last block of every other lane.
937 for lane in 1..instance.lanes {
938 // `l * instance->lane_length + (instance->lane_length - 1)`
939 let index = (lane as usize)
940 .checked_mul(lane_length)
941 .and_then(|base| base.checked_add(lane_length - 1))
942 .ok_or(Error::IncorrectParameter)?;
943 match blocks.get(index) {
944 Some(block) => blockhash.xor_with(block),
945 None => return Err(Error::IncorrectParameter),
946 }
947 }
948
949 // core.c:171-174 `store_block(blockhash_bytes, &blockhash);`
950 // `blake2b_long(context->out, context->outlen,
951 // blockhash_bytes, ARGON2_BLOCK_SIZE);`
952 let mut blockhash_bytes = blockhash.to_le_bytes();
953 let result = blake2b_long(out, &blockhash_bytes);
954
955 // core.c:176-177, on every path.
956 clear_internal_memory_u64(&mut blockhash.0);
957 clear_internal_memory(&mut blockhash_bytes);
958
959 result
960}
961
962/// `argon2_compare()` from `src/argon2.c`: a constant-time byte comparison.
963///
964/// Returns `false` immediately for differing lengths (the C never compares
965/// mismatched lengths — `outlen` is fixed by the decoded string).
966#[must_use]
967pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
968 if a.len() != b.len() {
969 return false;
970 }
971
972 let mut d = 0u8;
973 for (x, y) in a.iter().zip(b.iter()) {
974 d |= x ^ y;
975 }
976
977 // The accumulator is laundered before it is tested. Nothing downstream may
978 // learn that `d` is only ever compared against zero, because a compiler
979 // that knows *that* is free to rewrite the loop above into a `bcmp` that
980 // stops at the first differing byte — which is precisely the timing leak
981 // this function exists to prevent.
982 //
983 // What this actually emits on aarch64 (`--emit=asm`, release):
984 //
985 // LBB_2: ldrb w9,[x0],#1 ; ldrb w10,[x2],#1
986 // eor w9,w10,w9 ; orr w8,w9,w8
987 // subs x1,x1,#1 ; b.ne LBB_2 <- branch on the COUNTER
988 // strb w8,[sp,#15]; ldrb w8,[sp,#15] <- the black_box launder
989 // sub w8,w8,#1 ; ubfx w0,w8,#8,#1 <- branchless verdict
990 //
991 // One branch, and it is the loop counter; no `bcmp`, no `memcmp`, nothing
992 // that depends on the bytes. The launder costs two instructions once per
993 // call.
994 //
995 // `black_box` rather than the `asm!` barrier in `memory::secure_wipe_raw`
996 // because the thing being protected is a value in a register, not a store
997 // to memory, and because it exists on every target — including wasm and
998 // under Miri, where `asm!` does not.
999 let d = core::hint::black_box(d);
1000
1001 // argon2.c:246 `return (int)((1 & ((d - 1) >> 8)) - 1);` — 0 when `d == 0`,
1002 // -1 otherwise, computed without a branch. Written out rather than reduced
1003 // to `d == 0` so the constant-time property is on the page, not implied.
1004 let verdict = (1i32 & ((i32::from(d) - 1) >> 8)) - 1;
1005 verdict == 0
1006}
1007
1008// ---------------------------------------------------------------------------
1009// Public API
1010// ---------------------------------------------------------------------------
1011
1012/// Salt length used by the `*_with_random_salt` entry points, in bytes.
1013///
1014/// 16 is what RFC 9106 §4 recommends for password hashing, and is comfortably
1015/// above [`crate::params::MIN_SALT_LENGTH`]. It is a constant rather than an
1016/// argument because a caller who wants to choose the length also wants to
1017/// choose the bytes, and should call [`Argon2::hash_encoded`] with their own
1018/// salt instead.
1019#[cfg(feature = "std")]
1020pub const RANDOM_SALT_LEN: usize = 16;
1021
1022/// Longest salt the `*_bounded` verify entry points will accept, in bytes.
1023///
1024/// [`Params`] carries no salt length, so the pre-decode size gate in
1025/// [`Argon2::verify_encoded_bounded`] needs one number from somewhere. This is
1026/// it: generous enough that no real producer is near it — RFC 9106 recommends
1027/// 16 and [`RANDOM_SALT_LEN`] uses that — and small enough that a hostile string
1028/// cannot turn the decode into an allocation worth caring about.
1029///
1030/// A legitimate string with a salt longer than this is rejected with
1031/// [`Error::DecodingLengthFail`]; use the unbounded
1032/// [`Argon2::verify_encoded`] if you genuinely have one.
1033pub const BOUNDED_MAX_SALT_LEN: u32 = 1024;
1034
1035/// A configured Argon2 hasher.
1036///
1037/// Bundles the algorithm, version and validated [`Params`]. The secret (key) and
1038/// associated data are passed per call rather than stored, which keeps `Argon2`
1039/// free of lifetime parameters.
1040///
1041/// # Examples
1042///
1043/// ```
1044/// use argon2_rust::{Algorithm, Argon2, Params, Version};
1045///
1046/// // m=19456 KiB, t=2, 1 lane, 32-byte tag: `Params::default()`, which is
1047/// // what a password store should start from. About 8 ms per hash in release,
1048/// // so this runs as a real doctest rather than only being compiled.
1049/// let params = Params::default();
1050/// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1051/// let mut tag = [0u8; 32];
1052/// argon2.hash_into(b"password", b"somesalt", &mut tag)?;
1053/// assert_eq!(argon2.verify(b"password", b"somesalt", &tag), Ok(()));
1054/// # Ok::<(), argon2_rust::Error>(())
1055/// ```
1056///
1057/// # Two spellings
1058///
1059/// Three entry points carry a password-flavoured alias, and only three:
1060/// [`Argon2::hash_password_into`] for [`Argon2::hash_into`],
1061/// [`Argon2::hash_password`] for [`Argon2::hash_encoded`], and
1062/// [`Argon2::verify_password`] for [`Argon2::verify_encoded`]. Each of those
1063/// three is a pure delegation, same function and same bytes.
1064///
1065/// Six other entry points have a base name and nothing else: [`Argon2::hash`],
1066/// [`Argon2::verify`], [`Argon2::hash_into_with_ad`],
1067/// [`Argon2::verify_encoded_with_ad`], [`Argon2::verify_encoded_bounded`] and
1068/// [`Argon2::verify_encoded_bounded_with_ad`]. One runs the other way:
1069/// `Argon2::hash_password_with_random_salt` has a password name with no base
1070/// twin, and it is not a delegation either. It draws a fresh salt from the OS
1071/// before calling [`Argon2::hash_encoded`], so two calls with one password do
1072/// not return the same string.
1073///
1074/// Where the alias does exist, the two families do not spell the *output
1075/// format* the same way:
1076///
1077/// ```text
1078/// raw -> caller buffer raw -> Vec PHC -> String
1079/// base: hash_into hash hash_encoded
1080/// password: hash_password_into (none) hash_password
1081/// ^ raw ^ PHC
1082/// ```
1083///
1084/// [`Argon2::hash_password_into`] writes a **raw** tag into `out`, byte for
1085/// byte what [`Argon2::hash_into`] writes. [`Argon2::hash_password`] returns a
1086/// **PHC string**, character for character what [`Argon2::hash_encoded`]
1087/// returns. The only difference between those two names is `_into`, which reads
1088/// as a destination and not as a format; in the base family the word `encoded`
1089/// carries that distinction in the name, and in the password family nothing
1090/// does. Verification is the same shape: [`Argon2::verify_password`] takes a
1091/// PHC string, like [`Argon2::verify_encoded`], not the raw expected tag that
1092/// [`Argon2::verify`] takes.
1093///
1094/// There is no raw-`Vec` password spelling, which is the empty cell above; for
1095/// that shape the only name is [`Argon2::hash`].
1096///
1097/// ```
1098/// use argon2_rust::{Algorithm, Argon2, Params, Version};
1099///
1100/// let params = Params::new(1 << 8, 1, 1, 32)?;
1101/// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1102///
1103/// // `_into` picks the destination, and with it the raw format.
1104/// let mut raw = [0u8; 32];
1105/// argon2.hash_password_into(b"password", b"somesalt", &mut raw)?;
1106///
1107/// // No suffix at all, and the format changes to PHC.
1108/// let phc = argon2.hash_password(b"password", b"somesalt")?;
1109/// assert!(phc.starts_with("$argon2id$v=19$m=256,t=1,p=1$c29tZXNhbHQ$"));
1110///
1111/// // One tag underneath both: `raw` is the bytes the string base64s.
1112/// assert_eq!(argon2.hash(b"password", b"somesalt")?, raw);
1113/// # Ok::<(), argon2_rust::Error>(())
1114/// ```
1115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1116pub struct Argon2 {
1117 algorithm: Algorithm,
1118 version: Version,
1119 params: Params,
1120}
1121
1122impl Argon2 {
1123 /// Build a hasher. `params` is already validated, so this cannot fail.
1124 #[inline]
1125 #[must_use]
1126 pub const fn new(algorithm: Algorithm, version: Version, params: Params) -> Argon2 {
1127 Argon2 {
1128 algorithm,
1129 version,
1130 params,
1131 }
1132 }
1133
1134 /// The configured algorithm.
1135 #[inline]
1136 #[must_use]
1137 pub const fn algorithm(&self) -> Algorithm {
1138 self.algorithm
1139 }
1140
1141 /// The configured version.
1142 #[inline]
1143 #[must_use]
1144 pub const fn version(&self) -> Version {
1145 self.version
1146 }
1147
1148 /// The configured parameters.
1149 #[inline]
1150 #[must_use]
1151 pub const fn params(&self) -> &Params {
1152 &self.params
1153 }
1154
1155 /// A `Hasher`: this configuration plus scratch memory it keeps between
1156 /// calls.
1157 ///
1158 /// Allocates nothing — the first hash allocates the arena, and every hash
1159 /// after that reuses it. Use this when one thread hashes repeatedly;
1160 /// keep using [`Argon2::hash_into`] and friends when it does not.
1161 ///
1162 /// ```
1163 /// use argon2_rust::{Algorithm, Argon2, Params, Version};
1164 ///
1165 /// let params = Params::new(8, 1, 1, 32)?;
1166 /// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1167 ///
1168 /// let mut hasher = argon2.hasher();
1169 /// let mut tag = [0u8; 32];
1170 /// for salt in [&b"somesalt"[..], &b"othersaltx"[..]] {
1171 /// hasher.hash_into(b"password", salt, &mut tag)?;
1172 /// }
1173 ///
1174 /// // Same answer as the one-shot API, every time.
1175 /// let mut once = [0u8; 32];
1176 /// argon2.hash_into(b"password", b"somesalt", &mut once)?;
1177 /// hasher.hash_into(b"password", b"somesalt", &mut tag)?;
1178 /// assert_eq!(tag, once);
1179 /// # Ok::<(), argon2_rust::Error>(())
1180 /// ```
1181 #[inline]
1182 #[must_use]
1183 pub fn hasher(&self) -> Hasher {
1184 Hasher {
1185 argon2: *self,
1186 workspace: Workspace::new(),
1187 }
1188 }
1189
1190 /// Derive a tag into `out`.
1191 ///
1192 /// `out.len()` must equal [`Params::output_len`].
1193 ///
1194 /// ```
1195 /// use argon2_rust::{Algorithm, Argon2, Error, Params, Version};
1196 ///
1197 /// let params = Params::new(64, 1, 1, 32)?;
1198 /// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1199 ///
1200 /// let mut tag = [0u8; 32];
1201 /// argon2.hash_into(b"password", b"somesalt", &mut tag)?;
1202 ///
1203 /// // Those 32 bytes are what the PHC string base64s, so pinning the string
1204 /// // pins the tag without spelling out an array of hex.
1205 /// assert_eq!(
1206 /// argon2.hash_encoded(b"password", b"somesalt")?,
1207 /// "$argon2id$v=19$m=64,t=1,p=1$c29tZXNhbHQ$cpx6VEQbwTVZvcpxNIxOVUWZ5xnAipUmAe1cg2GMG70",
1208 /// );
1209 ///
1210 /// // `out.len()` is checked against `Params::output_len`, never used to
1211 /// // size the tag: a buffer of the wrong length is an error, not a
1212 /// // truncated hash.
1213 /// let mut too_short = [0u8; 16];
1214 /// assert_eq!(
1215 /// argon2.hash_into(b"password", b"somesalt", &mut too_short),
1216 /// Err(Error::OutPtrMismatch),
1217 /// );
1218 /// # Ok::<(), argon2_rust::Error>(())
1219 /// ```
1220 ///
1221 /// # Errors
1222 ///
1223 /// Whatever [`Params::validate_for`] returns, [`Error::OutPtrMismatch`] if
1224 /// `out.len()` disagrees with `params.output_len()`, or
1225 /// [`Error::MemoryAllocationError`].
1226 pub fn hash_into(&self, pwd: &[u8], salt: &[u8], out: &mut [u8]) -> Result<(), Error> {
1227 self.hash_into_with_ad(pwd, salt, &[], &[], out)
1228 }
1229
1230 /// Derive a tag into `out`, with a secret key and associated data.
1231 ///
1232 /// No PHC-emitting entry point accepts either; see the "Secret and
1233 /// associated data" section of [`Argon2::hash_encoded`].
1234 ///
1235 /// # Errors
1236 ///
1237 /// As [`Argon2::hash_into`].
1238 pub fn hash_into_with_ad(
1239 &self,
1240 pwd: &[u8],
1241 salt: &[u8],
1242 secret: &[u8],
1243 ad: &[u8],
1244 out: &mut [u8],
1245 ) -> Result<(), Error> {
1246 // SAFETY: the only `Backend` the public API ever names is
1247 // `fill_block::backend()`, the cached result of the
1248 // `is_*_feature_detected!` cascade, so this CPU can execute it by
1249 // construction. That is what keeps this — and every other public entry
1250 // point — safe while `hash_inner` is not.
1251 unsafe {
1252 hash_inner(
1253 crate::fill_block::backend(),
1254 self.algorithm,
1255 self.version,
1256 &self.params,
1257 pwd,
1258 salt,
1259 secret,
1260 ad,
1261 out,
1262 )
1263 }
1264 }
1265
1266 /// Derive a tag of [`Params::output_len`] bytes.
1267 ///
1268 /// ```
1269 /// use argon2_rust::{Algorithm, Argon2, Params, Version};
1270 ///
1271 /// let params = Params::new(64, 1, 1, 32)?;
1272 /// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1273 ///
1274 /// // The `Vec` is sized from the parameters, so there is no buffer to get
1275 /// // wrong and no `Error::OutPtrMismatch` to handle.
1276 /// let tag = argon2.hash(b"password", b"somesalt")?;
1277 /// assert_eq!(tag.len(), argon2.params().output_len());
1278 ///
1279 /// // Byte for byte what `hash_into` writes into a buffer you own; this is
1280 /// // the same function with the allocation moved inside.
1281 /// let mut into = [0u8; 32];
1282 /// argon2.hash_into(b"password", b"somesalt", &mut into)?;
1283 /// assert_eq!(tag, into);
1284 /// # Ok::<(), argon2_rust::Error>(())
1285 /// ```
1286 ///
1287 /// # Errors
1288 ///
1289 /// As [`Argon2::hash_into`].
1290 pub fn hash(&self, pwd: &[u8], salt: &[u8]) -> Result<Vec<u8>, Error> {
1291 let mut out = try_zeroed_vec(self.params.output_len())?;
1292 self.hash_into(pwd, salt, &mut out)?;
1293 Ok(out)
1294 }
1295
1296 /// Derive a tag and format it as a PHC string.
1297 ///
1298 /// Always emits `$v=`, exactly as `encode_string()` in the C does, even for
1299 /// [`Version::V0x10`].
1300 ///
1301 /// # Secret and associated data
1302 ///
1303 /// No PHC-emitting entry point takes a `secret` (pepper) or `ad`, because a
1304 /// PHC string has a field for neither: `argon2_hash()` (`argon2.h:322`)
1305 /// hardcodes `context.secret = NULL; context.ad = NULL` (`argon2.c:139-142`)
1306 /// and `encode_string` emits only `$type$v=$m=,t=,p=$salt$hash`. A peppered
1307 /// deployment must call [`Argon2::hash_into_with_ad`] and encode the tag
1308 /// itself — `mod encoding` is private, so that means the PHC layout and
1309 /// unpadded Base64 by hand. Its string is indistinguishable from an
1310 /// unpeppered one, so [`Argon2::verify_encoded`] on it answers
1311 /// [`Error::VerifyMismatch`] rather than any "missing pepper" signal.
1312 ///
1313 /// # Errors
1314 ///
1315 /// As [`Argon2::hash_into`], plus [`Error::EncodingFail`].
1316 pub fn hash_encoded(&self, pwd: &[u8], salt: &[u8]) -> Result<String, Error> {
1317 let mut tag = self.hash(pwd, salt)?;
1318 let encoded = crate::encoding::encode_string_alloc(
1319 self.algorithm,
1320 self.version,
1321 &self.params,
1322 salt,
1323 &tag,
1324 );
1325 // argon2.c:173 `clear_internal_memory(out, hashlen);`
1326 clear_internal_memory(&mut tag);
1327 encoded
1328 }
1329
1330 /// Recompute the tag and compare it with `expected` in constant time.
1331 ///
1332 /// A length mismatch is a [`Error::VerifyMismatch`], not a separate error:
1333 /// the C cannot reach that case, because `decode_string` sets
1334 /// `context->outlen` from the tag it just decoded.
1335 ///
1336 /// ```
1337 /// use argon2_rust::{Algorithm, Argon2, Error, Params, Version};
1338 ///
1339 /// let params = Params::new(64, 1, 1, 32)?;
1340 /// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1341 ///
1342 /// // A raw tag stored earlier, alongside the salt that produced it. The
1343 /// // parameters are yours to remember too, which is what the PHC string
1344 /// // from `hash_encoded` saves you.
1345 /// let expected = argon2.hash(b"password", b"somesalt")?;
1346 /// assert_eq!(argon2.verify(b"password", b"somesalt", &expected), Ok(()));
1347 ///
1348 /// // Wrong password.
1349 /// assert_eq!(
1350 /// argon2.verify(b"wrong", b"somesalt", &expected),
1351 /// Err(Error::VerifyMismatch),
1352 /// );
1353 /// // Wrong salt: the tag is a function of both.
1354 /// assert_eq!(
1355 /// argon2.verify(b"password", b"othersalt", &expected),
1356 /// Err(Error::VerifyMismatch),
1357 /// );
1358 /// // A truncated `expected` is that same error and not a length error,
1359 /// // exactly as the paragraph above says.
1360 /// assert_eq!(
1361 /// argon2.verify(b"password", b"somesalt", &expected[..16]),
1362 /// Err(Error::VerifyMismatch),
1363 /// );
1364 /// # Ok::<(), argon2_rust::Error>(())
1365 /// ```
1366 ///
1367 /// # Errors
1368 ///
1369 /// As [`Argon2::hash_into`], or [`Error::VerifyMismatch`].
1370 pub fn verify(&self, pwd: &[u8], salt: &[u8], expected: &[u8]) -> Result<(), Error> {
1371 let mut computed = try_zeroed_vec(self.params.output_len())?;
1372 let result = self.hash_into(pwd, salt, &mut computed);
1373 // argon2.c:349 `argon2_compare(hash, context->out, context->outlen)`.
1374 let matched = result.is_ok() && constant_time_eq(&computed, expected);
1375 clear_internal_memory(&mut computed);
1376
1377 result?;
1378 if matched {
1379 Ok(())
1380 } else {
1381 Err(Error::VerifyMismatch)
1382 }
1383 }
1384
1385 /// `argon2_verify()`: decode a PHC string and check `pwd` against it.
1386 ///
1387 /// # Errors
1388 ///
1389 /// [`Error::DecodingFail`] for a malformed string, [`Error::VerifyMismatch`]
1390 /// if the password is wrong, or any hashing error.
1391 pub fn verify_encoded(encoded: &str, pwd: &[u8], algorithm: Algorithm) -> Result<(), Error> {
1392 // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
1393 if pwd.len() > MAX_PWD_LENGTH as usize {
1394 return Err(Error::PwdTooLong);
1395 }
1396
1397 // argon2.c:289 `decode_string(&ctx, encoded, type)`.
1398 let decoded = crate::encoding::decode_string(encoded, algorithm)?;
1399
1400 // argon2.c:302 `argon2_verify_ctx(&ctx, desired_result, type)`.
1401 Argon2::new(decoded.algorithm, decoded.version, decoded.params).verify(
1402 pwd,
1403 &decoded.salt,
1404 &decoded.hash,
1405 )
1406 }
1407
1408 /// `argon2_verify_ctx()`: decode a PHC string and check `pwd` against it,
1409 /// with a secret key and associated data.
1410 ///
1411 /// # Errors
1412 ///
1413 /// As [`Argon2::verify_encoded`], plus the secret/ad validation errors of
1414 /// [`Argon2::hash_into_with_ad`].
1415 pub fn verify_encoded_with_ad(
1416 encoded: &str,
1417 pwd: &[u8],
1418 secret: &[u8],
1419 ad: &[u8],
1420 algorithm: Algorithm,
1421 ) -> Result<(), Error> {
1422 // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
1423 if pwd.len() > MAX_PWD_LENGTH as usize {
1424 return Err(Error::PwdTooLong);
1425 }
1426
1427 // argon2.c:289 `decode_string(&ctx, encoded, type)`.
1428 let decoded = crate::encoding::decode_string(encoded, algorithm)?;
1429
1430 // argon2.c:302 `argon2_verify_ctx(&ctx, desired_result, type)`.
1431 let argon2 = Argon2::new(decoded.algorithm, decoded.version, decoded.params);
1432 let mut computed = try_zeroed_vec(argon2.params.output_len())?;
1433 let result =
1434 argon2.hash_into_with_ad(pwd, &decoded.salt, secret, ad, &mut computed);
1435 let matched = result.is_ok() && constant_time_eq(&computed, &decoded.hash);
1436 clear_internal_memory(&mut computed);
1437
1438 result?;
1439 if matched {
1440 Ok(())
1441 } else {
1442 Err(Error::VerifyMismatch)
1443 }
1444 }
1445
1446 // -----------------------------------------------------------------
1447 // Password-flavoured spellings of the three entry points above
1448 // -----------------------------------------------------------------
1449 //
1450 // Same functions, the names the C's three public entry points suggest:
1451 // `argon2_hash` with a raw output buffer, `argon2_hash` with an encoded
1452 // output buffer, and `argon2_verify`. They exist so a caller can read the
1453 // API as "hash a password" rather than "hash some bytes"; the shorter
1454 // spellings stay because that is what this crate's own tests and benches
1455 // already call.
1456 //
1457 // That last half is an internal reason. The user-facing one is that these
1458 // are the names a C caller already knows: the per-algorithm wrappers it
1459 // links against are `argon2id_hash_raw` (argon2.c:230),
1460 // `argon2id_hash_encoded` (argon2.c:219) and `argon2id_verify`
1461 // (argon2.c:325), each a single `return` into `argon2_hash`/`argon2_verify`
1462 // and nothing else in the body. Only `argon2id_verify` fits on one line
1463 // (argon2.c:327); the two hash wrappers each spend three on the argument
1464 // list alone (argon2.c:225-227 and argon2.c:234-236), which is line
1465 // wrapping and not work. The raw/encoded choice is made entirely by which
1466 // out-pointer those wrappers pass as non-NULL (argon2.c:160 `if (hash)`,
1467 // argon2.c:165 `if (encoded && encodedlen)`).
1468 //
1469 // Note what the C's names do that these do not: they carry the output
1470 // format, `raw` against `encoded`. Here the only difference between
1471 // `hash_password_into` and `hash_password` is `_into`, which names a
1472 // destination, not a format. So the format asymmetry is stated on the type
1473 // (`Argon2`'s `# Two spellings`) and again on the first line of each method
1474 // below, where a reader scanning the method list will actually see it.
1475
1476 /// Derive a **raw** tag into `out`, not a PHC string.
1477 ///
1478 /// `argon2_hash()` with `hash != NULL` (`argon2.c:160`). The same function
1479 /// as [`Argon2::hash_into`]: `_into` picks the destination, and the format
1480 /// that comes with it is bytes. `out.len()` must equal
1481 /// [`Params::output_len`]. For the PHC string, [`Argon2::hash_password`].
1482 ///
1483 /// # Errors
1484 ///
1485 /// As [`Argon2::hash_into`].
1486 #[inline]
1487 pub fn hash_password_into(&self, pwd: &[u8], salt: &[u8], out: &mut [u8]) -> Result<(), Error> {
1488 self.hash_into(pwd, salt, out)
1489 }
1490
1491 /// Derive a tag and return the **PHC string** for it, not the raw bytes.
1492 ///
1493 /// `argon2_hash()` with `encoded != NULL` (`argon2.c:165`). The same
1494 /// function as [`Argon2::hash_encoded`]; for the raw tag, its sibling
1495 /// [`Argon2::hash_password_into`] or [`Argon2::hash`].
1496 ///
1497 /// Always emits `$v=`, just like `encode_string()` in the C, even for
1498 /// [`Version::V0x10`] — the `v=0x10` reference strings in `src/test.c`
1499 /// predate that field, so they have no `$v=` and are one field shorter than
1500 /// what this returns. Both forms decode, see [`Argon2::verify_password`].
1501 ///
1502 /// # Errors
1503 ///
1504 /// As [`Argon2::hash_into`], plus [`Error::EncodingFail`].
1505 #[inline]
1506 pub fn hash_password(&self, pwd: &[u8], salt: &[u8]) -> Result<String, Error> {
1507 self.hash_encoded(pwd, salt)
1508 }
1509
1510 /// Derive a PHC string with a fresh salt from the OS entropy source.
1511 ///
1512 /// Convenience for the common case where the caller does not manage its own
1513 /// salt. The salt is [`RANDOM_SALT_LEN`] bytes — the length RFC 9106 §4
1514 /// recommends — and lands in the returned string, so verification needs
1515 /// nothing else kept alongside it.
1516 ///
1517 /// The randomness comes straight from the OS, with the entry point chosen
1518 /// per platform (`getrandom(2)`, `getentropy`, `CCRandomGenerateBytes`,
1519 /// `ProcessPrng`, WASI `random_get`, or `/dev/urandom`) and declared by
1520 /// hand, so this costs the crate no dependency. Callers who already run
1521 /// their own CSPRNG should keep passing their own salt to
1522 /// [`Argon2::hash_encoded`].
1523 ///
1524 /// Hashing many passwords? [`Hasher::hash_password_with_random_salt`] does
1525 /// this over a pooled arena.
1526 ///
1527 /// # Errors
1528 ///
1529 /// [`Error::OsRandom`] if every OS entropy source for this platform fails,
1530 /// plus the errors of [`Argon2::hash_encoded`].
1531 #[cfg(feature = "std")]
1532 pub fn hash_password_with_random_salt(&self, pwd: &[u8]) -> Result<String, Error> {
1533 // Not wiped on the way out, deliberately, and unlike every other
1534 // buffer in this file: the salt is *published* in the returned string,
1535 // so scrubbing the stack copy protects nothing that is not already in
1536 // the caller's hands. `clear_internal_memory` is for secret-derived
1537 // material; a salt is not that.
1538 let mut salt = [0u8; RANDOM_SALT_LEN];
1539 crate::random::os_random(&mut salt)?;
1540 self.hash_encoded(pwd, &salt)
1541 }
1542
1543 /// Check `pwd` against a **PHC string**, not against a raw tag.
1544 ///
1545 /// `argon2_verify()` (`argon2.c:249`): decode `encoded`, then recompute and
1546 /// compare. The same function as [`Argon2::verify_encoded`]. The parameters
1547 /// come out of the string, so nothing on `self` is consulted, which is why
1548 /// this is an associated function. To check a raw expected tag with these
1549 /// parameters instead, [`Argon2::verify`].
1550 ///
1551 /// # Errors
1552 ///
1553 /// [`Error::DecodingFail`] for a malformed string, [`Error::VerifyMismatch`]
1554 /// if the password is wrong, or any hashing error.
1555 #[inline]
1556 pub fn verify_password(encoded: &str, pwd: &[u8], algorithm: Algorithm) -> Result<(), Error> {
1557 Argon2::verify_encoded(encoded, pwd, algorithm)
1558 }
1559
1560 /// [`Argon2::verify_encoded`], refusing costs above `ceiling` **before**
1561 /// allocating anything.
1562 ///
1563 /// # Why this exists
1564 ///
1565 /// `m_cost` in a PHC string is up to ten decimal digits, and the decoder
1566 /// accepts everything the C accepts — up to
1567 /// [`MAX_MEMORY`](crate::params::MAX_MEMORY) KiB, which is 4 TiB. Nothing in
1568 /// [`Argon2::verify_encoded`] sits between that number and the allocation,
1569 /// because nothing does in `argon2_verify` either; on a login endpoint,
1570 /// where the string is whatever a database row (or a request) contained,
1571 /// that is a one-line denial of service. `t_cost` is the same story in CPU
1572 /// time rather than bytes.
1573 ///
1574 /// The plain entry points keep exact C parity and are the right choice when
1575 /// the string is trusted — a config file, a fixture, your own output. This
1576 /// one is for when it is not.
1577 ///
1578 /// ```
1579 /// use argon2_rust::{Algorithm, Argon2, Params, Version};
1580 ///
1581 /// let hostile = "$argon2id$v=19$m=4294967295,t=1,p=1$c29tZXNhbHQ$\
1582 /// CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
1583 /// // 64 MiB, 8 passes, 4 lanes is far more than any sane stored hash.
1584 /// let ceiling = Params::new(1 << 16, 8, 4, 32)?;
1585 ///
1586 /// let err = Argon2::verify_encoded_bounded(
1587 /// hostile, b"password", Algorithm::Argon2id, &ceiling,
1588 /// ).unwrap_err();
1589 /// // Rejected on the parameters, without ever asking for 4 TiB.
1590 /// assert_eq!(err, argon2_rust::Error::MemoryTooMuch);
1591 /// # Ok::<(), argon2_rust::Error>(())
1592 /// ```
1593 ///
1594 /// # What is bounded
1595 ///
1596 /// Both the cost *and* the allocation. The length of `encoded` is checked
1597 /// against the longest string `ceiling` could have produced — with
1598 /// [`BOUNDED_MAX_SALT_LEN`] allowed for the salt — **before** the decoder
1599 /// runs, because the decoder sizes its salt and tag buffers from the input.
1600 /// Then the decoded parameters are held to all four of the ceiling's
1601 /// numbers.
1602 ///
1603 /// # Worker threads
1604 ///
1605 /// `ceiling.threads()` bounds them, and it is a *fifth*, independent knob —
1606 /// none of the four checks above implies it. Decoding sets `threads = lanes`
1607 /// (C parity), so the string's own `p` would otherwise choose how many OS
1608 /// threads this call spawns. A ceiling built with [`Params::new`] has
1609 /// `threads == lanes` and so bounds them together; use
1610 /// [`Params::new_with_threads`] to allow wide strings without spawning
1611 /// wide:
1612 ///
1613 /// ```
1614 /// use argon2_rust::{Params};
1615 /// // Accept up to 256 lanes, but never run more than 2 workers.
1616 /// let ceiling = Params::new_with_threads(1 << 16, 8, 256, 2, 32)?;
1617 /// # Ok::<(), argon2_rust::Error>(())
1618 /// ```
1619 ///
1620 /// Clamping is always safe: `threads` is a scheduling knob that cannot
1621 /// change the tag — only `lanes` can — so a bounded verify accepts exactly
1622 /// the same strings whatever the budget.
1623 ///
1624 /// # Errors
1625 ///
1626 /// The errors of [`Argon2::verify_encoded`], plus — checked in this order,
1627 /// and reusing the C's own codes rather than inventing new ones —
1628 /// [`Error::DecodingLengthFail`] if `encoded` is longer than `ceiling` could
1629 /// have produced, [`Error::OutputTooLong`] if the decoded tag is longer than
1630 /// `ceiling.output_len()`, [`Error::MemoryTooMuch`] if the decoded `m_cost`
1631 /// exceeds `ceiling.m_cost()`, [`Error::TimeTooLarge`] if `t_cost` exceeds
1632 /// `ceiling.t_cost()`, and [`Error::LanesTooMany`] if `lanes` exceeds
1633 /// `ceiling.lanes()`.
1634 pub fn verify_encoded_bounded(
1635 encoded: &str,
1636 pwd: &[u8],
1637 algorithm: Algorithm,
1638 ceiling: &Params,
1639 ) -> Result<(), Error> {
1640 // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
1641 if pwd.len() > MAX_PWD_LENGTH as usize {
1642 return Err(Error::PwdTooLong);
1643 }
1644
1645 let decoded = decode_bounded(encoded, algorithm, ceiling)?;
1646
1647 Argon2::new(decoded.algorithm, decoded.version, decoded.params).verify(
1648 pwd,
1649 &decoded.salt,
1650 &decoded.hash,
1651 )
1652 }
1653
1654 /// [`Argon2::verify_encoded_with_ad`] with the cost ceiling of
1655 /// [`Argon2::verify_encoded_bounded`].
1656 ///
1657 /// A keyed deployment is *more* likely to be the one parsing untrusted
1658 /// strings, not less, so the bounded form exists for both.
1659 ///
1660 /// # Errors
1661 ///
1662 /// As [`Argon2::verify_encoded_bounded`], plus the secret/ad validation
1663 /// errors of [`Argon2::hash_into_with_ad`].
1664 pub fn verify_encoded_bounded_with_ad(
1665 encoded: &str,
1666 pwd: &[u8],
1667 secret: &[u8],
1668 ad: &[u8],
1669 algorithm: Algorithm,
1670 ceiling: &Params,
1671 ) -> Result<(), Error> {
1672 // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
1673 if pwd.len() > MAX_PWD_LENGTH as usize {
1674 return Err(Error::PwdTooLong);
1675 }
1676
1677 let decoded = decode_bounded(encoded, algorithm, ceiling)?;
1678
1679 let argon2 = Argon2::new(decoded.algorithm, decoded.version, decoded.params);
1680 let mut computed = try_zeroed_vec(argon2.params.output_len())?;
1681 let result = argon2.hash_into_with_ad(pwd, &decoded.salt, secret, ad, &mut computed);
1682 let matched = result.is_ok() && constant_time_eq(&computed, &decoded.hash);
1683 clear_internal_memory(&mut computed);
1684
1685 result?;
1686 if matched {
1687 Ok(())
1688 } else {
1689 Err(Error::VerifyMismatch)
1690 }
1691 }
1692}
1693
1694/// Decode `encoded` and hold it to `ceiling`, for the `*_bounded` entry points.
1695///
1696/// # Why the length gate comes first
1697///
1698/// Checking the ceiling *after* decoding is not enough, and an earlier revision
1699/// of this function got that wrong. [`crate::encoding::decode_string`] sizes its
1700/// salt and tag buffers from the input string, so the decode itself is an
1701/// attacker-controlled allocation before any ceiling is consulted. Measured with
1702/// an allocator spy against the previous version: a well-formed string with
1703/// `m=8,t=1,p=1` and a 16 MiB Base64 tag peaked at **36 MiB** of live
1704/// allocation, then ran a full Argon2 and a 12 MiB comparison — under a ceiling
1705/// whose `output_len` was 32. Every cost was inside the ceiling; the tag was
1706/// never looked at.
1707///
1708/// So the size of the string is checked against what the ceiling could
1709/// legitimately produce *before* anything is parsed, and the decoded tag length
1710/// is then checked against `ceiling.output_len()` as well. A ceiling is four
1711/// numbers, and all four now mean something.
1712fn decode_bounded(
1713 encoded: &str,
1714 algorithm: Algorithm,
1715 ceiling: &Params,
1716) -> Result<crate::encoding::Decoded, Error> {
1717 // The longest string the ceiling could have produced. `num_len` is monotone
1718 // in its argument and the costs are themselves capped below, so taking the
1719 // ceiling's own values gives a true upper bound. `encoded_len` counts the
1720 // C's NUL, so this is permissive by exactly one byte.
1721 let max_encoded = crate::encoding::encoded_len(
1722 algorithm,
1723 ceiling.t_cost(),
1724 ceiling.m_cost(),
1725 ceiling.lanes(),
1726 BOUNDED_MAX_SALT_LEN,
1727 // `output_len` is bounded by MAX_OUTLEN, so this cast cannot truncate.
1728 ceiling.output_len() as u32,
1729 );
1730 if encoded.len() > max_encoded {
1731 // ARGON2_DECODING_LENGTH_FAIL: "Some of encoded parameters are too long
1732 // or too short". The C defines it for exactly this and never returns it;
1733 // it is the right code and it costs no new error variant.
1734 return Err(Error::DecodingLengthFail);
1735 }
1736
1737 let mut decoded = crate::encoding::decode_string(encoded, algorithm)?;
1738
1739 if decoded.params.output_len() > ceiling.output_len() {
1740 return Err(Error::OutputTooLong);
1741 }
1742 if decoded.params.m_cost() > ceiling.m_cost() {
1743 return Err(Error::MemoryTooMuch);
1744 }
1745 if decoded.params.t_cost() > ceiling.t_cost() {
1746 return Err(Error::TimeTooLarge);
1747 }
1748 if decoded.params.lanes() > ceiling.lanes() {
1749 return Err(Error::LanesTooMany);
1750 }
1751
1752 // The four checks above do **not** imply a worker-thread bound, and the
1753 // ceiling's `threads` is a separate field precisely so a caller can say
1754 // "allow wide strings, but never spawn wide". `decode_string` sets
1755 // `threads = lanes` (C parity, `argon2.c`), and `fill_pooled` spawns
1756 // `min(threads, lanes) - 1` helpers — so without this clamp a `p=256`
1757 // string inside a `lanes` ceiling of 256 spawns 255 OS threads even when
1758 // the ceiling asked for one worker. That is attacker-chosen concurrency on
1759 // an authentication path.
1760 //
1761 // Lowering `threads` is free: it is a pure scheduling knob that cannot
1762 // change the tag (only `lanes` can), which `threads_do_not_change_the_tag`
1763 // pins across both versions and all three algorithms.
1764 //
1765 // `min` with `lanes` keeps the value meaningful rather than merely legal —
1766 // workers above the lane count have nothing to claim — and cannot underflow
1767 // the `MIN_THREADS = 1` floor, because a validated ceiling has
1768 // `threads >= 1` and a decoded string has `lanes >= 1`.
1769 let threads = ceiling.threads().min(decoded.params.lanes());
1770 if threads != decoded.params.threads() {
1771 decoded.params = Params::new_with_threads(
1772 decoded.params.m_cost(),
1773 decoded.params.t_cost(),
1774 decoded.params.lanes(),
1775 threads,
1776 decoded.params.output_len(),
1777 )?;
1778 }
1779 Ok(decoded)
1780}
1781
1782// ---------------------------------------------------------------------------
1783// Hasher — the same API, over memory that survives the call
1784// ---------------------------------------------------------------------------
1785
1786/// An [`Argon2`] that keeps its block arena between calls.
1787///
1788/// Build one with [`Argon2::hasher`]. Every method mirrors the [`Argon2`]
1789/// method of the same name and returns the same bytes; the only difference is
1790/// that the arena is borrowed from a pool instead of allocated and freed each
1791/// time. Nothing else about the computation changes — same backend dispatch,
1792/// same threading, same wipe.
1793///
1794/// ```
1795/// use argon2_rust::{Algorithm, Argon2, Params, Version};
1796///
1797/// let params = Params::new(1 << 8, 1, 1, 32)?;
1798/// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1799/// let mut hasher = argon2.hasher();
1800///
1801/// let encoded = hasher.hash_encoded(b"password", b"somesalt")?;
1802/// assert!(hasher.verify_encoded(&encoded, b"password", Algorithm::Argon2id).is_ok());
1803/// # Ok::<(), argon2_rust::Error>(())
1804/// ```
1805///
1806/// # What it is worth, measured
1807///
1808/// Reuse skips the `mmap`, the first-touch page faults over the whole arena,
1809/// and the `munmap`. Interleaved A/B against [`Argon2::hash_into`], 15 paired
1810/// rounds on Linux/x86-64 (Sapphire Rapids, AVX-512):
1811///
1812/// ```text
1813/// m_cost t p | one-shot | pooled | delta
1814/// ---------|-----|-----------|-----------|--------
1815/// 8 KiB 1 1 | 20.4 us | 20.3 us | -0.7%
1816/// 64 KiB 1 1 | 27.9 us | 26.5 us | -5.3%
1817/// 1 MiB 1 1 | 212 us | 185 us | -11.7%
1818/// 4 MiB 1 1 | 989 us | 786 us | -19.9%
1819/// 4 MiB 1 4 | 806 us | 592 us | -26.9%
1820/// 64 MiB 1 1 | 25.89 ms| 19.43 ms | -24.9%
1821/// 64 MiB 1 4 | 11.65 ms| 8.40 ms | -34.0%
1822/// 256 MiB 1 1 | 111.74 ms| 86.17 ms | -23.3%
1823/// 256 MiB 1 4 | 46.14 ms| 35.09 ms | -24.0%
1824/// 256 MiB 3 4 | 109.85 ms| 99.26 ms | -9.7%
1825/// ```
1826///
1827/// The `t = 3` rows are smaller for the obvious reason: the same one-time
1828/// acquisition is spread over three passes of filling.
1829///
1830/// It does **not** remove allocator calls — there was only ever one per hash,
1831/// 1.7 us out of 306 ms at `m_cost = 1 GiB`.
1832///
1833/// # Wiping
1834///
1835/// Unchanged from the one-shot API. The arena is wiped when the call that
1836/// borrowed it returns — success, `?` error or unwind alike — so the window in
1837/// which a password's derived material is resident is exactly as long as it was
1838/// before. What reuse changes is that the wipe now doubles as the *next* call's
1839/// zeroing, instead of being followed by a fresh `alloc_zeroed` that zeroes
1840/// again.
1841///
1842/// Dropping the `Hasher` releases the arena to the allocator, wiped.
1843///
1844/// # Threading
1845///
1846/// One `Hasher` per thread. It is [`Send`], so it can move to whichever worker
1847/// picks up a request, and deliberately **not** [`Sync`]: two threads hashing
1848/// through one `Hasher` would be two hashes sharing one arena. The multi-lane
1849/// fill inside a single hash is unaffected — one [`std::thread::scope`] owns
1850/// its helper pool for the whole fill, over the arena this `Hasher` lent it for
1851/// the duration of that one call.
1852///
1853/// ```compile_fail
1854/// # use argon2_rust::{Algorithm, Argon2, Params, Version};
1855/// # let params = Params::new(8, 1, 1, 32).unwrap();
1856/// # let hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher();
1857/// fn needs_sync<T: Sync>(_: &T) {}
1858/// needs_sync(&hasher);
1859/// ```
1860///
1861/// # Two spellings
1862///
1863/// Every alias mirrors [`Argon2`], trap included: [`Hasher::hash_password_into`]
1864/// writes a **raw** tag while [`Hasher::hash_password`] returns a **PHC
1865/// string**, because `_into` names a destination and not a format. See
1866/// [`Argon2`'s section of the same name](Argon2#two-spellings) for the table.
1867///
1868/// ```
1869/// use argon2_rust::{Algorithm, Argon2, Params, Version};
1870///
1871/// let params = Params::new(1 << 8, 1, 1, 32)?;
1872/// let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher();
1873///
1874/// // Same prefix, same arena, different return type and different format.
1875/// let mut raw = [0u8; 32];
1876/// hasher.hash_password_into(b"password", b"somesalt", &mut raw)?;
1877/// let phc = hasher.hash_password(b"password", b"somesalt")?;
1878///
1879/// assert!(phc.starts_with("$argon2id$v=19$m=256,t=1,p=1$c29tZXNhbHQ$"));
1880/// assert_eq!(hasher.hash(b"password", b"somesalt")?, raw);
1881/// # Ok::<(), argon2_rust::Error>(())
1882/// ```
1883pub struct Hasher {
1884 argon2: Argon2,
1885 workspace: Workspace,
1886}
1887
1888impl Hasher {
1889 /// The configuration this hasher applies.
1890 #[inline]
1891 #[must_use]
1892 pub const fn argon2(&self) -> &Argon2 {
1893 &self.argon2
1894 }
1895
1896 /// Point the hasher at a different configuration, keeping the memory.
1897 ///
1898 /// For a process that has to hash at more than one parameter set — a
1899 /// password migration, say. The arena grows if the new `m_cost` needs more
1900 /// blocks and is kept as-is if it needs fewer, so the steady state is one
1901 /// allocation sized to the largest configuration seen.
1902 #[inline]
1903 pub fn set_argon2(&mut self, argon2: Argon2) {
1904 self.argon2 = argon2;
1905 }
1906
1907 /// The configured algorithm.
1908 #[inline]
1909 #[must_use]
1910 pub const fn algorithm(&self) -> Algorithm {
1911 self.argon2.algorithm
1912 }
1913
1914 /// The configured version.
1915 #[inline]
1916 #[must_use]
1917 pub const fn version(&self) -> Version {
1918 self.argon2.version
1919 }
1920
1921 /// The configured parameters.
1922 #[inline]
1923 #[must_use]
1924 pub const fn params(&self) -> &Params {
1925 &self.argon2.params
1926 }
1927
1928 /// Allocate the arena now instead of during the first hash.
1929 ///
1930 /// Only moves the cost; it does not remove it. Worth doing when the first
1931 /// request must not be the slow one, or to find out at start-up rather than
1932 /// under load that `m_cost` does not fit in memory.
1933 ///
1934 /// # Errors
1935 ///
1936 /// [`Error::MemoryAllocationError`].
1937 pub fn reserve(&mut self) -> Result<(), Error> {
1938 self.workspace.reserve(self.argon2.params.memory_blocks() as usize)
1939 }
1940
1941 /// Blocks of arena the hasher is holding on to. 1 KiB each.
1942 ///
1943 /// 0 before the first hash, or after [`clear`](Hasher::clear). Diagnostic:
1944 /// it is how a test proves that reuse is actually happening.
1945 #[inline]
1946 #[must_use]
1947 pub fn reserved_blocks(&self) -> usize {
1948 self.workspace.capacity()
1949 }
1950
1951 /// Give the arena back to the allocator, wiped, and keep the configuration.
1952 ///
1953 /// For a worker going idle that would rather not sit on `m_cost` KiB. The
1954 /// next hash allocates again.
1955 pub fn clear(&mut self) {
1956 self.workspace.clear();
1957 }
1958
1959 /// Derive a tag into `out`. [`Argon2::hash_into`], reusing the arena.
1960 ///
1961 /// ```
1962 /// use argon2_rust::{Algorithm, Argon2, Params, Version};
1963 ///
1964 /// let params = Params::new(64, 1, 1, 32)?;
1965 /// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1966 /// let mut hasher = argon2.hasher();
1967 ///
1968 /// // `Argon2::hasher` allocates nothing; the first hash sizes the arena.
1969 /// assert_eq!(hasher.reserved_blocks(), 0);
1970 ///
1971 /// let mut tags = Vec::new();
1972 /// for pwd in [&b"first"[..], &b"second"[..]] {
1973 /// let mut tag = [0u8; 32];
1974 /// hasher.hash_into(pwd, b"somesalt", &mut tag)?;
1975 /// tags.push(tag);
1976 /// }
1977 ///
1978 /// // Two hashes, one arena: 64 blocks of 1 KiB, the `m_cost` above. The
1979 /// // second call neither allocated nor grew it.
1980 /// assert_eq!(hasher.reserved_blocks(), 64);
1981 /// assert_ne!(tags[0], tags[1]);
1982 ///
1983 /// // Reuse changes where the memory came from and nothing else.
1984 /// assert_eq!(argon2.hash(b"second", b"somesalt")?, tags[1]);
1985 /// # Ok::<(), argon2_rust::Error>(())
1986 /// ```
1987 ///
1988 /// # Errors
1989 ///
1990 /// As [`Argon2::hash_into`].
1991 #[inline]
1992 pub fn hash_into(&mut self, pwd: &[u8], salt: &[u8], out: &mut [u8]) -> Result<(), Error> {
1993 self.hash_into_with_ad(pwd, salt, &[], &[], out)
1994 }
1995
1996 /// [`Argon2::hash_into_with_ad`], reusing the arena.
1997 ///
1998 /// # Errors
1999 ///
2000 /// As [`Argon2::hash_into`].
2001 pub fn hash_into_with_ad(
2002 &mut self,
2003 pwd: &[u8],
2004 salt: &[u8],
2005 secret: &[u8],
2006 ad: &[u8],
2007 out: &mut [u8],
2008 ) -> Result<(), Error> {
2009 let argon2 = self.argon2;
2010 self.hash_into_using(&argon2, pwd, salt, secret, ad, out)
2011 }
2012
2013 /// [`Argon2::hash`], reusing the arena.
2014 ///
2015 /// # Errors
2016 ///
2017 /// As [`Argon2::hash_into`].
2018 pub fn hash(&mut self, pwd: &[u8], salt: &[u8]) -> Result<Vec<u8>, Error> {
2019 let mut out = try_zeroed_vec(self.argon2.params.output_len())?;
2020 self.hash_into(pwd, salt, &mut out)?;
2021 Ok(out)
2022 }
2023
2024 /// [`Argon2::hash_encoded`], reusing the arena.
2025 ///
2026 /// # Errors
2027 ///
2028 /// As [`Argon2::hash_into`], plus [`Error::EncodingFail`].
2029 pub fn hash_encoded(&mut self, pwd: &[u8], salt: &[u8]) -> Result<String, Error> {
2030 let argon2 = self.argon2;
2031 let mut tag = self.hash(pwd, salt)?;
2032 let encoded = crate::encoding::encode_string_alloc(
2033 argon2.algorithm,
2034 argon2.version,
2035 &argon2.params,
2036 salt,
2037 &tag,
2038 );
2039 // argon2.c:173 `clear_internal_memory(out, hashlen);`
2040 clear_internal_memory(&mut tag);
2041 encoded
2042 }
2043
2044 /// [`Argon2::verify`], reusing the arena.
2045 ///
2046 /// # Errors
2047 ///
2048 /// As [`Argon2::hash_into`], or [`Error::VerifyMismatch`].
2049 pub fn verify(&mut self, pwd: &[u8], salt: &[u8], expected: &[u8]) -> Result<(), Error> {
2050 let argon2 = self.argon2;
2051 self.verify_using(&argon2, pwd, salt, expected)
2052 }
2053
2054 /// [`Argon2::verify_encoded`], reusing the arena.
2055 ///
2056 /// The parameters come from `encoded`, **not** from this hasher — that is
2057 /// what verifying a stored PHC string means, and it is what lets one hasher
2058 /// check strings written at several different `m_cost`s.
2059 ///
2060 /// ```
2061 /// use argon2_rust::{Algorithm, Argon2, Error, Params, Version};
2062 ///
2063 /// let params = Params::new(64, 1, 1, 32)?;
2064 /// let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher();
2065 ///
2066 /// // Registration: one string, carrying the salt and the parameters.
2067 /// let stored = hasher.hash_encoded(b"password", b"somesalt")?;
2068 /// assert_eq!(
2069 /// stored,
2070 /// "$argon2id$v=19$m=64,t=1,p=1$c29tZXNhbHQ$cpx6VEQbwTVZvcpxNIxOVUWZ5xnAipUmAe1cg2GMG70",
2071 /// );
2072 ///
2073 /// // Two logins, over the arena the registration already paid for.
2074 /// assert_eq!(
2075 /// hasher.verify_encoded(&stored, b"password", Algorithm::Argon2id),
2076 /// Ok(()),
2077 /// );
2078 /// assert_eq!(
2079 /// hasher.verify_encoded(&stored, b"wrong", Algorithm::Argon2id),
2080 /// Err(Error::VerifyMismatch),
2081 /// );
2082 ///
2083 /// // The string's `m=64` is not above what this hasher already holds, so
2084 /// // the pool served both verifies and did not grow. See below for what
2085 /// // happens when a decoded `m_cost` is larger.
2086 /// assert_eq!(hasher.reserved_blocks(), 64);
2087 /// # Ok::<(), argon2_rust::Error>(())
2088 /// ```
2089 ///
2090 /// # The string cannot grow this hasher — but it can still be huge
2091 ///
2092 /// Read this one first: what follows bounds what an untrusted `m_cost` can
2093 /// **retain**, and nothing at all about what it can **allocate**. A decoded
2094 /// `m_cost` of `0xFFFFFFFF` still asks for a 4 TiB arena here, exactly as it
2095 /// does in [`Argon2::verify_encoded`] and exactly as it does in the C. If
2096 /// `encoded` comes from anywhere an attacker can write, bound it first —
2097 /// [`Hasher::verify_encoded_bounded`] does that — or the process dies on the
2098 /// allocation regardless of everything below.
2099 ///
2100 /// `encoded` is untrusted input: on a login endpoint it is whatever the
2101 /// database row said, and a `m_cost` field is four bytes of decimal that can
2102 /// ask for 4 TiB. A pooled arena is *retained*, so if a decoded `m_cost`
2103 /// were allowed to size it, one string would set a permanent high-water mark
2104 /// on a long-lived per-worker hasher — memory the process never gives back,
2105 /// chosen by the caller rather than by this hasher's owner.
2106 ///
2107 /// So it is not allowed to. A decoded `m_cost` that fits in memory this
2108 /// hasher already holds — [`reserved_blocks`](Hasher::reserved_blocks), or
2109 /// the [`params`](Hasher::params) it is configured for — is served from the
2110 /// pool as usual. One that would have to *grow* the pool gets a private
2111 /// arena instead, allocated, wiped and freed inside this call exactly as
2112 /// [`Argon2::verify_encoded`] does. Verifying still works at any `m_cost`
2113 /// the decoder accepts — including ones that will not fit in this machine.
2114 /// It just cannot leave anything behind.
2115 ///
2116 /// That mirrors the C, where `finalize()` ends every `argon2_ctx` with
2117 /// `free_memory(...)` (`core.c:184`), so `argon2_verify` never retains an
2118 /// arena sized by the string it was handed.
2119 ///
2120 /// To verify *and* keep the memory — a migration that re-hashes upward, say
2121 /// — call [`set_argon2`](Hasher::set_argon2) first. Then the size is the
2122 /// owner's choice, which is the whole distinction being drawn here.
2123 ///
2124 /// # Errors
2125 ///
2126 /// As [`Argon2::verify_encoded`].
2127 pub fn verify_encoded(
2128 &mut self,
2129 encoded: &str,
2130 pwd: &[u8],
2131 algorithm: Algorithm,
2132 ) -> Result<(), Error> {
2133 // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
2134 if pwd.len() > MAX_PWD_LENGTH as usize {
2135 return Err(Error::PwdTooLong);
2136 }
2137
2138 // argon2.c:289 `decode_string(&ctx, encoded, type)`.
2139 let decoded = crate::encoding::decode_string(encoded, algorithm)?;
2140
2141 // argon2.c:302 `argon2_verify_ctx(&ctx, desired_result, type)`.
2142 let argon2 = Argon2::new(decoded.algorithm, decoded.version, decoded.params);
2143
2144 if decoded.params.memory_blocks() as usize > self.pooled_ceiling() {
2145 // Bigger than any arena this hasher's *owner* asked for. Run it on a
2146 // private arena that is freed on the way out, so an attacker-chosen
2147 // `m_cost` cannot pin memory to a worker for the rest of its life.
2148 return argon2.verify(pwd, &decoded.salt, &decoded.hash);
2149 }
2150 self.verify_using(&argon2, pwd, &decoded.salt, &decoded.hash)
2151 }
2152
2153 /// [`Argon2::verify_encoded_with_ad`], reusing the arena.
2154 ///
2155 /// # Errors
2156 ///
2157 /// As [`Argon2::verify_encoded_with_ad`].
2158 pub fn verify_encoded_with_ad(
2159 &mut self,
2160 encoded: &str,
2161 pwd: &[u8],
2162 secret: &[u8],
2163 ad: &[u8],
2164 algorithm: Algorithm,
2165 ) -> Result<(), Error> {
2166 // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
2167 if pwd.len() > MAX_PWD_LENGTH as usize {
2168 return Err(Error::PwdTooLong);
2169 }
2170
2171 // argon2.c:289 `decode_string(&ctx, encoded, type)`.
2172 let decoded = crate::encoding::decode_string(encoded, algorithm)?;
2173
2174 // argon2.c:302 `argon2_verify_ctx(&ctx, desired_result, type)`.
2175 let argon2 = Argon2::new(decoded.algorithm, decoded.version, decoded.params);
2176
2177 if decoded.params.memory_blocks() as usize > self.pooled_ceiling() {
2178 // As `verify_encoded`: keep an attacker-chosen `m_cost` off the
2179 // pooled arena by running on a one-shot arena instead.
2180 let mut computed = try_zeroed_vec(argon2.params.output_len())?;
2181 let result =
2182 argon2.hash_into_with_ad(pwd, &decoded.salt, secret, ad, &mut computed);
2183 let matched = result.is_ok() && constant_time_eq(&computed, &decoded.hash);
2184 clear_internal_memory(&mut computed);
2185 result?;
2186 return if matched {
2187 Ok(())
2188 } else {
2189 Err(Error::VerifyMismatch)
2190 };
2191 }
2192 self.verify_using_ad(&argon2, pwd, &decoded.salt, secret, ad, &decoded.hash)
2193 }
2194
2195 // -----------------------------------------------------------------
2196 // Password-flavoured spellings, matching `Argon2`'s
2197 // -----------------------------------------------------------------
2198
2199 /// Derive a **raw** tag into `out`, not a PHC string, reusing the arena.
2200 ///
2201 /// [`Argon2::hash_password_into`] over pooled memory, which is the same
2202 /// function as [`Hasher::hash_into`]. `out.len()` must equal
2203 /// [`Params::output_len`]. For the PHC string, [`Hasher::hash_password`].
2204 ///
2205 /// # Errors
2206 ///
2207 /// As [`Argon2::hash_into`].
2208 #[inline]
2209 pub fn hash_password_into(
2210 &mut self,
2211 pwd: &[u8],
2212 salt: &[u8],
2213 out: &mut [u8],
2214 ) -> Result<(), Error> {
2215 self.hash_into(pwd, salt, out)
2216 }
2217
2218 /// Derive a tag and return the **PHC string** for it, reusing the arena.
2219 ///
2220 /// [`Argon2::hash_password`] over pooled memory, which is the same function
2221 /// as [`Hasher::hash_encoded`]. For the raw tag instead, its sibling
2222 /// [`Hasher::hash_password_into`] or [`Hasher::hash`].
2223 ///
2224 /// # Errors
2225 ///
2226 /// As [`Argon2::hash_into`], plus [`Error::EncodingFail`].
2227 #[inline]
2228 pub fn hash_password(&mut self, pwd: &[u8], salt: &[u8]) -> Result<String, Error> {
2229 self.hash_encoded(pwd, salt)
2230 }
2231
2232 /// Derive a **PHC string** with a fresh salt from the OS entropy source,
2233 /// reusing the arena.
2234 ///
2235 /// [`Argon2::hash_password_with_random_salt`] over pooled memory, which is
2236 /// [`Hasher::hash_encoded`] with a [`RANDOM_SALT_LEN`]-byte salt drawn for
2237 /// you and carried in the returned string. There is no raw-tag counterpart:
2238 /// a caller who keeps the tag has to keep the salt too, and then generating
2239 /// it here saves nothing.
2240 ///
2241 /// This is the spelling that matters for the case the type exists to serve:
2242 /// a long-lived per-worker hasher registering many users, where every hash
2243 /// wants both the pooled arena *and* a fresh salt.
2244 ///
2245 /// # Errors
2246 ///
2247 /// [`Error::OsRandom`] if every OS entropy source fails, plus the errors of
2248 /// [`Hasher::hash_encoded`].
2249 #[cfg(feature = "std")]
2250 pub fn hash_password_with_random_salt(&mut self, pwd: &[u8]) -> Result<String, Error> {
2251 // Not wiped on the way out, deliberately, and unlike every other
2252 // buffer in this file: the salt is *published* in the returned string,
2253 // so scrubbing the stack copy protects nothing that is not already in
2254 // the caller's hands. `clear_internal_memory` is for secret-derived
2255 // material; a salt is not that.
2256 let mut salt = [0u8; RANDOM_SALT_LEN];
2257 crate::random::os_random(&mut salt)?;
2258 self.hash_encoded(pwd, &salt)
2259 }
2260
2261 /// Check `pwd` against a **PHC string**, not a raw tag, reusing the arena.
2262 ///
2263 /// [`Argon2::verify_password`] over pooled memory, which is the same
2264 /// function as [`Hasher::verify_encoded`] and inherits its pooled-arena
2265 /// rule: a decoded `m_cost` above this hasher's high-water mark runs on a
2266 /// private arena that is freed on the way out, so the string cannot grow
2267 /// the pool. To check a raw expected tag instead, [`Hasher::verify`].
2268 ///
2269 /// # Errors
2270 ///
2271 /// As [`Argon2::verify_encoded`].
2272 #[inline]
2273 pub fn verify_password(
2274 &mut self,
2275 encoded: &str,
2276 pwd: &[u8],
2277 algorithm: Algorithm,
2278 ) -> Result<(), Error> {
2279 self.verify_encoded(encoded, pwd, algorithm)
2280 }
2281
2282 /// [`Argon2::verify_encoded_bounded`], reusing the arena.
2283 ///
2284 /// The ceiling is checked before anything is allocated, so it bounds the
2285 /// *allocation* — which is the half [`Hasher::verify_encoded`] does not
2286 /// address. Note that the pooled-arena rule still applies underneath: a
2287 /// decoded `m_cost` within `ceiling` but above this hasher's own high-water
2288 /// mark runs on a private arena, so passing a generous `ceiling` cannot
2289 /// enlarge the pool either.
2290 ///
2291 /// `ceiling.threads()` bounds the worker threads exactly as it does on
2292 /// [`Argon2::verify_encoded_bounded`] — worth knowing here in particular,
2293 /// since a `Hasher` is what a server holds while verifying strings it did
2294 /// not write, and the arena it reuses is not the only resource a wide `p`
2295 /// can spend.
2296 ///
2297 /// # Errors
2298 ///
2299 /// As [`Argon2::verify_encoded_bounded`].
2300 pub fn verify_encoded_bounded(
2301 &mut self,
2302 encoded: &str,
2303 pwd: &[u8],
2304 algorithm: Algorithm,
2305 ceiling: &Params,
2306 ) -> Result<(), Error> {
2307 // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
2308 if pwd.len() > MAX_PWD_LENGTH as usize {
2309 return Err(Error::PwdTooLong);
2310 }
2311
2312 let decoded = decode_bounded(encoded, algorithm, ceiling)?;
2313
2314 let argon2 = Argon2::new(decoded.algorithm, decoded.version, decoded.params);
2315 if decoded.params.memory_blocks() as usize > self.pooled_ceiling() {
2316 // As `verify_encoded`: keep an m_cost this hasher's owner never
2317 // asked for off the retained arena.
2318 return argon2.verify(pwd, &decoded.salt, &decoded.hash);
2319 }
2320 self.verify_using(&argon2, pwd, &decoded.salt, &decoded.hash)
2321 }
2322
2323 /// [`Argon2::verify_encoded_bounded_with_ad`], reusing the arena.
2324 ///
2325 /// # Errors
2326 ///
2327 /// As [`Argon2::verify_encoded_bounded_with_ad`].
2328 pub fn verify_encoded_bounded_with_ad(
2329 &mut self,
2330 encoded: &str,
2331 pwd: &[u8],
2332 secret: &[u8],
2333 ad: &[u8],
2334 algorithm: Algorithm,
2335 ceiling: &Params,
2336 ) -> Result<(), Error> {
2337 // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
2338 if pwd.len() > MAX_PWD_LENGTH as usize {
2339 return Err(Error::PwdTooLong);
2340 }
2341
2342 let decoded = decode_bounded(encoded, algorithm, ceiling)?;
2343
2344 let argon2 = Argon2::new(decoded.algorithm, decoded.version, decoded.params);
2345 if decoded.params.memory_blocks() as usize > self.pooled_ceiling() {
2346 // As `verify_encoded_with_ad`: an m_cost this hasher's owner never
2347 // asked for runs on a one-shot arena.
2348 let mut computed = try_zeroed_vec(argon2.params.output_len())?;
2349 let result = argon2.hash_into_with_ad(pwd, &decoded.salt, secret, ad, &mut computed);
2350 let matched = result.is_ok() && constant_time_eq(&computed, &decoded.hash);
2351 clear_internal_memory(&mut computed);
2352 result?;
2353 return if matched {
2354 Ok(())
2355 } else {
2356 Err(Error::VerifyMismatch)
2357 };
2358 }
2359 self.verify_using_ad(&argon2, pwd, &decoded.salt, secret, ad, &decoded.hash)
2360 }
2361
2362 // -----------------------------------------------------------------
2363 // The two private workers every public method above funnels through
2364 // -----------------------------------------------------------------
2365
2366 /// The largest arena an *untrusted* `m_cost` may borrow from the pool.
2367 ///
2368 /// Two sources, both chosen by whoever owns this hasher, never by an input:
2369 /// the configuration it was built or [`set_argon2`](Hasher::set_argon2)'d
2370 /// with, and whatever the workspace already holds (which
2371 /// [`reserve`](Hasher::reserve) or an earlier, larger configuration may have
2372 /// made bigger than the current one).
2373 ///
2374 /// The guarantee is a ceiling, not a freeze: a decoded `m_cost` under this
2375 /// bound may still be the thing that allocates the arena, on a hasher whose
2376 /// owner has not hashed yet. What it cannot do is push the retained arena
2377 /// past a size the owner has already asked for — so the worst an input can
2378 /// cost is memory the very next `hash_into` was going to take anyway, and
2379 /// there is no ratchet.
2380 ///
2381 /// The one caller is [`verify_encoded`](Hasher::verify_encoded), because it
2382 /// is the only method whose `m_cost` does not come from `self`.
2383 #[inline]
2384 fn pooled_ceiling(&self) -> usize {
2385 core::cmp::max(
2386 self.workspace.capacity(),
2387 self.argon2.params.memory_blocks() as usize,
2388 )
2389 }
2390
2391 /// `argon2_ctx()` with `argon2`'s configuration and this hasher's memory.
2392 ///
2393 /// `argon2` is passed explicitly rather than read from `self` so that
2394 /// [`verify_encoded`](Hasher::verify_encoded) can use the parameters it
2395 /// decoded from the string. It is [`Copy`], so callers hand in a copy and
2396 /// the borrow checker never has to reconcile it with `&mut self.workspace`.
2397 fn hash_into_using(
2398 &mut self,
2399 argon2: &Argon2,
2400 pwd: &[u8],
2401 salt: &[u8],
2402 secret: &[u8],
2403 ad: &[u8],
2404 out: &mut [u8],
2405 ) -> Result<(), Error> {
2406 // SAFETY: the same argument that makes `Argon2::hash_into_with_ad`
2407 // safe — the only `Backend` this crate's safe API ever names is
2408 // `fill_block::backend()`, the cached result of the
2409 // `is_*_feature_detected!` cascade, so this CPU can execute it by
2410 // construction.
2411 unsafe {
2412 hash_in_workspace(
2413 &mut self.workspace,
2414 crate::fill_block::backend(),
2415 argon2.algorithm,
2416 argon2.version,
2417 &argon2.params,
2418 pwd,
2419 salt,
2420 secret,
2421 ad,
2422 out,
2423 None,
2424 None,
2425 )
2426 }
2427 }
2428
2429 /// [`Argon2::verify`]'s body, over this hasher's memory.
2430 fn verify_using(
2431 &mut self,
2432 argon2: &Argon2,
2433 pwd: &[u8],
2434 salt: &[u8],
2435 expected: &[u8],
2436 ) -> Result<(), Error> {
2437 self.verify_using_ad(argon2, pwd, salt, &[], &[], expected)
2438 }
2439
2440 fn verify_using_ad(
2441 &mut self,
2442 argon2: &Argon2,
2443 pwd: &[u8],
2444 salt: &[u8],
2445 secret: &[u8],
2446 ad: &[u8],
2447 expected: &[u8],
2448 ) -> Result<(), Error> {
2449 let mut computed = try_zeroed_vec(argon2.params.output_len())?;
2450 let result = self.hash_into_using(argon2, pwd, salt, secret, ad, &mut computed);
2451 // argon2.c:349 `argon2_compare(hash, context->out, context->outlen)`.
2452 let matched = result.is_ok() && constant_time_eq(&computed, expected);
2453 clear_internal_memory(&mut computed);
2454
2455 result?;
2456 if matched {
2457 Ok(())
2458 } else {
2459 Err(Error::VerifyMismatch)
2460 }
2461 }
2462}
2463
2464impl core::fmt::Debug for Hasher {
2465 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2466 f.debug_struct("Hasher")
2467 .field("argon2", &self.argon2)
2468 .field("reserved_blocks", &self.reserved_blocks())
2469 .finish()
2470 }
2471}
2472
2473/// A zeroed `Vec<u8>` of `len` bytes, without the abort-on-OOM of
2474/// `Vec::with_capacity`.
2475fn try_zeroed_vec(len: usize) -> Result<Vec<u8>, Error> {
2476 let mut v = Vec::new();
2477 v.try_reserve(len)
2478 .map_err(|_| Error::MemoryAllocationError)?;
2479 // Cannot reallocate: the capacity was just reserved.
2480 v.resize(len, 0);
2481 Ok(v)
2482}
2483
2484/// `argon2_ctx()`: validate, size the arena, initialise, fill, finalise.
2485///
2486/// The one place the whole computation lives; every public entry point funnels
2487/// through here. `backend` is resolved by the caller so the forced-backend test
2488/// hook and the normal path share this body.
2489///
2490/// # Safety
2491///
2492/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute `backend`.
2493// One parameter per `argon2_context` field this port needs; collapsing them into
2494// a struct would just move the same list somewhere else.
2495#[allow(clippy::too_many_arguments)]
2496unsafe fn hash_inner(
2497 backend: Backend,
2498 algorithm: Algorithm,
2499 version: Version,
2500 params: &Params,
2501 pwd: &[u8],
2502 salt: &[u8],
2503 secret: &[u8],
2504 ad: &[u8],
2505 out: &mut [u8],
2506) -> Result<(), Error> {
2507 // SAFETY: forwarded verbatim from this function's own contract.
2508 unsafe {
2509 hash_owned(
2510 backend, algorithm, version, params, pwd, salt, secret, ad, out, None, None,
2511 )
2512 }
2513}
2514
2515/// One-shot hashing over a freshly allocated arena.
2516///
2517/// `h0_out` is `None` on every stable API path. That distinction is
2518/// security-relevant: normal hashing must not materialise a second copy of H0
2519/// merely to throw it away after the computation. The unstable KAT hook passes
2520/// a destination because H0 is one of its requested outputs.
2521///
2522/// # Safety
2523///
2524/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute `backend`.
2525#[allow(clippy::too_many_arguments)]
2526unsafe fn hash_owned(
2527 backend: Backend,
2528 algorithm: Algorithm,
2529 version: Version,
2530 params: &Params,
2531 pwd: &[u8],
2532 salt: &[u8],
2533 secret: &[u8],
2534 ad: &[u8],
2535 out: &mut [u8],
2536 trace: Option<PassTrace<'_>>,
2537 h0_out: Option<&mut [u8; PREHASH_DIGEST_LENGTH]>,
2538) -> Result<(), Error> {
2539 let memory_blocks = validate_and_size(params, pwd, salt, secret, ad, out)?;
2540
2541 // core.c:621 "1. Memory allocation". A fresh allocation every call, freed
2542 // on the way out. `Hasher` runs the same computation over an arena borrowed
2543 // from a `Workspace`.
2544 let mut arena = Arena::new(memory_blocks)?;
2545
2546 // SAFETY: `backend` is forwarded verbatim from this function's own
2547 // contract. `arena` was just sized from the same `params`, and it lives
2548 // until the end of this function, i.e. past every use inside.
2549 unsafe {
2550 hash_in_arena(
2551 &mut arena, backend, algorithm, version, params, pwd, salt, secret, ad, out, trace,
2552 h0_out,
2553 )
2554 }
2555 // `arena` drops here: `Arena::drop` wipes it (`zeroize-memory`) and frees
2556 // it, which is core.c:184's `free_memory(...)`. It drops on `Ok`, `Err` and
2557 // unwind alike. The two `?`s above fire before the arena exists.
2558}
2559
2560/// `argon2_ctx()` with the two hooks `src/genkat.c` needs.
2561///
2562/// Returns the 64-byte pre-hashing digest `H0` that `initial_kat()` prints, and
2563/// invokes `trace(pass, whole_arena)` after each pass, which is what
2564/// `internal_kat()` prints. `tests/kat.rs` reaches this through `__internal`.
2565///
2566/// # Safety
2567///
2568/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute `backend`,
2569/// which `backend.is_available()` or [`crate::fill_block::backend`] establishes.
2570/// Nothing else here is unsafe — validation, allocation and finalisation are all
2571/// ordinary safe code — but a `Backend` this CPU lacks makes the fill loop jump
2572/// into a `#[target_feature]` function it cannot run.
2573///
2574/// # Errors
2575///
2576/// As [`Argon2::hash_into`].
2577#[allow(clippy::too_many_arguments)]
2578pub unsafe fn hash_traced(
2579 backend: Backend,
2580 algorithm: Algorithm,
2581 version: Version,
2582 params: &Params,
2583 pwd: &[u8],
2584 salt: &[u8],
2585 secret: &[u8],
2586 ad: &[u8],
2587 out: &mut [u8],
2588 trace: Option<PassTrace<'_>>,
2589) -> Result<[u8; PREHASH_DIGEST_LENGTH], Error> {
2590 let mut h0 = [0u8; PREHASH_DIGEST_LENGTH];
2591 // SAFETY: forwarded verbatim from this function's own contract.
2592 let result = unsafe {
2593 hash_owned(
2594 backend,
2595 algorithm,
2596 version,
2597 params,
2598 pwd,
2599 salt,
2600 secret,
2601 ad,
2602 out,
2603 trace,
2604 Some(&mut h0),
2605 )
2606 };
2607 if let Err(error) = result {
2608 // H0 was requested as output, but an error means it will not leave this
2609 // function. Do not turn that failed internal trace into stack residue.
2610 clear_internal_memory(&mut h0);
2611 return Err(error);
2612 }
2613 Ok(h0)
2614}
2615
2616/// Steps 1 and 2 of `argon2_ctx()`: validate every input, then align the memory
2617/// size. Returns the block count the arena must have.
2618///
2619/// Split out so that both arena sources — [`hash_traced`]'s one-shot
2620/// [`Arena::new`] and [`Hasher`]'s pooled [`Workspace`] — reject bad input
2621/// *before* anything is allocated, and reject it identically.
2622///
2623/// # Errors
2624///
2625/// Whatever [`Params::validate_for`] returns, or [`Error::OutPtrMismatch`].
2626fn validate_and_size(
2627 params: &Params,
2628 pwd: &[u8],
2629 salt: &[u8],
2630 secret: &[u8],
2631 ad: &[u8],
2632 out: &[u8],
2633) -> Result<usize, Error> {
2634 // argon2.c:41 "1. Validate all inputs".
2635 params.validate_for(pwd.len(), salt.len(), secret.len(), ad.len())?;
2636
2637 // argon2.c:49-51 `ARGON2_INCORRECT_TYPE` cannot fire: `Algorithm` is a
2638 // closed enum, so there is no "no such version of Argon2".
2639 //
2640 // Rust-only check. The C's `context->out` and `context->outlen` are one
2641 // object; here the buffer and the configured length are separate, so they
2642 // can disagree. `ARGON2_OUT_PTR_MISMATCH` is defined in `argon2.h` but
2643 // never returned by the C, which makes it exactly the right code for this.
2644 if out.len() != params.output_len() {
2645 return Err(Error::OutPtrMismatch);
2646 }
2647
2648 // argon2.c:55-70 "2. Align memory size". See `Params::memory_layout`.
2649 Ok(params.memory_layout().0 as usize)
2650}
2651
2652/// Steps 3 to 5 of `argon2_ctx()` over an arena the caller already sized.
2653///
2654/// The whole computation lives here — pre-hash, first blocks, fill, finalise —
2655/// so the one-shot and pooled paths cannot drift apart. Everything they do not
2656/// share is on either side of this call: where the arena came from, and what
2657/// happens to it afterwards.
2658///
2659/// Deliberately does **not** zero the arena. Argon2 does not need it (pass 0
2660/// writes every block before anything reads one) and [`Arena`] already
2661/// guarantees the only property that matters for soundness, which is that every
2662/// block is *initialised*. See the module docs on [`crate::memory`].
2663///
2664/// # Safety
2665///
2666/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute `backend`.
2667///
2668/// # Errors
2669///
2670/// [`Error::MemoryAllocationError`] if `arena.len()` disagrees with
2671/// `params.memory_layout()`, plus whatever [`initial_hash`],
2672/// [`fill_first_blocks`], [`fill_memory_blocks_traced`] and [`finalize`] return.
2673#[allow(clippy::too_many_arguments)]
2674unsafe fn hash_in_arena(
2675 arena: &mut Arena,
2676 backend: Backend,
2677 algorithm: Algorithm,
2678 version: Version,
2679 params: &Params,
2680 pwd: &[u8],
2681 salt: &[u8],
2682 secret: &[u8],
2683 ad: &[u8],
2684 out: &mut [u8],
2685 trace: Option<PassTrace<'_>>,
2686 h0_out: Option<&mut [u8; PREHASH_DIGEST_LENGTH]>,
2687) -> Result<(), Error> {
2688 let (memory_blocks, _segment_length, lane_length) = params.memory_layout();
2689
2690 // `Instance::new`'s safety contract is `memory_len == memory_blocks`, and
2691 // below it is handed `arena.len()`. Both callers size the arena from this
2692 // same `params`, so this can only fire if someone wires up a third one
2693 // wrongly — at which point it must be an error, not undefined behaviour.
2694 // A pooled arena whose *capacity* is larger is fine and expected; it is the
2695 // visible `len()` that has to match.
2696 if arena.len() != memory_blocks as usize {
2697 return Err(Error::MemoryAllocationError);
2698 }
2699
2700 // The release wipe may use as many threads as the caller sanctioned. It
2701 // cannot affect the tag, so `threads()` — the OS-thread budget — is the
2702 // right number here rather than `effective_threads()`, which is
2703 // `min(threads, lanes)` and describes the *algorithmic* parallelism.
2704 arena.set_workers(params.threads());
2705
2706 // core.c:631 "2. Initial hashing". The 8 bytes after `H0` are already zero,
2707 // which is what core.c:633 achieves with `clear_internal_memory`.
2708 let mut blockhash = [0u8; PREHASH_SEED_LENGTH];
2709 if let Err(error) = initial_hash_into(
2710 algorithm,
2711 version,
2712 params,
2713 pwd,
2714 salt,
2715 secret,
2716 ad,
2717 &mut blockhash,
2718 ) {
2719 clear_internal_memory(&mut blockhash);
2720 return Err(error);
2721 }
2722 if let Some(h0) = h0_out {
2723 #[cfg(all(test, feature = "std"))]
2724 H0_COPY_COUNT.with(|count| count.set(count.get() + 1));
2725 h0.copy_from_slice(&blockhash[..PREHASH_DIGEST_LENGTH]);
2726 }
2727
2728 // core.c:643 "3. Creating first blocks".
2729 let fill_first = fill_first_blocks(
2730 &mut blockhash,
2731 arena.as_mut_slice(),
2732 params.lanes(),
2733 lane_length,
2734 );
2735 // core.c:645 `clear_internal_memory(blockhash, ARGON2_PREHASH_SEED_LENGTH);`
2736 clear_internal_memory(&mut blockhash);
2737 fill_first?;
2738
2739 // SAFETY: `arena` is borrowed for the whole of this function and `instance`
2740 // does not escape it, so the arena outlives every use of the pointer. It
2741 // owns `arena.len()` initialised, `ARENA_ALIGN`-aligned `Block`s — that is
2742 // `Arena`'s invariant 1, and it holds for a pooled arena exactly as it does
2743 // for a fresh one, since neither reuse nor the release wipe can
2744 // de-initialise memory. `arena.len() == memory_blocks` was just checked,
2745 // which is `Instance::new`'s remaining requirement.
2746 let instance =
2747 unsafe { Instance::new(arena.as_mut_ptr(), arena.len(), algorithm, version, params) };
2748
2749 // argon2.c:89 "4. Filling memory".
2750 // SAFETY: the CPU's ability to execute `backend` is forwarded verbatim from
2751 // this function's own contract. `instance` was just built from an `Arena`
2752 // that outlives it, and the arena is uniquely borrowed (`&mut Arena`), so no
2753 // other thread holds a handle on it.
2754 unsafe { fill_memory_blocks_traced(&instance, backend, trace) }?;
2755
2756 // argon2.c:95 "5. Finalization". Wiping and releasing the arena is the
2757 // caller's job, and it happens on this function's error paths too because
2758 // both callers do it in a `Drop`.
2759 finalize(&instance, out)?;
2760
2761 Ok(())
2762}
2763
2764/// [`hash_traced`] over an arena borrowed from `workspace` instead of a fresh
2765/// one. The engine behind every [`Hasher`] method.
2766///
2767/// # Safety
2768///
2769/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute `backend`.
2770///
2771/// # Errors
2772///
2773/// As [`hash_traced`].
2774#[allow(clippy::too_many_arguments)]
2775unsafe fn hash_in_workspace(
2776 workspace: &mut Workspace,
2777 backend: Backend,
2778 algorithm: Algorithm,
2779 version: Version,
2780 params: &Params,
2781 pwd: &[u8],
2782 salt: &[u8],
2783 secret: &[u8],
2784 ad: &[u8],
2785 out: &mut [u8],
2786 trace: Option<PassTrace<'_>>,
2787 h0_out: Option<&mut [u8; PREHASH_DIGEST_LENGTH]>,
2788) -> Result<(), Error> {
2789 let memory_blocks = validate_and_size(params, pwd, salt, secret, ad, out)?;
2790
2791 // The whole point: no allocator call and no zeroing memset when the parked
2792 // arena is already big enough. `acquire` only reallocates when it has to
2793 // grow, and the previous release left the blocks zeroed.
2794 let mut arena = workspace.acquire(memory_blocks)?;
2795
2796 // SAFETY: `backend` is forwarded verbatim from this function's own
2797 // contract, and `arena` was just sized from the same `params`.
2798 unsafe {
2799 hash_in_arena(
2800 &mut arena, backend, algorithm, version, params, pwd, salt, secret, ad, out, trace,
2801 h0_out,
2802 )
2803 }
2804 // The `ArenaGuard` drops here and hands the arena back to `workspace` after
2805 // a `clear_internal_memory_blocks` over exactly the blocks this hash could
2806 // reach. It drops whether the call above returned `Ok` or `Err`, and on
2807 // unwind — that is the reason to take a guard rather than an owned `Arena`.
2808 // Same wipe as `Arena::drop`, same `zeroize-memory` gate, just before the
2809 // free instead of together with it. The next acquisition therefore starts
2810 // from a zeroed arena without a second memset, and that saved memset is the
2811 // entire measured win. The two `?`s above fire before the guard exists.
2812}
2813
2814/// Run a hash with a specific [`Backend`], bypassing runtime detection.
2815///
2816/// Test and bench hook: lets the suite exercise every backend the host can
2817/// execute, not just the fastest one.
2818///
2819/// # Safety
2820///
2821/// This bypasses detection, so **the caller** must establish what detection
2822/// otherwise would: that this CPU can execute `backend`. `backend.is_available()`
2823/// is the portable way to do it. See [`fill_memory_blocks_traced`] for the full
2824/// contract.
2825///
2826/// Guarded, and therefore fine:
2827///
2828/// ```
2829/// # use argon2_rust::{Algorithm, Backend, Params, Version};
2830/// # use argon2_rust::__internal::hash_with_backend;
2831/// # let params = Params::new(8, 1, 1, 32).unwrap();
2832/// # let mut out = [0u8; 32];
2833/// for &backend in Backend::ALL {
2834/// if !backend.is_available() {
2835/// continue; // this CPU would SIGILL
2836/// }
2837/// // SAFETY: `is_available()` just said this CPU can execute `backend`.
2838/// unsafe {
2839/// hash_with_backend(
2840/// backend, Algorithm::Argon2id, Version::V0x13, ¶ms,
2841/// b"password", b"somesaltsomesalt", &[], &[], &mut out,
2842/// ).unwrap();
2843/// }
2844/// }
2845/// ```
2846///
2847/// The **same snippet with the `unsafe` block deleted** must not compile, which
2848/// is the whole point: safe code cannot reach a `#[target_feature]` function
2849/// whose feature was never detected. Keep these two in sync — the pair is the
2850/// regression test, and the runnable one above is what proves the failing one
2851/// below fails for the right reason rather than through some unrelated typo:
2852///
2853/// ```compile_fail
2854/// # use argon2_rust::{Algorithm, Backend, Params, Version};
2855/// # use argon2_rust::__internal::hash_with_backend;
2856/// # let params = Params::new(8, 1, 1, 32).unwrap();
2857/// # let mut out = [0u8; 32];
2858/// for &backend in Backend::ALL {
2859/// if !backend.is_available() {
2860/// continue; // this CPU would SIGILL
2861/// }
2862/// hash_with_backend(
2863/// backend, Algorithm::Argon2id, Version::V0x13, ¶ms,
2864/// b"password", b"somesaltsomesalt", &[], &[], &mut out,
2865/// ).unwrap();
2866/// }
2867/// ```
2868///
2869/// # Errors
2870///
2871/// As [`Argon2::hash_into`].
2872///
2873/// # Panics
2874///
2875/// Never.
2876#[cfg(feature = "internal-api")]
2877#[allow(clippy::too_many_arguments)]
2878pub unsafe fn hash_with_backend(
2879 backend: Backend,
2880 algorithm: Algorithm,
2881 version: Version,
2882 params: &Params,
2883 pwd: &[u8],
2884 salt: &[u8],
2885 secret: &[u8],
2886 ad: &[u8],
2887 out: &mut [u8],
2888) -> Result<(), Error> {
2889 // SAFETY: forwarded verbatim from this function's own contract.
2890 unsafe {
2891 hash_inner(
2892 backend, algorithm, version, params, pwd, salt, secret, ad, out,
2893 )
2894 }
2895}
2896
2897#[cfg(test)]
2898mod tests {
2899 use super::*;
2900
2901 // ------------------------------------------------------------------
2902 // decode_bounded — the worker-thread clamp
2903 // ------------------------------------------------------------------
2904
2905 /// The ceiling's `threads` is an OS-thread budget that none of the four
2906 /// magnitude checks implies.
2907 ///
2908 /// Decoding sets `threads = lanes`, and `fill_pooled` spawns
2909 /// `min(threads, lanes) - 1` helpers, so a string whose `p` is *within* the
2910 /// `lanes` ceiling used to hand an attacker that many OS threads on an
2911 /// authentication path. Measured before the clamp: a `p=256` string against
2912 /// a ceiling of `threads = 1` really did spawn 255 helpers.
2913 ///
2914 /// Asserted here rather than by sampling the live thread count, because the
2915 /// hash is over in milliseconds and a sampler misses the peak — which is
2916 /// exactly how this was nearly written off as unreproducible.
2917 #[test]
2918 fn decode_bounded_clamps_workers_to_the_ceilings_thread_budget() {
2919 const LANES: u32 = 256;
2920 let params = Params::new(8 * LANES, 1, LANES, 32).expect("params");
2921 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
2922 let encoded = argon2.hash_encoded(b"pw", b"somesalt").expect("encode");
2923
2924 // "Strings this wide are allowed; spawning this wide is not."
2925 let ceiling = Params::new_with_threads(8 * LANES, 1, LANES, 1, 32).expect("ceiling");
2926 let decoded =
2927 decode_bounded(&encoded, Algorithm::Argon2id, &ceiling).expect("within the ceiling");
2928
2929 assert_eq!(decoded.params.lanes(), LANES, "lanes must survive: it picks the tag");
2930 assert_eq!(decoded.params.threads(), 1, "workers must obey the ceiling");
2931 assert_eq!(decoded.params.effective_threads(), 1);
2932 }
2933
2934 /// The clamp only ever lowers. A ceiling that permits more workers than the
2935 /// string needs must leave the decoded value alone, so the ordinary
2936 /// `Params::new` ceiling (where `threads == lanes`) keeps full parallelism.
2937 #[test]
2938 fn decode_bounded_leaves_workers_alone_when_the_ceiling_is_generous() {
2939 let params = Params::new(1 << 10, 1, 4, 32).expect("params");
2940 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
2941 let encoded = argon2.hash_encoded(b"pw", b"somesalt").expect("encode");
2942
2943 // `Params::new` sets threads = lanes = 8, i.e. more than the string's 4.
2944 let ceiling = Params::new(1 << 16, 8, 8, 32).expect("ceiling");
2945 let decoded =
2946 decode_bounded(&encoded, Algorithm::Argon2id, &ceiling).expect("within the ceiling");
2947
2948 assert_eq!(decoded.params.lanes(), 4);
2949 assert_eq!(decoded.params.threads(), 4, "clamped to lanes, not raised to 8");
2950 }
2951
2952 // ------------------------------------------------------------------
2953 // index_alpha
2954 // ------------------------------------------------------------------
2955
2956 fn instance_for(params: &Params, algorithm: Algorithm, arena: &mut [Block]) -> Instance {
2957 // SAFETY: `arena` outlives the returned `Instance` at every call site
2958 // below, and none of these tests index into it.
2959 unsafe {
2960 Instance::new(
2961 arena.as_mut_ptr(),
2962 arena.len(),
2963 algorithm,
2964 Version::V0x13,
2965 params,
2966 )
2967 }
2968 }
2969
2970 #[test]
2971 fn index_alpha_pass0_slice0_is_all_but_the_previous() {
2972 let params = Params::new(1 << 12, 1, 1, 32).expect("params");
2973 let mut arena = [Block::ZERO; 2];
2974 let inst = instance_for(¶ms, Algorithm::Argon2i, &mut arena);
2975
2976 // reference_area_size = index - 1, start_position = 0, so the result is
2977 // always < index: index_alpha never returns the block being written.
2978 for index in 2..64u32 {
2979 for pseudo in [0u32, 1, 0x7FFF_FFFF, 0x8000_0000, u32::MAX] {
2980 let pos = Position::new(0, 0, 0, index);
2981 let alpha = index_alpha(&inst, &pos, pseudo, true);
2982 assert!(alpha < index, "index={index} pseudo={pseudo} -> {alpha}");
2983 }
2984 }
2985 }
2986
2987 #[test]
2988 fn index_alpha_never_selects_the_current_or_a_concurrent_block() {
2989 // This is the property the parallel safety argument rests on.
2990 let params = Params::new_with_threads(1024, 3, 4, 4, 32).expect("params");
2991 let mut arena = [Block::ZERO; 2];
2992 let inst = instance_for(¶ms, Algorithm::Argon2d, &mut arena);
2993 let seg = inst.segment_length;
2994
2995 for pass in 0..3u32 {
2996 for slice in 0..SYNC_POINTS {
2997 for index in 0..seg {
2998 if pass == 0 && slice == 0 && index < 2 {
2999 continue;
3000 }
3001 let pos = Position::new(pass, 1, slice, index);
3002 for pseudo in [0u32, 1, 12345, 0x8000_0000, u32::MAX] {
3003 // Cross-lane: must land outside the current slice.
3004 // `fill_segment` pins `ref_lane = position.lane` on
3005 // pass 0 / slice 0, so `same_lane == false` is not
3006 // reachable there and the C's answer (block 0, the only
3007 // candidate) is a same-lane reference anyway.
3008 if !(pass == 0 && slice == 0) {
3009 let alpha = index_alpha(&inst, &pos, pseudo, false);
3010 let alpha_slice = alpha / seg;
3011 assert_ne!(
3012 alpha_slice, slice,
3013 "cross-lane reference into the live slice: \
3014 pass={pass} slice={slice} index={index} pseudo={pseudo}"
3015 );
3016 }
3017
3018 // Same lane: may be in this slice, but strictly before
3019 // the block being written.
3020 let alpha = index_alpha(&inst, &pos, pseudo, true);
3021 if alpha / seg == slice {
3022 assert!(
3023 alpha % seg < index,
3024 "same-lane reference at or past the current block: \
3025 pass={pass} slice={slice} index={index} -> {alpha}"
3026 );
3027 }
3028 }
3029 }
3030 }
3031 }
3032 }
3033
3034 #[test]
3035 fn index_alpha_wraps_at_index_zero_across_lanes() {
3036 // The `((index == 0) ? (-1) : 0)` branch. With slice = 1 and
3037 // segment_length = 2 the C computes reference_area_size = 2 - 1 = 1,
3038 // so the only legal answer is block 0.
3039 let params = Params::new_with_threads(8, 1, 1, 1, 32).expect("params");
3040 let mut arena = [Block::ZERO; 2];
3041 let inst = instance_for(¶ms, Algorithm::Argon2i, &mut arena);
3042 assert_eq!(inst.segment_length, 2);
3043
3044 let pos = Position::new(0, 0, 1, 0);
3045 for pseudo in [0u32, 1, 0x1234_5678, u32::MAX] {
3046 assert_eq!(index_alpha(&inst, &pos, pseudo, false), 0);
3047 }
3048 }
3049
3050 #[test]
3051 fn index_alpha_start_position_skips_the_current_slice() {
3052 // pass > 0: start_position = (slice + 1) * segment_length, except for
3053 // the last slice where it is 0.
3054 let params = Params::new_with_threads(1024, 2, 4, 4, 32).expect("params");
3055 let mut arena = [Block::ZERO; 2];
3056 let inst = instance_for(¶ms, Algorithm::Argon2d, &mut arena);
3057 let seg = inst.segment_length;
3058
3059 // pseudo_rand = 0 makes relative_position = ras - 1, the far end of the
3060 // window, so the answer is (start_position + ras - 1) % lane_length.
3061 for slice in 0..SYNC_POINTS {
3062 let pos = Position::new(1, 0, slice, 5);
3063 let ras = inst.lane_length - seg + 5 - 1;
3064 let start = if slice == SYNC_POINTS - 1 {
3065 0
3066 } else {
3067 (slice + 1) * seg
3068 };
3069 assert_eq!(
3070 index_alpha(&inst, &pos, 0, true),
3071 (start + ras - 1) % inst.lane_length
3072 );
3073 }
3074 }
3075
3076 /// `reference_area_size - 1` is evaluated in **`uint32_t`**, not `uint64_t`.
3077 ///
3078 /// This is the one place the task brief's summary and `core.c` disagree, and
3079 /// it is invisible to every other test in this repository — mutating
3080 /// `u64::from(ras.wrapping_sub(1))` into `u64::from(ras).wrapping_sub(1)`
3081 /// leaves the whole suite green, including all 26 official vectors, the
3082 /// KATs and a 95 040-case differential against the C. So it is pinned here
3083 /// directly, against values dumped from the real `index_alpha`.
3084 ///
3085 /// The two readings differ only when `reference_area_size == 0`, where the
3086 /// C gives `relative_position = 0x0000_0000_FFFF_FFFF` and the 64-bit-first
3087 /// reading gives `0xFFFF_FFFF_FFFF_FFFF`. Both then go through
3088 /// `% lane_length`, which hides the difference whenever `lane_length`
3089 /// divides `2^64 - 2^32 = 2^32 * (2^32 - 1)`. Since
3090 /// `2^32 - 1 = 3 * 5 * 17 * 257 * 65537`, that is true for every power of
3091 /// two and for `lane_length` 12 and 20 — which is why a grid of "nice"
3092 /// segment lengths cannot see it. `segment_length` 7, 11, 13, 100 and 341
3093 /// can.
3094 ///
3095 /// `reference_area_size == 0` needs `pass = 0`, `slice = 0`, `index = 1`,
3096 /// which `fill_segment` never produces (it starts at `index = 2` there), so
3097 /// this is unreachable through the public API — but it is still what the C
3098 /// computes, and the next person to "simplify" this line needs a test that
3099 /// stops them.
3100 #[test]
3101 fn index_alpha_reference_area_size_zero_uses_32_bit_arithmetic() {
3102 // Dumped from the C, `index_alpha(&inst, &{0,0,0,1}, r, 1)`:
3103 // seg=2 lane_length=8 -> 7 (does NOT discriminate)
3104 // seg=3 lane_length=12 -> 3 (does NOT discriminate)
3105 // seg=5 lane_length=20 -> 15 (does NOT discriminate)
3106 // seg=7 lane_length=28 -> 3 (64-bit-first would give 15)
3107 // seg=11 lane_length=44 -> 3 (64-bit-first would give 15)
3108 // seg=13 lane_length=52 -> 47 (64-bit-first would give 15)
3109 // seg=100 lane_length=400 -> 95 (64-bit-first would give 15)
3110 // seg=341 lane_length=1364 -> 3 (64-bit-first would give 15)
3111 const CASES: [(u32, u32); 8] = [
3112 (2, 7),
3113 (3, 3),
3114 (5, 15),
3115 (7, 3),
3116 (11, 3),
3117 (13, 47),
3118 (100, 95),
3119 (341, 3),
3120 ];
3121
3122 let params = Params::new(1 << 12, 1, 1, 32).expect("params");
3123 let mut arena = [Block::ZERO; 2];
3124 let mut inst = instance_for(¶ms, Algorithm::Argon2i, &mut arena);
3125
3126 for (segment_length, expected) in CASES {
3127 inst.segment_length = segment_length;
3128 inst.lane_length = segment_length * SYNC_POINTS;
3129 // pass 0, slice 0, index 1 => reference_area_size = 1 - 1 = 0.
3130 let pos = Position::new(0, 0, 0, 1);
3131 for pseudo in [0u32, 1, 0x7FFF_FFFF, 0x8000_0000, u32::MAX, 0xDEAD_BEEF] {
3132 // `reference_area_size == 0` makes `(ras * rel) >> 32` zero for
3133 // every `pseudo_rand`, so the answer does not depend on it.
3134 assert_eq!(
3135 index_alpha(&inst, &pos, pseudo, true),
3136 expected,
3137 "segment_length={segment_length} pseudo={pseudo:#010x}"
3138 );
3139 assert_eq!(index_alpha(&inst, &pos, pseudo, false), expected);
3140 }
3141 }
3142 }
3143
3144 #[test]
3145 fn index_alpha_degenerate_instance_does_not_panic() {
3146 // lane_length == 0 would divide by zero in the C.
3147 let params = Params::new(8, 1, 1, 32).expect("params");
3148 let mut arena = [Block::ZERO; 2];
3149 let mut inst = instance_for(¶ms, Algorithm::Argon2i, &mut arena);
3150 inst.lane_length = 0;
3151 inst.segment_length = 0;
3152 assert_eq!(index_alpha(&inst, &Position::new(0, 0, 0, 0), 7, true), 0);
3153 }
3154
3155 // ------------------------------------------------------------------
3156 // constant_time_eq
3157 // ------------------------------------------------------------------
3158
3159 #[test]
3160 fn constant_time_eq_matches_argon2_compare() {
3161 assert!(constant_time_eq(b"", b""));
3162 assert!(constant_time_eq(b"abc", b"abc"));
3163 assert!(!constant_time_eq(b"abc", b"abd"));
3164 assert!(!constant_time_eq(b"abc", b"abcd"));
3165 assert!(!constant_time_eq(b"", b"a"));
3166 // A single differing bit in the last byte.
3167 assert!(!constant_time_eq(&[0u8; 32], &{
3168 let mut b = [0u8; 32];
3169 b[31] = 1;
3170 b
3171 }));
3172 // 0x80 in the high bit: the C's `d - 1` must not sign-extend wrongly.
3173 assert!(!constant_time_eq(&[0u8; 4], &[0, 0, 0, 0x80]));
3174 }
3175
3176 /// Structural guard for the two stable call sites: neither may request the
3177 /// optional H0 output copy from `hash_in_arena`. This observes that API
3178 /// choice, not stack contents; the traced call below proves the counter is
3179 /// live and reserves the copy for the unstable KAT API that returns H0.
3180 #[cfg(feature = "std")]
3181 #[test]
3182 fn stable_hashes_do_not_request_an_h0_output_copy() {
3183 H0_COPY_COUNT.with(|count| count.set(0));
3184
3185 let params = Params::new(32, 1, 1, 32).expect("params");
3186 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
3187 let mut tag = [0u8; 32];
3188 argon2
3189 .hash_into(b"password", b"somesalt", &mut tag)
3190 .expect("one-shot hash");
3191 let mut hasher = argon2.hasher();
3192 hasher
3193 .hash_into(b"password", b"somesalt", &mut tag)
3194 .expect("pooled hash");
3195 H0_COPY_COUNT.with(|count| assert_eq!(count.get(), 0, "stable paths copied H0"));
3196
3197 // The hook itself must be live or the zero above would prove nothing.
3198 // SAFETY: the scalar backend is available on every CPU.
3199 let mut h0 = unsafe {
3200 hash_traced(
3201 Backend::Scalar,
3202 argon2.algorithm,
3203 argon2.version,
3204 &argon2.params,
3205 b"password",
3206 b"somesalt",
3207 &[],
3208 &[],
3209 &mut tag,
3210 None,
3211 )
3212 }
3213 .expect("traced hash");
3214 H0_COPY_COUNT.with(|count| assert_eq!(count.get(), 1, "trace did not copy H0"));
3215 clear_internal_memory(&mut h0);
3216 }
3217
3218 #[test]
3219 fn fill_first_blocks_rejects_a_short_internal_arena() {
3220 let mut blockhash = [0xA5; PREHASH_SEED_LENGTH];
3221 let mut arena = [];
3222 assert_eq!(
3223 fill_first_blocks(&mut blockhash, &mut arena, 1, 8),
3224 Err(Error::IncorrectParameter)
3225 );
3226 }
3227
3228 // ------------------------------------------------------------------
3229 // initial_hash
3230 // ------------------------------------------------------------------
3231
3232 #[test]
3233 fn initial_hash_matches_the_genkat_pre_hashing_digest() {
3234 // `phc-winner-argon2/kats/argon2id`, first "Pre-hashing digest" line:
3235 // t_cost 3, m_cost 32, lanes 4, outlen 32,
3236 // pwd 32 x 0x01, salt 16 x 0x02, secret 8 x 0x03, ad 12 x 0x04.
3237 let params = Params::new_with_threads(32, 3, 4, 4, 32).expect("params");
3238 let h = initial_hash(
3239 Algorithm::Argon2id,
3240 Version::V0x13,
3241 ¶ms,
3242 &[1u8; 32],
3243 &[2u8; 16],
3244 &[3u8; 8],
3245 &[4u8; 12],
3246 )
3247 .expect("initial_hash");
3248
3249 let expected = "2889de487eb42ae500c0007ed9252f1069eadec40d5765b485de6dc2437a67b8\
3250 546a2f0acc1a0882db8fcf74714b472e94df421a5da1112ffa11434370a1e997";
3251 let mut hex = String::new();
3252 for byte in &h[..PREHASH_DIGEST_LENGTH] {
3253 hex.push_str(&alloc::format!("{byte:02x}"));
3254 }
3255 assert_eq!(hex, expected);
3256 // The 8 trailing bytes must be zero before `fill_first_blocks` fills them.
3257 assert_eq!(&h[PREHASH_DIGEST_LENGTH..], &[0u8; 8]);
3258 }
3259
3260 #[test]
3261 fn initial_hash_field_order_is_load_bearing() {
3262 // Swapping any two parameters must change H0. Compare `lanes` against
3263 // `outlen`: both are 4, so a transposition would be invisible unless the
3264 // values differ.
3265 let a = Params::new_with_threads(64, 1, 2, 2, 32).expect("params");
3266 let b = Params::new_with_threads(64, 1, 4, 4, 32).expect("params");
3267 let ha = initial_hash(
3268 Algorithm::Argon2i,
3269 Version::V0x13,
3270 &a,
3271 b"p",
3272 b"salt",
3273 &[],
3274 &[],
3275 )
3276 .expect("h");
3277 let hb = initial_hash(
3278 Algorithm::Argon2i,
3279 Version::V0x13,
3280 &b,
3281 b"p",
3282 b"salt",
3283 &[],
3284 &[],
3285 )
3286 .expect("h");
3287 assert_ne!(ha, hb);
3288
3289 // Version and type are hashed separately.
3290 let h10 = initial_hash(
3291 Algorithm::Argon2i,
3292 Version::V0x10,
3293 &a,
3294 b"p",
3295 b"salt",
3296 &[],
3297 &[],
3298 )
3299 .expect("h");
3300 assert_ne!(ha, h10);
3301 let hid = initial_hash(
3302 Algorithm::Argon2id,
3303 Version::V0x13,
3304 &a,
3305 b"p",
3306 b"salt",
3307 &[],
3308 &[],
3309 )
3310 .expect("h");
3311 assert_ne!(ha, hid);
3312
3313 // The length prefixes make "ab" || "" different from "a" || "b".
3314 let h1 = initial_hash(
3315 Algorithm::Argon2i,
3316 Version::V0x13,
3317 &a,
3318 b"ab",
3319 b"saltsalt",
3320 &[],
3321 &[],
3322 )
3323 .expect("h");
3324 let h2 = initial_hash(
3325 Algorithm::Argon2i,
3326 Version::V0x13,
3327 &a,
3328 b"a",
3329 b"bsaltsalt",
3330 &[],
3331 &[],
3332 )
3333 .expect("h");
3334 assert_ne!(h1, h2);
3335 }
3336
3337 // ------------------------------------------------------------------
3338 // The whole pipeline
3339 // ------------------------------------------------------------------
3340
3341 fn hex(bytes: &[u8]) -> String {
3342 let mut s = String::new();
3343 for byte in bytes {
3344 s.push_str(&alloc::format!("{byte:02x}"));
3345 }
3346 s
3347 }
3348
3349 #[test]
3350 fn one_official_vector_end_to_end() {
3351 // test.c: Argon2i v=19 t=2 m=1<<16 p=1 "password" / "somesalt".
3352 let params = Params::new(1 << 16, 2, 1, 32).expect("params");
3353 let argon2 = Argon2::new(Algorithm::Argon2i, Version::V0x13, params);
3354 let tag = argon2.hash(b"password", b"somesalt").expect("hash");
3355 assert_eq!(
3356 hex(&tag),
3357 "c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0"
3358 );
3359 }
3360
3361 #[test]
3362 fn genkat_tag_matches_for_all_three_types() {
3363 // The final "Tag:" line of each `phc-winner-argon2/kats/*` file:
3364 // t_cost 3, m_cost 32, lanes 4, outlen 32, pwd 32 x 0x01,
3365 // salt 16 x 0x02, secret 8 x 0x03, ad 12 x 0x04.
3366 let params = Params::new_with_threads(32, 3, 4, 4, 32).expect("params");
3367 for (algorithm, version, expected) in [
3368 (
3369 Algorithm::Argon2d,
3370 Version::V0x13,
3371 "512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb",
3372 ),
3373 (
3374 Algorithm::Argon2i,
3375 Version::V0x13,
3376 "c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8",
3377 ),
3378 (
3379 Algorithm::Argon2id,
3380 Version::V0x13,
3381 "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659",
3382 ),
3383 (
3384 Algorithm::Argon2d,
3385 Version::V0x10,
3386 "96a9d4e5a1734092c85e29f410a45914a5dd1f5cbf08b2670da68a0285abf32b",
3387 ),
3388 (
3389 Algorithm::Argon2i,
3390 Version::V0x10,
3391 "87aeedd6517ab830cd9765cd8231abb2e647a5dee08f7c05e02fcb763335d0fd",
3392 ),
3393 (
3394 Algorithm::Argon2id,
3395 Version::V0x10,
3396 "b64615f07789b66b645b67ee9ed3b377ae350b6bfcbb0fc95141ea8f322613c0",
3397 ),
3398 ] {
3399 let argon2 = Argon2::new(algorithm, version, params);
3400 let mut tag = [0u8; 32];
3401 argon2
3402 .hash_into_with_ad(&[1u8; 32], &[2u8; 16], &[3u8; 8], &[4u8; 12], &mut tag)
3403 .expect("hash");
3404 assert_eq!(hex(&tag), expected, "{algorithm:?} {version:?}");
3405 }
3406 }
3407
3408 /// The whole single-threaded pipeline on the smallest legal instance.
3409 ///
3410 /// `m_cost = MIN_MEMORY = 8` blocks, one lane, so the arena is 8 KiB and one
3411 /// pass is 4 slices of `segment_length = 2`. Small enough that
3412 /// `cargo +nightly miri test --lib tiny_` can run allocate → `initial_hash`
3413 /// → `fill_first_blocks` → `fill_segment` → `finalize` → wipe → free end to
3414 /// end. Ground truth from the C reference:
3415 ///
3416 /// ```text
3417 /// printf password | ./argon2 somesalt -{i,d,id} -t 1 -m 3 -p 1 -l 32 -r
3418 /// ```
3419 #[test]
3420 fn tiny_single_threaded_hash_matches_the_c_reference() {
3421 let params = Params::new(8, 1, 1, 32).expect("params");
3422 assert_eq!(params.memory_layout(), (8, 2, 8));
3423 for (algorithm, expected) in [
3424 (
3425 Algorithm::Argon2i,
3426 "cbf2bce47e6d23999626143fabc5db69164743ee000ddd3f8895a6f82cfb9a6e",
3427 ),
3428 (
3429 Algorithm::Argon2d,
3430 "c519e603ac603ec1aeb5b71ec44a6179e3f3975b14c0c97e3914c79e6363e178",
3431 ),
3432 (
3433 Algorithm::Argon2id,
3434 "f137f8e186a403a679ccd0606e5ab5dcdafe43c1640855ac8c6e33e9bd63eeb3",
3435 ),
3436 ] {
3437 let mut tag = [0u8; 32];
3438 Argon2::new(algorithm, Version::V0x13, params)
3439 .hash_into(b"password", b"somesalt", &mut tag)
3440 .expect("hash");
3441 assert_eq!(hex(&tag), expected, "{algorithm:?}");
3442 }
3443 }
3444
3445 /// The same, two lanes and two passes, so the multi-threaded path and the
3446 /// cross-lane `index_alpha` branches are exercised under Miri too.
3447 ///
3448 /// ```text
3449 /// printf password | ./argon2 somesalt -{i,d,id} -t 2 -m 4 -p 2 -l 32 -r
3450 /// ```
3451 #[test]
3452 fn tiny_two_lane_hash_matches_the_c_reference() {
3453 let params = Params::new(16, 2, 2, 32).expect("params");
3454 assert_eq!(params.memory_layout(), (16, 2, 8));
3455 for (algorithm, expected) in [
3456 (
3457 Algorithm::Argon2i,
3458 "7fbb85db7e9636115f2fd0f29ea4214baaada18b39fffed7875eeb9fa9b308c5",
3459 ),
3460 (
3461 Algorithm::Argon2d,
3462 "59f20a66a4c31bf0438a2f494867c32120409a91380f0687aefee984ba86bda8",
3463 ),
3464 (
3465 Algorithm::Argon2id,
3466 "747d7631b182faf749d7efc31aec31df4ecfe3b57c792f53800ac2c9978b4888",
3467 ),
3468 ] {
3469 let mut tag = [0u8; 32];
3470 Argon2::new(algorithm, Version::V0x13, params)
3471 .hash_into(b"password", b"somesalt", &mut tag)
3472 .expect("hash");
3473 assert_eq!(hex(&tag), expected, "{algorithm:?} (threads = lanes = 2)");
3474 }
3475 }
3476
3477 #[test]
3478 fn out_length_mismatch_is_out_ptr_mismatch() {
3479 let params = Params::new(1 << 8, 1, 1, 32).expect("params");
3480 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
3481 let mut out = [0u8; 16];
3482 assert_eq!(
3483 argon2.hash_into(b"password", b"somesalt", &mut out),
3484 Err(Error::OutPtrMismatch)
3485 );
3486 }
3487
3488 #[test]
3489 fn trace_fires_once_per_pass_with_the_whole_arena() {
3490 let params = Params::new(1 << 8, 3, 1, 32).expect("params");
3491 let mut passes = alloc::vec::Vec::new();
3492 let mut out = [0u8; 32];
3493 let mut trace = |pass: u32, blocks: &[Block]| {
3494 passes.push((pass, blocks.len()));
3495 };
3496 // SAFETY: `backend()` is what runtime detection picked for this CPU.
3497 let h0 = unsafe {
3498 hash_traced(
3499 crate::fill_block::backend(),
3500 Algorithm::Argon2id,
3501 Version::V0x13,
3502 ¶ms,
3503 b"password",
3504 b"somesalt",
3505 &[],
3506 &[],
3507 &mut out,
3508 Some(&mut trace),
3509 )
3510 }
3511 .expect("hash_traced");
3512
3513 assert_eq!(passes, alloc::vec![(0, 256), (1, 256), (2, 256)]);
3514 assert_eq!(h0.len(), PREHASH_DIGEST_LENGTH);
3515 }
3516
3517 /// A panic on the leader must propagate, not deadlock the pool.
3518 ///
3519 /// The worker pool spans the whole fill and its helpers park on a spin
3520 /// barrier between slices. If the leader unwinds out of `thread::scope`
3521 /// without releasing them, `Scope`'s `Drop` blocks for ever joining threads
3522 /// that are waiting for a `generation` bump that is never coming — the
3523 /// crate hangs instead of failing. This test reaches that path through the
3524 /// one leader-side callback that exists, and it is the reason
3525 /// `ReleaseHelpers` is a `Drop` guard rather than a line at the end of the
3526 /// loop.
3527 ///
3528 /// If this regresses, it does not fail — it hangs. That is the point.
3529 #[test]
3530 #[cfg(feature = "parallel")]
3531 // wasip1 is panic=abort: there is no unwinding to test there.
3532 #[cfg_attr(target_arch = "wasm32", ignore = "no unwinding on wasi (panic=abort)")]
3533 fn a_panicking_trace_callback_unwinds_instead_of_deadlocking_the_pool() {
3534 // 4 lanes and 4 threads, so there really are helpers parked on the
3535 // barrier when the callback runs.
3536 let params = Params::new(64, 2, 4, 32).expect("params");
3537 let mut arena = Arena::new(params.memory_blocks() as usize).expect("arena");
3538 let mut blockhash = initial_hash(
3539 Algorithm::Argon2id,
3540 Version::V0x13,
3541 ¶ms,
3542 b"password",
3543 b"somesaltsomesalt",
3544 &[],
3545 &[],
3546 )
3547 .expect("H0");
3548 let (_, _, lane_length) = params.memory_layout();
3549 fill_first_blocks(
3550 &mut blockhash,
3551 arena.as_mut_slice(),
3552 params.lanes(),
3553 lane_length,
3554 )
3555 .expect("first blocks");
3556
3557 let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3558 // SAFETY: `arena` was sized from `params` and outlives `instance`.
3559 let instance = unsafe {
3560 Instance::new(
3561 arena.as_mut_ptr(),
3562 arena.len(),
3563 Algorithm::Argon2id,
3564 Version::V0x13,
3565 ¶ms,
3566 )
3567 };
3568 let mut boom = |_pass: u32, _blocks: &[Block]| panic!("trace exploded");
3569 // SAFETY: `Backend::Scalar` runs anywhere, and `instance` is valid.
3570 unsafe {
3571 fill_memory_blocks_traced(&instance, Backend::Scalar, Some(&mut boom)).expect("fill")
3572 };
3573 }));
3574
3575 assert!(caught.is_err(), "the callback's panic must reach the caller");
3576 }
3577
3578 #[test]
3579 fn threads_do_not_change_the_tag() {
3580 // Spec item (12): only `lanes` affects the tag.
3581 for lanes in [2u32, 4] {
3582 let single = Params::new_with_threads(1 << 10, 2, lanes, 1, 32).expect("params");
3583 let multi = Params::new_with_threads(1 << 10, 2, lanes, lanes, 32).expect("params");
3584 let a = Argon2::new(Algorithm::Argon2id, Version::V0x13, single)
3585 .hash(b"password", b"somesalt")
3586 .expect("st");
3587 let b = Argon2::new(Algorithm::Argon2id, Version::V0x13, multi)
3588 .hash(b"password", b"somesalt")
3589 .expect("mt");
3590 assert_eq!(a, b, "lanes={lanes}");
3591 }
3592 }
3593
3594 #[test]
3595 fn verify_round_trips_and_rejects() {
3596 let params = Params::new(1 << 8, 2, 1, 32).expect("params");
3597 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
3598 let encoded = argon2.hash_encoded(b"password", b"somesalt").expect("enc");
3599 assert!(encoded.starts_with("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$"));
3600
3601 assert_eq!(
3602 Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2id),
3603 Ok(())
3604 );
3605 assert_eq!(
3606 Argon2::verify_encoded(&encoded, b"passwore", Algorithm::Argon2id),
3607 Err(Error::VerifyMismatch)
3608 );
3609 assert_eq!(
3610 Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2i),
3611 Err(Error::DecodingFail)
3612 );
3613
3614 let tag = argon2.hash(b"password", b"somesalt").expect("hash");
3615 assert_eq!(argon2.verify(b"password", b"somesalt", &tag), Ok(()));
3616 assert_eq!(
3617 argon2.verify(b"password", b"somesalt", &tag[..16]),
3618 Err(Error::VerifyMismatch)
3619 );
3620 }
3621
3622 #[test]
3623 fn password_flavoured_names_are_the_same_functions() {
3624 let params = Params::new(1 << 8, 2, 1, 32).expect("params");
3625 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
3626
3627 let mut a = [0u8; 32];
3628 let mut b = [0u8; 32];
3629 argon2
3630 .hash_into(b"password", b"somesalt", &mut a)
3631 .expect("hash_into");
3632 argon2
3633 .hash_password_into(b"password", b"somesalt", &mut b)
3634 .expect("hash_password_into");
3635 assert_eq!(a, b);
3636
3637 let encoded = argon2.hash_password(b"password", b"somesalt").expect("enc");
3638 assert_eq!(
3639 encoded,
3640 argon2.hash_encoded(b"password", b"somesalt").expect("enc")
3641 );
3642 assert!(encoded.starts_with("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$"));
3643
3644 assert_eq!(
3645 Argon2::verify_password(&encoded, b"password", Algorithm::Argon2id),
3646 Ok(())
3647 );
3648 assert_eq!(
3649 Argon2::verify_password(&encoded, b"passwore", Algorithm::Argon2id),
3650 Err(Error::VerifyMismatch)
3651 );
3652 }
3653
3654 // ------------------------------------------------------------------
3655 // Hasher — the pooled arena
3656 // ------------------------------------------------------------------
3657
3658 /// Everything one hash can be observed to produce: the pre-hashing digest,
3659 /// the whole arena after every pass, and the tag.
3660 ///
3661 /// The arena dumps are the point. A tag comparison would prove the two
3662 /// paths agree; a word-by-word arena comparison proves they agree *for the
3663 /// same reason*, and says exactly which block diverged when they do not.
3664 /// This is `genkat.c`'s `internal_kat` output in memory instead of on
3665 /// stdout — the same evidence `tests/kat.rs` checks against the golden
3666 /// files, applied to the one axis those files cannot see: where the arena
3667 /// came from.
3668 type Dump = (
3669 alloc::vec::Vec<(u32, alloc::vec::Vec<Block>)>,
3670 [u8; PREHASH_DIGEST_LENGTH],
3671 [u8; 32],
3672 );
3673
3674 /// One hash down the one-shot path (`Arena::new` .. `Arena::drop`).
3675 ///
3676 /// # Safety
3677 ///
3678 /// This CPU must be able to execute `backend`.
3679 unsafe fn dump_one_shot(backend: Backend, argon2: &Argon2, pwd: &[u8], salt: &[u8]) -> Dump {
3680 let mut tag = [0u8; 32];
3681 let mut passes: alloc::vec::Vec<(u32, alloc::vec::Vec<Block>)> = alloc::vec::Vec::new();
3682 let mut trace = |pass: u32, blocks: &[Block]| passes.push((pass, blocks.to_vec()));
3683 // SAFETY: forwarded verbatim from this function's own contract.
3684 let h0 = unsafe {
3685 hash_traced(
3686 backend,
3687 argon2.algorithm,
3688 argon2.version,
3689 &argon2.params,
3690 pwd,
3691 salt,
3692 &[3u8; 8],
3693 &[4u8; 12],
3694 &mut tag,
3695 Some(&mut trace),
3696 )
3697 }
3698 .expect("one-shot hash");
3699 (passes, h0, tag)
3700 }
3701
3702 /// The same hash over an arena borrowed from `workspace`.
3703 ///
3704 /// # Safety
3705 ///
3706 /// This CPU must be able to execute `backend`.
3707 unsafe fn dump_pooled(
3708 workspace: &mut Workspace,
3709 backend: Backend,
3710 argon2: &Argon2,
3711 pwd: &[u8],
3712 salt: &[u8],
3713 ) -> Dump {
3714 let mut tag = [0u8; 32];
3715 let mut h0 = [0u8; PREHASH_DIGEST_LENGTH];
3716 let mut passes: alloc::vec::Vec<(u32, alloc::vec::Vec<Block>)> = alloc::vec::Vec::new();
3717 let mut trace = |pass: u32, blocks: &[Block]| passes.push((pass, blocks.to_vec()));
3718 // SAFETY: forwarded verbatim from this function's own contract.
3719 unsafe {
3720 hash_in_workspace(
3721 workspace,
3722 backend,
3723 argon2.algorithm,
3724 argon2.version,
3725 &argon2.params,
3726 pwd,
3727 salt,
3728 &[3u8; 8],
3729 &[4u8; 12],
3730 &mut tag,
3731 Some(&mut trace),
3732 Some(&mut h0),
3733 )
3734 }
3735 .expect("pooled hash");
3736 (passes, h0, tag)
3737 }
3738
3739 /// Report the *first* divergence, not a 96 KiB `assert_eq!` diff.
3740 fn assert_same_dump(what: &str, expected: &Dump, actual: &Dump) {
3741 assert_eq!(actual.1, expected.1, "{what}: H0 differs");
3742 assert_eq!(actual.0.len(), expected.0.len(), "{what}: pass count");
3743
3744 for (want, got) in expected.0.iter().zip(actual.0.iter()) {
3745 assert_eq!(got.0, want.0, "{what}: pass index");
3746 assert_eq!(
3747 got.1.len(),
3748 want.1.len(),
3749 "{what}: arena length after pass {}",
3750 want.0
3751 );
3752 for (block, (wb, gb)) in want.1.iter().zip(got.1.iter()).enumerate() {
3753 for (word, (w, g)) in wb.0.iter().zip(gb.0.iter()).enumerate() {
3754 assert_eq!(
3755 g, w,
3756 "{what}: pass {}, block {block}, word {word}",
3757 want.0
3758 );
3759 }
3760 }
3761 }
3762 assert_eq!(actual.2, expected.2, "{what}: tag differs");
3763 }
3764
3765 /// The headline correctness claim, checked at the strongest granularity
3766 /// available: a pooled hash must produce a **byte-identical arena** at every
3767 /// pass boundary, not merely an identical tag.
3768 ///
3769 /// Rounds 1 and 2 are the ones that matter — round 0 runs on a
3770 /// freshly-allocated arena, so only a later round can catch a reused arena
3771 /// leaking a previous tenant's bytes into the computation. `genkat.c`'s
3772 /// parameters are used because they are the ones the golden files pin, and
3773 /// `lanes = threads = 4` puts the `std::thread::scope` path under the same
3774 /// check as the single-threaded one.
3775 #[test]
3776 fn a_pooled_hash_reproduces_the_one_shot_arena_word_for_word() {
3777 let params = Params::new_with_threads(32, 3, 4, 4, 32).expect("params");
3778
3779 for algorithm in [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id] {
3780 for version in [Version::V0x10, Version::V0x13] {
3781 let argon2 = Argon2::new(algorithm, version, params);
3782
3783 for &backend in Backend::ALL {
3784 if !backend.is_available() {
3785 continue; // this CPU would SIGILL
3786 }
3787 // SAFETY: guarded by `is_available()` immediately above.
3788 let expected =
3789 unsafe { dump_one_shot(backend, &argon2, &[1u8; 32], &[2u8; 16]) };
3790
3791 let mut workspace = Workspace::new();
3792 for round in 0..3 {
3793 // SAFETY: as above.
3794 let actual = unsafe {
3795 dump_pooled(&mut workspace, backend, &argon2, &[1u8; 32], &[2u8; 16])
3796 };
3797 assert_same_dump(
3798 &alloc::format!("{algorithm:?} {version:?} {backend} round {round}"),
3799 &expected,
3800 &actual,
3801 );
3802 }
3803 }
3804 }
3805 }
3806 }
3807
3808 /// The `Hasher` API itself, against the one-shot API, over enough parameter
3809 /// shapes to cover single-threaded, multi-lane threaded, and multi-pass.
3810 #[test]
3811 fn hasher_agrees_with_the_one_shot_api() {
3812 let configs = [
3813 Params::new(8, 1, 1, 32).expect("minimum"),
3814 Params::new(1 << 8, 2, 1, 32).expect("st"),
3815 Params::new_with_threads(1 << 9, 2, 4, 4, 32).expect("mt"),
3816 Params::new_with_threads(64, 3, 2, 2, 24).expect("odd outlen"),
3817 ];
3818
3819 for params in configs {
3820 for algorithm in [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id] {
3821 let argon2 = Argon2::new(algorithm, Version::V0x13, params);
3822 let mut hasher = argon2.hasher();
3823
3824 for round in 0..4u8 {
3825 let pwd = [round; 7];
3826 let mut want = alloc::vec![0u8; params.output_len()];
3827 let mut got = alloc::vec![0u8; params.output_len()];
3828
3829 argon2.hash_into(&pwd, b"somesalt", &mut want).expect("one");
3830 hasher.hash_into(&pwd, b"somesalt", &mut got).expect("pool");
3831 assert_eq!(got, want, "{algorithm:?} round {round}");
3832
3833 // ...and with a secret and associated data.
3834 argon2
3835 .hash_into_with_ad(&pwd, b"somesalt", &[3u8; 8], &[4u8; 12], &mut want)
3836 .expect("one ad");
3837 hasher
3838 .hash_into_with_ad(&pwd, b"somesalt", &[3u8; 8], &[4u8; 12], &mut got)
3839 .expect("pool ad");
3840 assert_eq!(got, want, "{algorithm:?} round {round} with ad");
3841 }
3842 }
3843 }
3844 }
3845
3846 /// The pooled counterparts of `tiny_single_threaded_hash_matches_the_c_reference`
3847 /// and `tiny_two_lane_hash_matches_the_c_reference`, against the same ground
3848 /// truth from the C reference.
3849 ///
3850 /// Sized so that `cargo +nightly miri test --lib tiny_` can run the whole
3851 /// new path end to end: acquire → hash → release-and-wipe → **re**-acquire →
3852 /// hash. The two-lane half puts the `std::thread::scope` raw-pointer sharing
3853 /// over an arena that has already been used once, which is the one piece of
3854 /// unsafe territory reuse actually changes. The growth step at the end makes
3855 /// Miri watch the old allocation being freed while the new one is filled.
3856 #[test]
3857 fn tiny_pooled_hashes_match_the_c_reference() {
3858 // `printf password | ./argon2 somesalt -id -t 1 -m 3 -p 1 -l 32 -r`
3859 let one_lane = Params::new(8, 1, 1, 32).expect("params");
3860 // `printf password | ./argon2 somesalt -id -t 2 -m 4 -p 2 -l 32 -r`
3861 let two_lane = Params::new(16, 2, 2, 32).expect("params");
3862
3863 let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, one_lane).hasher();
3864 let mut tag = [0u8; 32];
3865
3866 for round in 0..2 {
3867 hasher
3868 .hash_into(b"password", b"somesalt", &mut tag)
3869 .expect("single lane");
3870 assert_eq!(
3871 hex(&tag),
3872 "f137f8e186a403a679ccd0606e5ab5dcdafe43c1640855ac8c6e33e9bd63eeb3",
3873 "single lane, round {round}"
3874 );
3875 }
3876
3877 // Same hasher, wider configuration: the arena grows once, then reuses.
3878 hasher.set_argon2(Argon2::new(
3879 Algorithm::Argon2id,
3880 Version::V0x13,
3881 two_lane,
3882 ));
3883 for round in 0..2 {
3884 hasher
3885 .hash_into(b"password", b"somesalt", &mut tag)
3886 .expect("two lanes");
3887 assert_eq!(
3888 hex(&tag),
3889 "747d7631b182faf749d7efc31aec31df4ecfe3b57c792f53800ac2c9978b4888",
3890 "two lanes, round {round}"
3891 );
3892 }
3893
3894 // And back down: the big arena is kept and re-lent as a narrow window.
3895 hasher.set_argon2(Argon2::new(
3896 Algorithm::Argon2id,
3897 Version::V0x13,
3898 one_lane,
3899 ));
3900 hasher
3901 .hash_into(b"password", b"somesalt", &mut tag)
3902 .expect("single lane again");
3903 assert_eq!(
3904 hex(&tag),
3905 "f137f8e186a403a679ccd0606e5ab5dcdafe43c1640855ac8c6e33e9bd63eeb3"
3906 );
3907 assert_eq!(hasher.reserved_blocks(), two_lane.memory_blocks() as usize);
3908 }
3909
3910 /// Reuse is not a claim, it is an address: every hash after the first must
3911 /// land on the same allocation.
3912 #[test]
3913 fn reuse_lands_on_one_allocation() {
3914 let params = Params::new(1 << 8, 2, 1, 32).expect("params");
3915 let blocks = params.memory_blocks() as usize;
3916 let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher();
3917
3918 assert_eq!(hasher.reserved_blocks(), 0, "nothing allocated up front");
3919
3920 let mut tag = [0u8; 32];
3921 hasher.hash_into(b"password", b"somesalt", &mut tag).expect("first");
3922 assert_eq!(hasher.reserved_blocks(), blocks);
3923
3924 // Peek at the parked arena the way the next hash would. The guard drops
3925 // at the end of the statement, handing it straight back.
3926 let first = hasher.workspace.acquire(blocks).expect("peek").as_ptr();
3927 for round in 0..8 {
3928 hasher.hash_into(b"password", b"somesalt", &mut tag).expect("again");
3929 assert_eq!(
3930 hasher.workspace.acquire(blocks).expect("peek").as_ptr(),
3931 first,
3932 "round {round} reallocated"
3933 );
3934 }
3935 assert_eq!(hasher.reserved_blocks(), blocks);
3936 }
3937
3938 /// The control for the wipe test below, and a fact worth pinning in its own
3939 /// right: a finished hash leaves the **whole** arena full of material
3940 /// derived from that password. There is something real to wipe.
3941 ///
3942 /// Without this, `the_arena_a_hash_borrowed_comes_back_wiped` would be
3943 /// worthless — an arena that was never written would also read as all-zero.
3944 #[test]
3945 fn a_finished_hash_leaves_the_whole_arena_full_of_derived_material() {
3946 let params = Params::new(1 << 8, 2, 1, 32).expect("params");
3947 let mut arena = Arena::new(params.memory_blocks() as usize).expect("arena");
3948 let mut out = [0u8; 32];
3949
3950 // SAFETY: `Backend::Scalar` is available on every CPU.
3951 unsafe {
3952 hash_in_arena(
3953 &mut arena,
3954 Backend::Scalar,
3955 Algorithm::Argon2id,
3956 Version::V0x13,
3957 ¶ms,
3958 b"password",
3959 b"somesalt",
3960 &[],
3961 &[],
3962 &mut out,
3963 None,
3964 None,
3965 )
3966 }
3967 .expect("hash");
3968
3969 let dirty = arena.as_slice().iter().filter(|b| **b != Block::ZERO).count();
3970 assert_eq!(
3971 dirty,
3972 arena.len(),
3973 "every block should still hold derived material before the wipe"
3974 );
3975 }
3976
3977 /// The security property reuse must not weaken: the arena is wiped when the
3978 /// call that borrowed it returns, so what is parked between calls is zero,
3979 /// not the last password's derived material.
3980 ///
3981 /// Its control is
3982 /// [`a_finished_hash_leaves_the_whole_arena_full_of_derived_material`],
3983 /// which proves the bytes this test demands be gone were there to begin
3984 /// with.
3985 #[test]
3986 #[cfg(feature = "zeroize-memory")]
3987 fn the_arena_a_hash_borrowed_comes_back_wiped() {
3988 let params = Params::new(1 << 8, 2, 1, 32).expect("params");
3989 let blocks = params.memory_blocks() as usize;
3990 let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher();
3991
3992 let mut tag = [0u8; 32];
3993 for round in 0..3 {
3994 hasher.hash_into(b"password", b"somesalt", &mut tag).expect("hash");
3995 let parked = hasher.workspace.acquire(blocks).expect("peek");
3996 assert!(
3997 parked.as_slice().iter().all(|b| *b == Block::ZERO),
3998 "round {round}: the arena still holds derived material"
3999 );
4000 }
4001 }
4002
4003 /// A hash that fails validation must leave the hasher exactly as it was —
4004 /// no half-released arena, no lost capacity, no wrong answer afterwards.
4005 #[test]
4006 fn an_error_does_not_disturb_reuse() {
4007 let params = Params::new(1 << 8, 2, 1, 32).expect("params");
4008 let blocks = params.memory_blocks() as usize;
4009 let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher();
4010
4011 let mut tag = [0u8; 32];
4012 hasher.hash_into(b"password", b"somesalt", &mut tag).expect("warm up");
4013 let before = hasher.workspace.acquire(blocks).expect("peek").as_ptr();
4014
4015 // Wrong output length: rejected before anything is allocated.
4016 let mut short = [0u8; 16];
4017 assert_eq!(
4018 hasher.hash_into(b"password", b"somesalt", &mut short),
4019 Err(Error::OutPtrMismatch)
4020 );
4021 // Salt too short: rejected by `validate_for`.
4022 assert!(hasher.hash_into(b"password", b"salt", &mut tag).is_err());
4023
4024 assert_eq!(hasher.reserved_blocks(), blocks, "capacity survived");
4025 assert_eq!(
4026 hasher.workspace.acquire(blocks).expect("peek").as_ptr(),
4027 before,
4028 "and it is the same allocation"
4029 );
4030
4031 let mut after = [0u8; 32];
4032 hasher.hash_into(b"password", b"somesalt", &mut after).expect("still works");
4033 assert_eq!(after, tag);
4034 }
4035
4036 /// One hasher, several configurations. Growth reallocates once; shrinking
4037 /// keeps the big arena; every answer still matches the one-shot API.
4038 #[test]
4039 fn changing_the_configuration_keeps_the_memory_and_the_answers() {
4040 let small = Params::new(1 << 8, 1, 1, 32).expect("small");
4041 let large = Params::new(1 << 10, 1, 1, 32).expect("large");
4042 let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, small).hasher();
4043
4044 let mut tag = [0u8; 32];
4045 let mut want = [0u8; 32];
4046
4047 for (params, label) in [(small, "small"), (large, "large"), (small, "small again")] {
4048 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
4049 hasher.set_argon2(argon2);
4050 assert_eq!(hasher.params().m_cost(), params.m_cost(), "{label}");
4051 assert_eq!(hasher.algorithm(), Algorithm::Argon2id);
4052 assert_eq!(hasher.version(), Version::V0x13);
4053 assert_eq!(hasher.argon2(), &argon2);
4054
4055 hasher.hash_into(b"password", b"somesalt", &mut tag).expect(label);
4056 argon2.hash_into(b"password", b"somesalt", &mut want).expect(label);
4057 assert_eq!(tag, want, "{label}");
4058 }
4059
4060 assert_eq!(
4061 hasher.reserved_blocks(),
4062 large.memory_blocks() as usize,
4063 "a smaller configuration must not shrink the arena"
4064 );
4065 }
4066
4067 /// `reserve` front-loads the allocation; `clear` gives it back. Neither
4068 /// changes an answer.
4069 #[test]
4070 fn reserve_and_clear_move_the_allocation_around() {
4071 let params = Params::new(1 << 8, 1, 1, 32).expect("params");
4072 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
4073 let mut hasher = argon2.hasher();
4074
4075 hasher.reserve().expect("reserve");
4076 assert_eq!(hasher.reserved_blocks(), params.memory_blocks() as usize);
4077 let reserved = hasher
4078 .workspace
4079 .acquire(params.memory_blocks() as usize)
4080 .expect("peek")
4081 .as_ptr();
4082
4083 let mut tag = [0u8; 32];
4084 hasher.hash_into(b"password", b"somesalt", &mut tag).expect("hash");
4085 assert_eq!(
4086 hasher
4087 .workspace
4088 .acquire(params.memory_blocks() as usize)
4089 .expect("peek")
4090 .as_ptr(),
4091 reserved,
4092 "the first hash must use the reserved arena, not a new one"
4093 );
4094
4095 hasher.clear();
4096 assert_eq!(hasher.reserved_blocks(), 0);
4097
4098 let mut again = [0u8; 32];
4099 hasher.hash_into(b"password", b"somesalt", &mut again).expect("after clear");
4100 assert_eq!(again, tag);
4101 assert_eq!(hasher.reserved_blocks(), params.memory_blocks() as usize);
4102 }
4103
4104 /// The encoded and verifying halves of the API, including the one method
4105 /// that takes its parameters from the string rather than from the hasher.
4106 #[test]
4107 fn hasher_encodes_and_verifies_like_argon2() {
4108 let params = Params::new(1 << 8, 2, 1, 32).expect("params");
4109 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
4110 let mut hasher = argon2.hasher();
4111
4112 let encoded = hasher.hash_encoded(b"password", b"somesalt").expect("enc");
4113 assert_eq!(
4114 encoded,
4115 argon2.hash_encoded(b"password", b"somesalt").expect("enc")
4116 );
4117 assert_eq!(
4118 encoded,
4119 hasher.hash_password(b"password", b"somesalt").expect("enc")
4120 );
4121
4122 assert_eq!(
4123 hasher.verify_encoded(&encoded, b"password", Algorithm::Argon2id),
4124 Ok(())
4125 );
4126 assert_eq!(
4127 hasher.verify_password(&encoded, b"passwore", Algorithm::Argon2id),
4128 Err(Error::VerifyMismatch)
4129 );
4130 assert_eq!(
4131 hasher.verify_encoded(&encoded, b"password", Algorithm::Argon2i),
4132 Err(Error::DecodingFail)
4133 );
4134
4135 let tag = hasher.hash(b"password", b"somesalt").expect("hash");
4136 assert_eq!(tag, argon2.hash(b"password", b"somesalt").expect("hash"));
4137 assert_eq!(hasher.verify(b"password", b"somesalt", &tag), Ok(()));
4138 assert_eq!(
4139 hasher.verify(b"password", b"somesalt", &tag[..16]),
4140 Err(Error::VerifyMismatch)
4141 );
4142
4143 let mut into = [0u8; 32];
4144 hasher
4145 .hash_password_into(b"password", b"somesalt", &mut into)
4146 .expect("hash_password_into");
4147 assert_eq!(&into[..], &tag[..]);
4148 }
4149
4150 /// `verify_encoded` reads `m_cost` out of the string, so one hasher can be
4151 /// pointed at strings written at different costs. All of them must verify —
4152 /// and none of them may leave the hasher any bigger than its *owner* made
4153 /// it, because the string is untrusted input and a pooled arena is retained.
4154 #[test]
4155 fn verifying_a_mix_of_costs_never_lets_a_string_grow_the_arena() {
4156 let small = Params::new(1 << 8, 1, 1, 32).expect("small");
4157 let large = Params::new(1 << 10, 1, 1, 32).expect("large");
4158
4159 let encoded_small = Argon2::new(Algorithm::Argon2id, Version::V0x13, small)
4160 .hash_encoded(b"password", b"somesalt")
4161 .expect("enc small");
4162 let encoded_large = Argon2::new(Algorithm::Argon2id, Version::V0x13, large)
4163 .hash_encoded(b"password", b"somesalt")
4164 .expect("enc large");
4165
4166 // Deliberately configured for neither algorithm nor version: those
4167 // `verify_encoded` does take from the string. The *size* it does not.
4168 let mut hasher = Argon2::new(Algorithm::Argon2i, Version::V0x10, small).hasher();
4169
4170 for round in 0..3 {
4171 assert_eq!(
4172 hasher.verify_encoded(&encoded_large, b"password", Algorithm::Argon2id),
4173 Ok(()),
4174 "round {round} large"
4175 );
4176 assert_eq!(
4177 hasher.verify_encoded(&encoded_small, b"password", Algorithm::Argon2id),
4178 Ok(()),
4179 "round {round} small"
4180 );
4181 assert_eq!(
4182 hasher.reserved_blocks(),
4183 small.memory_blocks() as usize,
4184 "round {round}: the encoded string set the high-water mark"
4185 );
4186 }
4187
4188 // The owner raising the configuration is a different matter: that is a
4189 // deliberate choice, so it pools as normal, and a string of that size
4190 // may then use the arena it paid for.
4191 hasher.set_argon2(Argon2::new(Algorithm::Argon2id, Version::V0x13, large));
4192 assert_eq!(
4193 hasher.verify_encoded(&encoded_large, b"password", Algorithm::Argon2id),
4194 Ok(())
4195 );
4196 assert_eq!(hasher.reserved_blocks(), large.memory_blocks() as usize);
4197 }
4198
4199 /// The rule `verify_encoded` enforces is a *ceiling*, and the ceiling is the
4200 /// owner's configuration — not "whatever is already allocated", which would
4201 /// be zero on a hasher that has not hashed yet and would therefore send
4202 /// every verify down the un-pooled path.
4203 ///
4204 /// So: a decoded cost below the configured one pools even as the very first
4205 /// call, and the arena it leaves behind is never larger than the arena the
4206 /// owner's own next `hash_into` would have taken.
4207 #[test]
4208 fn a_decoded_cost_under_the_configured_one_pools_from_the_very_first_call() {
4209 let tiny = Params::new(1 << 7, 1, 1, 32).expect("tiny");
4210 let configured = Params::new(1 << 10, 1, 1, 32).expect("configured");
4211
4212 let encoded_tiny = Argon2::new(Algorithm::Argon2id, Version::V0x13, tiny)
4213 .hash_encoded(b"password", b"somesalt")
4214 .expect("enc tiny");
4215
4216 // Nothing allocated yet, and the first thing this hasher ever does is
4217 // verify somebody else's string.
4218 let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, configured).hasher();
4219 assert_eq!(hasher.reserved_blocks(), 0);
4220
4221 assert_eq!(
4222 hasher.verify_encoded(&encoded_tiny, b"password", Algorithm::Argon2id),
4223 Ok(())
4224 );
4225 assert_eq!(
4226 hasher.reserved_blocks(),
4227 tiny.memory_blocks() as usize,
4228 "a cost under the ceiling should still use the pool"
4229 );
4230 assert!(
4231 hasher.reserved_blocks() <= configured.memory_blocks() as usize,
4232 "an input must never push the pool past the owner's configuration"
4233 );
4234
4235 // And the owner's own hashing still grows it to the configured size.
4236 let mut tag = [0u8; 32];
4237 hasher
4238 .hash_into(b"password", b"somesalt", &mut tag)
4239 .expect("hash");
4240 assert_eq!(
4241 hasher.reserved_blocks(),
4242 configured.memory_blocks() as usize
4243 );
4244 }
4245
4246 /// A `Hasher` must be movable to whichever worker picks up a request. The
4247 /// matching negative — that it is not `Sync` — is the `compile_fail`
4248 /// doctest on [`Hasher`], which is what stops two threads sharing one arena.
4249 #[test]
4250 fn a_hasher_is_send() {
4251 const fn assert_send<T: Send>() {}
4252 assert_send::<Hasher>();
4253 }
4254
4255 /// `hash_in_arena` is the one place an arena of the wrong size could reach
4256 /// `Instance::new`, whose safety contract is `memory_len == memory_blocks`.
4257 /// It must be an error, never undefined behaviour.
4258 #[test]
4259 fn a_wrongly_sized_arena_is_an_error_not_undefined_behaviour() {
4260 let params = Params::new(1 << 8, 1, 1, 32).expect("params");
4261 assert_eq!(params.memory_blocks(), 256);
4262 let mut arena = Arena::new(64).expect("64 blocks");
4263 let mut out = [0u8; 32];
4264
4265 // SAFETY: `Backend::Scalar` is available on every CPU.
4266 let result = unsafe {
4267 hash_in_arena(
4268 &mut arena,
4269 Backend::Scalar,
4270 Algorithm::Argon2id,
4271 Version::V0x13,
4272 ¶ms,
4273 b"password",
4274 b"somesalt",
4275 &[],
4276 &[],
4277 &mut out,
4278 None,
4279 None,
4280 )
4281 };
4282 assert_eq!(result.err(), Some(Error::MemoryAllocationError));
4283 assert_eq!(out, [0u8; 32], "nothing was written");
4284 }
4285
4286 #[test]
4287 fn every_available_backend_agrees_with_scalar() {
4288 let params = Params::new_with_threads(1 << 9, 2, 2, 2, 32).expect("params");
4289 let mut reference = [0u8; 32];
4290 // SAFETY: `Backend::Scalar` is available on every CPU.
4291 unsafe {
4292 hash_inner(
4293 Backend::Scalar,
4294 Algorithm::Argon2id,
4295 Version::V0x13,
4296 ¶ms,
4297 b"password",
4298 b"somesalt",
4299 &[],
4300 &[],
4301 &mut reference,
4302 )
4303 }
4304 .expect("scalar");
4305
4306 for &backend in Backend::ALL {
4307 if !backend.is_available() {
4308 continue;
4309 }
4310 let mut out = [0u8; 32];
4311 // SAFETY: guarded by `is_available()` immediately above.
4312 unsafe {
4313 hash_inner(
4314 backend,
4315 Algorithm::Argon2id,
4316 Version::V0x13,
4317 ¶ms,
4318 b"password",
4319 b"somesalt",
4320 &[],
4321 &[],
4322 &mut out,
4323 )
4324 }
4325 .expect("backend");
4326 assert_eq!(out, reference, "{backend}");
4327 }
4328 }
4329}