Password Hashing
Secure password storage using cryptographic hashing algorithms
- How It Works
- Generating Hashes with password_hash(...)
- Basic syntax
- Parameterized syntax
- Supported Algorithms
- Argon2 (Highest security)
- PBKDF2 (SHA-512 / SHA-256 / SHA-1)
- Crypt SHA-512
- Crypt SHA-256
- NT Hash
- Plaintext
- Usage Examples
- JSON File Backend
- SQL Backend
- Backend Configuration
- Security Recommendations
- Performance Considerations
- Related Documentation
Radiator Server supports multiple password hashing algorithms for secure credential storage. When user passwords are loaded from backends (SQL, JSON files, etc.), Radiator automatically detects the hashing algorithm based on the password format prefix and uses the appropriate verification method during authentication.
How It Works
- Passwords stored in backends include a prefix that identifies the hashing algorithm:
{argon2},{crypt-sha256}, etc. - When a backend loads user data, the password format is detected automatically
- During authentication, Radiator compares the provided plaintext password against the stored hash using the appropriate algorithm
- No configuration is needed - the algorithm is determined by the password prefix
Radiator can also generate password hashes inside pipelines with the password_hash(...) filter when you need to migrate or update stored credentials.
If you want a step-by-step login-time migration pattern, see Password Rehashing During Login.
Generating Hashes with password_hash(...)
Use the password_hash(...) value filter to transform cleartext input into a canonical Radiator password value ({algorithm}<hash-body>).
This is useful when you want to:
- Keep authentication and rehash logic in Radconf
- Migrate users from plaintext to hashed storage after successful login
- Standardize hash format before writing back to a backend
Basic syntax
auth.password | password_hash("argon2")
auth.password | password_hash("pbkdf2-sha512")
The first argument is the algorithm name. The generated value includes the matching prefix, for example {argon2}... or {pbkdf2-sha512}....
The pipe expression transforms the value on the left and returns the transformed value. By itself, it does not write anything to a backend.
To persist the hash, use the result on the right-hand side of an assignment or backend binding.
Store to a variable (for use later in the same policy):
modify {
vars.new_password_hash = auth.password | password_hash("argon2");
}
Store directly to a backend field:
query "UPDATE_PASSWORD" {
put {
key "%{aaa.identity}";
collection "customers";
bindings {
password_hash = auth.password | password_hash("argon2");
}
}
}
Parameterized syntax
password_hash(...) also supports named key=value options after the algorithm.
PBKDF2 example:
auth.password | password_hash("pbkdf2-sha512", rounds=300000)
Argon2 example:
auth.password | password_hash("argon2", memory_kib=8192, iterations=2, parallelism=1)
Supported options:
rounds: PBKDF2 iteration count. Supported only withpbkdf2-sha1,pbkdf2-sha256, andpbkdf2-sha512.iterations: Argon2 time cost (t). Supported only withargon2.memory_kib: Argon2 memory cost in KiB (m). Supported only withargon2.parallelism: Argon2 parallelism (p). Supported only withargon2.
Validation and compatibility rules:
- Options must use
=assignment syntax, for examplerounds=300000. - Option values must be positive integers.
rounds,iterations,memory_kib, andparallelismare each bounded by an upper limit set several orders of magnitude above current OWASP/RFC 9106 recommendations. Values within realistic security guidance are always accepted; pathological values are rejected to protect server resources.- Do not combine
roundswith Argon2 options (iterations,memory_kib,parallelism). - If no options are provided, Radiator uses the algorithm defaults.
The same Argon2 limits apply when verifying a stored {argon2} hash: a hash whose embedded cost parameters exceed the limits is rejected as if the password were incorrect. This protects authentication paths from DoS via attacker-supplied or corrupted hash strings.
Supported Algorithms
Radiator supports the following password hashing algorithms:
| Algorithm | Prefix | Security Level | Use Case |
|---|---|---|---|
| Argon2 | {argon2} | Highest | Strongest GPU/ASIC resistance; needs the CPU and memory budget to match |
| PBKDF2-SHA512 | {pbkdf2-sha512} | High | FIPS 140-approved KDF; LDAP/RFC 2307bis interoperability |
| PBKDF2-SHA256 | {pbkdf2-sha256} | High | FIPS 140-approved KDF; LDAP/RFC 2307bis interoperability |
| PBKDF2-SHA1 | {pbkdf2-sha1} or {pbkdf2} | Medium | Legacy LDAP/Dovecot stores (use SHA-256/512 for new data) |
| Crypt SHA-512 | {crypt-sha512} | Medium | Legacy systems, Linux/Unix compatibility |
| Crypt SHA-256 | {crypt-sha256} | Medium | Legacy systems, Linux/Unix compatibility |
| NT Hash | {nthash} | Low | Windows compatibility (not recommended for new systems) |
| Plaintext | (no prefix) | None | Required for protocols that need the cleartext password (CHAP, MS-CHAP/MS-CHAPv2, EAP-MD5); development/testing |
Prefix matching is case-insensitive, so LDAP RFC 2307bis style values like {PBKDF2-SHA512} work alongside the lowercase {pbkdf2-sha512} form.
Argon2 (Highest security)
Argon2 offers the strongest resistance against brute-force attacks among the supported algorithms, including GPU- and ASIC-based cracking. Radiator uses the Argon2id variant for balanced protection against side-channel and GPU attacks.
Argon2 is memory-hard by design, so each verification allocates the configured memory budget (e.g. 16 MiB at m=16384). Under high authentication volume this becomes the limiting factor: peak concurrent requests times m must fit in the server's RAM budget, and CPU stays busy for the full verification time. For example, 10 concurrent verifications at m=4096 reserve ~40 MB, and at m=16384 ~160 MB. If the deployment cannot guarantee that headroom, choose a lower-memory PBKDF2 variant rather than under-tuning Argon2.
Format:
{argon2}$argon2id$v=19$m=16384,t=2,p=1$<salt>$<hash>
Parameters:
m=4096(4 MiB),m=16384(16 MiB), orm=32768(32 MiB) - Memory cost in KiB (specified via-m 12/14/15where 2^n KiB)t=2ort=3- Time cost (number of iterations through memory)p=1orp=2- Parallelism (number of parallel threads)- Salt and hash are base64-encoded
Recommended settings:
- Standard:
t=2, p=1, m=4096(4 MiB) - Good balance for most use cases - High security:
t=2, p=1, m=16384(16 MiB) - Better protection against attacks - Very high security:
t=3, p=1, m=16384(16 MiB) - Excellent protection
See "Performance Considerations" section below for detailed timing benchmarks.
Generate Argon2 hash:
# Standard security (4 MiB)
echo -n "password" | argon2 $(openssl rand -base64 16) -id -t 2 -m 12 -p 1 -l 32 -e
# High security (16 MiB)
echo -n "password" | argon2 $(openssl rand -base64 16) -id -t 2 -m 14 -p 1 -l 32 -e
Example:
{argon2}$argon2id$v=19$m=16384,t=2,p=1$ZTM0ODY1YTUxZTQ1ZWFiNQ$MAz1wx2FHcKDw6vYVVUKTwR+Sc240NzMAQxjbtRhDbY
PBKDF2 (SHA-512 / SHA-256 / SHA-1)
PBKDF2-HMAC is the password hashing function specified in RFC 2898. Radiator supports the SHA-512, SHA-256 and SHA-1 variants. PBKDF2 is not memory-hard, so each verification needs only a few hundred bytes of working memory regardless of iteration count. This makes it well suited to high-volume authentication servers where Argon2's per-request memory budget would not fit.
When to choose PBKDF2:
- High-volume / memory-constrained deployments. PBKDF2 scales to thousands of concurrent verifications without the multi-MiB-per-request RAM cost of Argon2.
- FIPS 140 / NIST SP 800-132 compliance. PBKDF2 is an approved key derivation function under FIPS 140-3 and is recommended by NIST SP 800-132 for password-based key derivation; Argon2 is not (yet) on the FIPS-approved list.
- OWASP Password Storage Cheat Sheet lists PBKDF2 as an acceptable second choice after Argon2id, particularly for FIPS-validated environments.
- Interoperability with existing identity stores that emit
{PBKDF2-SHAxxx}or{PBKDF2}hashes (OpenLDAP, 389-ds, Dovecot, Django, Synology DSM, ...).
For green-field deployments with the CPU and memory headroom to absorb Argon2's per-request cost, prefer Argon2id. Otherwise PBKDF2-HMAC-SHA512 is the next-strongest option in Radiator.
Formats accepted on input:
-
PHC string (the standard portable format for PBKDF2-SHA256 / PBKDF2-SHA512):
{pbkdf2-sha512}$pbkdf2-sha512$i=210000,l=64$<b64-salt>$<b64-hash> {pbkdf2-sha256}$pbkdf2-sha256$i=600000,l=32$<b64-salt>$<b64-hash> -
LDAP RFC 2307bis layout (also commonly used for PBKDF2-SHA1, which has no PHC identifier):
{pbkdf2-sha512}<iterations>$<b64-salt>$<b64-hash> {pbkdf2-sha256}<iterations>$<b64-salt>$<b64-hash> {pbkdf2-sha1}<iterations>$<b64-salt>$<b64-hash> {pbkdf2}<iterations>$<b64-salt>$<b64-hash> # alias for PBKDF2-SHA1
Both standard and unpadded base64 encodings are accepted. The iteration count and salt embedded in the stored value are always honoured during verification, so hashes generated against older defaults keep working.
When generating new PBKDF2 hashes for Radiator, follow the OWASP Password Storage Cheat Sheet defaults: 1 300 000 iterations for SHA-1, 600 000 for SHA-256 and 210 000 for SHA-512, with at least a 16-byte CSPRNG salt. Hashes can be produced by the identity store (LDAP, application database, password manager, ...) or generated inside a Radiator pipeline with password_hash(...).
Example:
{pbkdf2-sha512}$pbkdf2-sha512$i=210000,l=64$Z2QuoEKKKa3xJ3OQM/CR6w$Q1qfeq43...
{pbkdf2-sha1}2$c2FsdA==$6mwBTcctb4zNHtkqzh1B8NjeiVc=
Crypt SHA-512
SHA-512 based crypt hashing, commonly used in Linux/Unix systems. Provides reasonable security for legacy applications.
Format:
{crypt-sha512}$6$rounds=5000$<salt>$<hash>
Example:
{crypt-sha512}$6$rounds=5000$saltsaltsal$hash...
Crypt SHA-256
SHA-256 based crypt hashing, similar to SHA-512 but with shorter hash length.
Format:
{crypt-sha256}$5$rounds=5000$<salt>$<hash>
Example:
{crypt-sha256}$5$rounds=5000$saltsaltsal$hash...
NT Hash
NT hash (MD4-based) used in Windows systems. Provided for compatibility but not recommended for new deployments due to weak security.
Format:
{nthash}<hex-encoded-hash>
Plaintext
Passwords without a prefix are treated as plaintext. Plaintext storage is required for authentication protocols where the server cannot verify the credential without knowing the actual cleartext password:
- CHAP (RFC 1994) - the server must compute
MD5(ident || password || challenge)to compare against the client response. - MS-CHAP / MS-CHAPv2 - the server must derive the NT hash from the cleartext at verification time (a stored
{nthash}is also accepted; see above). - EAP-MD5 - same MD5 challenge/response construction as CHAP.
- Digest authentication variants that hash the cleartext into
HA1per request.
For PAP and any protocol that only needs to compare a presented password against a stored value, prefer a hashed format (Argon2, PBKDF2, crypt-sha512) over plaintext.
Example:
mypassword123
Warning: Never use plaintext passwords in production environments.
Warning: If a plaintext password contains text that looks like a hash prefix (e.g., {argon2}, {crypt-sha256}), Radiator will attempt to parse it as a hashed password, causing authentication to fail. Always use hashed passwords to avoid this ambiguity.
Usage Examples
JSON File Backend
{
"users": [
{
"username": "alice",
"password": "{argon2}$argon2id$v=19$m=16384,t=2,p=1$..."
},
{
"username": "bob",
"password": "{crypt-sha512}$6$rounds=5000$..."
}
]
}
SQL Backend
CREATE TABLE users (
username VARCHAR(255) PRIMARY KEY,
password TEXT NOT NULL
);
INSERT INTO users (username, password) VALUES
('alice', '{argon2}$argon2id$v=19$m=16384,t=2,p=1$...'),
('bob', '{crypt-sha256}$5$rounds=5000$...');
Backend Configuration
No special configuration is needed. The password format is detected automatically:
backends {
sqlite "USERS" {
filename "users.db";
query "FIND_USER" {
statement "SELECT username, password FROM users WHERE username = ?";
bindings { aaa.identity; }
mapping {
user.username = username;
user.password = password; # Format auto-detected from prefix
}
}
}
}
Security Recommendations
- Pick the strongest algorithm your capacity supports: Argon2id gives the best resistance against modern attacks but is memory-hard; under high authentication volume, prefer PBKDF2-HMAC-SHA512 if the server cannot sustain Argon2's per-request memory and CPU cost
- Choose appropriate parameters: Use
t=2, p=1, m=4096(4 MiB) for standard security, orm=16384(16 MiB) for higher security. See "Performance Considerations" for sizing examples. - Generate unique salts: Always use cryptographically random salts (use
openssl randor equivalent) - Migrate legacy hashes: If using crypt-sha256/512, consider migrating to Argon2, or to PBKDF2-HMAC-SHA512 where interoperability with LDAP-style password stores is required
- Never use plaintext: Always hash passwords, even for development databases
- Avoid NT Hash: Only use for Windows integration where absolutely required
Performance Considerations
Password hashing is intentionally slow to prevent brute-force attacks. The longer the time to verify, the more resistant to brute-force attacks the hash is. It also costs more CPU time on Radiator server, so it is usually the dominant sizing guideline.
The following figures are example measurements. Measure on the target hardware before choosing production parameters.
| Algorithm | Parameters | CPU per verification | CPUs for 1k TPS | Comments |
|---|---|---|---|---|
| Argon2id | t=2, p=1, m=16384 (16 MiB) | ~19 ms | ~19 | Higher memory-cost Argon2 setting |
| Argon2id | t=2, p=1, m=32768 (32 MiB) | ~38 ms | ~38 | Higher memory-cost Argon2 setting |
| PBKDF2-SHA1 | i=1 300 000 (OWASP default) | ~133 ms | ~133 | Legacy interop; prefer SHA-256/512 for new data |
| PBKDF2-SHA256 | i=600 000 (OWASP default) | ~64 ms | ~64 | FIPS 140-approved KDF |
| PBKDF2-SHA512 | i=210 000 (OWASP default) | ~101 ms | ~101 | FIPS 140-approved KDF |
| Crypt SHA-512 | rounds=5000 (default) | ~2.1 ms | ~2 | Legacy Linux/Unix compatibility |
| Crypt SHA-256 | rounds=5000 (default) | ~0.6 ms | <1 | Legacy Linux/Unix compatibility |
| NT Hash | - | ~0.05 ms | <1 | Weak security; Windows compatibility only |
The "CPUs for 1k TPS" column is cpu_per_verification_ms: a single
fully busy core sustains 1000 / cpu_per_verification_ms verifications per
second, so 1k TPS needs approximately that many cores. Provision additional
headroom for the rest of the request pipeline and traffic bursts.
PBKDF2 cost scales linearly with i. Argon2 cost depends primarily on t and
m; increasing m also increases the per-request memory budget. Increasing
Argon2 p can change internal parallelism, but it does not guarantee a linear
throughput increase for authentication workloads. Pick the slowest hash the
available computing budget can absorb at peak load.
Related Documentation
- HTTP Basic Authentication - Using password hashing with HTTP authentication
- TOTP/HOTP Authentication - Two-factor authentication with password hashing
- jsonfile Backend - Using password hashes in JSON files
- SQL Backends - Using password hashes in databases
- How It Works
- Generating Hashes with password_hash(...)
- Basic syntax
- Parameterized syntax
- Supported Algorithms
- Argon2 (Highest security)
- PBKDF2 (SHA-512 / SHA-256 / SHA-1)
- Crypt SHA-512
- Crypt SHA-256
- NT Hash
- Plaintext
- Usage Examples
- JSON File Backend
- SQL Backend
- Backend Configuration
- Security Recommendations
- Performance Considerations
- Related Documentation
About Radiator software development security
Architecture Overview
Backend Load Balancing
Basic Installation
Built-in Environment Variables
Byte Size Units
Certificate Revocation Lists
Comparison Operators
Configuration Editor
Configuration Import and Export
Containers
Cron and interval timers
Data Types
Duration Units
Environment Variables
Execution Context
Execution Pipelines
Filters
Getting a Radiator License
Health check /live and /ready
High Availability and Load Balancing
High availability identifiers
HTTP Basic Authentication
Introduction
Linux systemd support
Local AAA Backends
Log storage and formatting
Management API privilege levels
Namespaces
Password Hashing
Password Rehashing During Login
Probabilistic Sampling
Prometheus scraping
PROXY Protocol Support
Radiator server health and boot up logic
Radiator sizing
Radiator software releases
RadiatorDB
RadiatorDB Backup
RadiatorDB CLI
RadiatorDB Installation
RadiatorDB REST API
Rate Limiting
Rate Limiting Algorithms
Reverse Dynamic Authorization
Service Level Objective
TACACS+ Authentication, Authorization, and Accounting
Template Rendering CLI
Timestamp Format
Timestamps
Tools radiator-client
TOTP/HOTP Authentication
What is Radiator?
YubiKey Authentication
YubiKey Context Variables
About Radiator software development security
Architecture Overview
Backend Load Balancing
Basic Installation
Built-in Environment Variables
Byte Size Units
Certificate Revocation Lists
Comparison Operators
Configuration Editor
Configuration Import and Export
Containers
Cron and interval timers
Data Types
Duration Units
Environment Variables
Execution Context
Execution Pipelines
Filters
Getting a Radiator License
Health check /live and /ready
High Availability and Load Balancing
High availability identifiers
HTTP Basic Authentication
Introduction
Linux systemd support
Local AAA Backends
Log storage and formatting
Management API privilege levels
Namespaces
Password Hashing
Password Rehashing During Login
Probabilistic Sampling
Prometheus scraping
PROXY Protocol Support
Radiator server health and boot up logic
Radiator sizing
Radiator software releases
RadiatorDB
RadiatorDB Backup
RadiatorDB CLI
RadiatorDB Installation
RadiatorDB REST API
Rate Limiting
Rate Limiting Algorithms
Reverse Dynamic Authorization
Service Level Objective
TACACS+ Authentication, Authorization, and Accounting
Template Rendering CLI
Timestamp Format
Timestamps
Tools radiator-client
TOTP/HOTP Authentication
What is Radiator?
YubiKey Authentication
YubiKey Context Variables