Supported Password Hashing Algorithms
This page describes the password-hashing algorithms and reviewed policies in Erbsland Core. You will learn how Argon2id and scrypt slow offline guesses, how the separate HMAC pepper layer protects their output, and why other hashing APIs are excluded from password storage.
Use PasswordHashPolicy presets instead of selecting numeric
costs yourself.
Recommendations can change as hardware and cryptographic guidance evolve.
Every stored PasswordHash records the exact algorithm and costs, so
successful verification can migrate older records.
Algorithm Overview
Algorithm |
Reviewed policy |
Construction |
Intended use |
|---|---|---|---|
Argon2id |
64 MiB, 3 passes, 4 lanes |
Memory-hard hybrid addressing |
Recommended default |
Argon2id low-memory |
19 MiB, 2 passes, 1 lane |
Same construction with a smaller memory budget |
Constrained trusted environments |
scrypt |
|
PBKDF2 and Salsa20/8 BlockMix ROMix |
Compatibility and migration |
All presets use a 16-byte random salt and produce a 32-byte raw output. The normal keyed mode then protects that output with HMAC-SHA-256 before storage.
Argon2id
Argon2id version 1.3 combines two addressing strategies. The first half of its first pass uses data-independent memory references to limit password-dependent cache-timing leakage. The remaining work uses data-dependent references to strengthen resistance to time-memory tradeoffs. Its large memory matrix makes parallel password guessing expensive on CPUs, GPUs, and specialized hardware.
The implementation follows RFC 9106 and includes its BLAKE2b-based variable-length hash and 1024-byte compression function directly in the library. The code keeps the memory matrix and intermediate values in sensitive byte storage.
Recommended Policy
PasswordHashPolicy::recommended() uses 64 MiB,
three passes, and four lanes.
This is the normal choice for interactive authentication when the service can reserve that memory for each concurrent
verification.
PasswordHashPolicy::lowMemory() uses 19 MiB, two
passes, and one lane.
Use it only when the recommended policy cannot fit a measured memory or concurrency budget.
Reducing memory makes large-scale guessing less expensive, so prefer admission control and bounded authentication
concurrency over lowering the policy automatically under load.
scrypt
scrypt expands the password and salt with PBKDF2-HMAC-SHA-256, repeatedly mixes a large memory region with Salsa20/8 BlockMix and ROMix, then applies PBKDF2 again. The memory dependency raises the cost of evaluating many guesses in parallel.
The implementation follows RFC 7914.
PasswordHashPolicy::scrypt() uses N=2^17, r=8,
and p=1.
Argon2id remains the preferred default; scrypt provides a strong established alternative for migration and
interoperability requirements.
The Pepper Layer
For keyed records, the stored verifier is:
HMAC-SHA-256(application-key, canonical-header-through-salt || NUL || raw-password-hash)
The application key is not passed into the public salt field and never enters the stored record. The raw Argon2id or scrypt output is also not stored. Binding the canonical header authenticates the algorithm, costs, pepper mode, optional key identifier, and salt along with the derived password value.
This layer follows the additional keyed-iteration guidance in NIST SP 800-63B-4. It is an additional control, not a substitute for a memory-hard derivation, unique salts, database access controls, or rate limiting.
Storage Format
The serialized value is a strict, versioned, comma-separated record such as:
f:el-password-hash,v:1,a:argon2id,av:19,m:65536,t:3,p:4,x:hmac-sha256,s:<salt>,d:<verifier>
Identified peppers add an i: field.
Explicitly unkeyed records use x:none.
Salt and verifier fields use unpadded Base64url.
The parser requires fixed field order and canonical numeric and Base64url encodings.
It rejects unknown fields and versions, records longer than 512 bytes, arithmetic overflow, and costs beyond hard
resource limits before allocating an algorithm workspace.
Applications should still treat the complete record as opaque and use
PasswordHash::fromString() and
PasswordHash::toString().
Automatic Migration
The active PasswordHasher policy defines what new records should
look like.
After a successful verification it returns a replacement whenever any of these properties differs:
storage format version,
password-hashing algorithm,
algorithm costs,
keyed or unkeyed mode,
active pepper identifier or key material.
Persisting this replacement gradually upgrades active accounts. Keep old identified peppers available as fallbacks until their records have migrated. See Storing and Verifying Passwords for the login and rotation workflows.
Unsafe Custom Costs
UnsafeCustomPasswordHashParameters permits checked custom Argon2id or scrypt costs.
It lives in cryptology::unsafe and is not imported into the flattened erbsland namespace.
The constructor validates arithmetic and hard ceilings of 1 GiB memory, ten Argon2 passes, sixteen lanes, and equivalent
scrypt work.
The hard limits prevent obviously dangerous stored parameters; they do not make a cheap custom policy secure. Use custom costs only after measuring production hardware, peak authentication concurrency, latency targets, and denial-of-service exposure. Keep the reviewed presets unless a documented deployment constraint requires a different choice.
Why Other Algorithms Are Excluded
General SHA and Hasher
SHA-2 and SHA-3 are intentionally fast general message digests.
That is useful for file integrity and protocol constructions but also lets an attacker test password guesses quickly.
A salt does not make a fast digest deliberately expensive.
The public password API therefore never accepts a HashAlgorithm.
PBKDF2
PBKDF2-HMAC-SHA-256 is implemented only as an internal part of scrypt. Its cost is primarily CPU work and does not provide the memory-hard behavior selected for this API. The public password API does not create standalone PBKDF2 records.
bcrypt
bcrypt remains important in existing systems, but its historical password-length and input-handling constraints make
transparent migration easy to get wrong.
Erbsland Core does not create or parse bcrypt records.
Applications migrating bcrypt data should verify it through a dedicated compatibility component, then write an Argon2id
PasswordHash after the next successful login.