Expand description
A pure-Rust port of the reference Argon2 implementation (phc-winner-argon2), with runtime-dispatched SIMD backends.
Argon2 is the winner of the 2015 Password Hashing Competition and is
specified in RFC 9106. Three
variants exist, see Algorithm:
Argon2d— data-dependent addressing: fastest, but leaks a memory access pattern that depends on the password.Argon2i— data-independent addressing: side-channel resistant.Argon2id— independent for the first half-pass, dependent afterwards. The default, and what RFC 9106 recommends.
§Example
use argon2_rust::{Algorithm, Argon2, Error, Params, Version};
// Default: 19 MiB, t=2, 1 lane, 32-byte tag (raw hash output). ~8 ms per
// hash in release on M-series — cheap enough for a real doctest. Raise
// `m_cost` until it fits your budget.
let params = Params::default();
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let mut tag = [0u8; 32];
argon2.hash_into(b"password", b"somesalt", &mut tag)?;
assert_eq!(argon2.verify(b"password", b"somesalt", &tag), Ok(()));
assert_eq!(
argon2.verify(b"wrong", b"somesalt", &tag),
Err(Error::VerifyMismatch),
);§SIMD backends
The compression function is selected by runtime CPU feature detection,
never by cfg(target_feature) alone, so one binary runs at full speed on
every machine. Detection happens at most once per process and the result is
cached; a hash call resolves one function pointer before entering its loops.
Ask detected_backend what this CPU picked. The cost model is documented
on the private fill_block module.
Backend | Requires | Source it was ported from |
|---|---|---|
Scalar | — | src/ref.c |
Neon | aarch64 | src/opt.c (128-bit path) |
Sse2 | x86/x86_64 + SSE2 | src/opt.c (128-bit path) |
Avx2 | x86_64 + AVX2 | src/opt.c (__AVX2__) |
Avx512 | x86_64 + AVX-512F | src/opt.c (__AVX512F__) |
The unpadded standard-Base64 codec used by PHC strings has a separate,
cached dispatch: AVX2, SSSE3, AArch64 NEON, wasm SIMD128, then scalar. Its
vector kernels follow base64-simd/aklomp/base64, while inputs shorter
than one vector stay on the original scalar loop. As in base64-simd,
AVX-512-capable CPUs use this codec’s AVX2 path; the wider backend remains
specific to the Argon2 compression function above. Call encode_base64
and decode_base64 for that codec on its own; detected_base64_backend
reports which implementation this CPU picked. The PHC string itself is
encode_string / decode_string (C encode_string / decode_string)
or decode_phc when the input may be a @phc/format / node-argon2
string (m,p,t any order, optional data=).
BLAKE2b (H0, the long expansion, and finalize) has its own cascade, x86
only: AVX-512 → AVX2 → SSE4.1 → scalar. AArch64 stays on scalar; a NEON
compress was measured slower. blake2b / blake2b_long are the
one-shot entry points; detected_blake2b_backend reports the choice.
§Features
std(default) — runtime CPU feature detection, includingmemchr’s SIMD cascade for PHC field scans.parallel(default, impliesstd) — multi-threaded fill. Onestd::thread::scopefor the whole fill, whose workers meet at a barrier at each of the4 * t_costalgorithmic sync points, rather than a fresh scope per sync point. Note that the thread count does not change the tag; onlyParams::lanesdoes.zeroize-memory(default) — securely wipe internal buffers, the equivalent ofFLAG_clear_internal_memoryin the C.bump-alloc— internal test/bench control. Together withinternal-api, givesmemory::Workspacea reusable bump allocator for measuring small scratch buffers. It does not change the stable hash/encode/verify paths, which deliberately keep theirVecs. Measured upper bound: 17 ns per hash, or 0.00013% of an RFC 9106 hash.internal-api— exposes__internalfor tests and benches. Not stable.
The crate is #![no_std] and needs alloc plus memchr. That stays
true with every feature turned on. Without std, backend selection (and
memchr) fall back to compile-time target_feature cfgs.
§Not a password-hash provider
This is a port of the C reference, not an implementation of the RustCrypto
password-hash traits: there is no PasswordHasher or PasswordVerifier
here, and no dependency that would supply one. Params carries no serde
impls either, though its full state round-trips through the accessors and
Params::to_builder. Interoperation is at the string level — the PHC
strings this crate reads and writes are the ones the argon2 crate reads and
writes. A future optional feature for those traits would be additive and
would not change this default surface.
§SemVer
From 1.0.0, the default-feature public API is covered by Semantic
Versioning. The internal-api feature and its __internal module are not.
MSRV may rise on a minor release. See the README “SemVer policy” section for
the full list.
§Reusing memory across hashes
Each Argon2 hash acquires its block arena once and releases it on the
way out. A process that hashes repeatedly can instead keep a Hasher,
from Argon2::hasher, which parks the arena between calls and wipes it on
release, so the next call gets a zeroed arena that is already mapped and
already resident.
Skipping the mmap, the first-touch faults and the munmap is worth around
-25% at m_cost = 64 MiB, and next to nothing below 1 MiB, where a hash is
mostly BLAKE2b; Hasher has the
per-cost measurements.
use argon2_rust::{Algorithm, Argon2, Hasher, Params, Version, params::Memory};
// Nameable, so it can be a field, a `thread_local!` or a worker slot —
// which is the only shape in which reuse is worth anything.
struct Worker {
hasher: Hasher,
}
let params = Params::builder().memory(Memory::kib(1 << 8)).passes(1).build()?;
let mut worker = Worker {
hasher: Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher(),
};
let mut tag = [0u8; 32];
worker.hasher.hash_into(b"password", b"somesalt", &mut tag)?;§Salts
Argon2::hash_password_with_random_salt (and the pooled
Hasher::hash_password_with_random_salt) draw a RANDOM_SALT_LEN-byte
salt from the OS and put it in the returned PHC string, so nothing has to be
stored alongside. The entropy comes from whichever entry point is correct
for the target — getrandom(2), getentropy, CCRandomGenerateBytes,
ProcessPrng, WASI random_get, or /dev/urandom — each declared by hand,
so this costs no dependency. Callers who already run a CSPRNG should keep
passing their own salt.
§Verifying strings you did not write
m_cost in a PHC string is up to ten digits of decimal, and
Argon2::verify_encoded will honour all of them — up to
params::MAX_MEMORY KiB, which is 4 TiB — because argon2_verify does
too. That is fine for a config file and a denial of service for a login
endpoint. Argon2::verify_encoded_bounded takes a ceiling and rejects an
over-large cost while it is still a number, before anything is allocated:
use argon2_rust::{Algorithm, Argon2, Error, Params, params::Memory};
let hostile = "$argon2id$v=19$m=4294967295,t=1,p=1$c29tZXNhbHQ$\
CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
let ceiling = Params::builder().memory(Memory::mib(64)).passes(8).lanes(4).build()?;
assert_eq!(
Argon2::verify_encoded_bounded(hostile, b"pw", Algorithm::Argon2id, &ceiling),
Err(Error::MemoryTooMuch),
);Memory is not the only resource the string spends. Decoding sets
threads = lanes (C parity), so p also picks how many OS threads the
verify spawns; the ceiling’s own threads bounds that, and a ceiling that
leaves ParamsBuilder::threads unset bounds
it together with lanes. Set that one setter to accept wide strings without
spawning wide. The clamp cannot change a verdict — only lanes feeds the tag.
§Panics
No fallible path in this crate panics: hashing, verifying, encoding,
decoding and parameter validation all report failure as an Error, whose
numeric Error::as_c_code matches the C reference — except for the
crate-specific codes below Error::MIN_C_CODE, which the C has no
equivalent for.
There is exactly one intentional exception, and it is not on a fallible
path: ParamsBuilder::build_or_panic
panics on invalid parameters. It exists so a const item can turn bad
parameters into a compile error, which is what a panic in a const
evaluation is. Anywhere a runtime error is the right answer, use
ParamsBuilder::build — it is the normal way
in.
Re-exports§
pub use crate::error::Error;pub use crate::params::Algorithm;pub use crate::params::Params;pub use crate::params::Version;
Modules§
- error
- Error codes, mirroring
argon2_error_codesfromphc-winner-argon2/include/argon2.h. - params
- Limits,
Algorithm,VersionandParams.
Structs§
- Argon2
- A configured Argon2 hasher.
- Decoded
- The fields a PHC string yields.
- Hasher
- An
Argon2that keeps its block arena between calls.
Enums§
- Backend
- Which
fill_segmentimplementation to use. - Base64
Backend - A Base64 SIMD implementation compiled into this crate.
- Blake2b
Backend - The compression implementations available to BLAKE2b on this target.
Constants§
- BOUNDED_
MAX_ SALT_ LEN - Longest salt the
*_boundedverify entry points will accept, in bytes. - RANDOM_
SALT_ LEN - Salt length used by the
*_with_random_saltentry points, in bytes.
Functions§
- blake2b
blake2b(out, outlen, in, inlen, NULL, 0): the one-shot unkeyed digest.- blake2b_
long blake2b_long(out, outlen, in, inlen): Argon2’s variable-length extension.- constant_
time_ eq argon2_compare()fromsrc/argon2.c: a constant-time byte comparison.- decode_
base64 - Decode unpadded standard Base64, the alphabet PHC strings use.
- decode_
phc - Decode a PHC string, detecting the algorithm from the
$argon2*prefix. - decode_
string decode_string(ctx, str, type).- detected_
backend - The
Backendthis CPU resolved to, cached after the first call. - detected_
base64_ backend - The Base64
Base64Backendthis CPU resolved to, cached after the first call. - detected_
blake2b_ backend - The BLAKE2b
Blake2bBackendthis CPU resolved to, cached after the first call. - encode_
base64 - Encode
srcas unpadded standard Base64, the alphabet PHC strings use. - encode_
string encode_string(dst, dst_len, ctx, type).- encode_
string_ alloc encode_stringinto a freshly allocatedString.- encoded_
len argon2_encodedlen(...)fromsrc/argon2.c.- from_
base64 from_base64(dst, dst_len, src).- to_
base64 to_base64(dst, dst_len, src, src_len).