Executive Summary
A CVSS 9.8 Critical cryptographic weakness (CVE-2026-16235) has been disclosed in Crypt::Password, a widely used Perl module for generating and verifying password hashes. Through version 0.28, the module uses Perl's built-in rand() function — a predictable, non-cryptographic pseudorandom number generator — to generate password salts. This renders all stored password hashes vulnerable to offline dictionary and brute-force attacks.
The fix requires upgrading to a patched version that uses a cryptographically secure random source.
Vulnerability Overview
| Attribute | Value |
|---|---|
| CVE ID | CVE-2026-16235 |
| CVSS Score | 9.8 (Critical) |
| Type | Use of Cryptographically Weak Pseudorandom Number Generator (PRNG) |
| CWE | CWE-338 |
| Attack Vector | Network (offline attack on stolen hashes) |
| Privileges Required | None |
| User Interaction | None |
| Affected Module | Crypt::Password for Perl (through v0.28) |
Technical Details
The Problem: rand() Is Not Cryptographic
Perl's built-in rand() function uses a linear congruential generator (LCG) or similar PRNG seeded from a predictable source. Unlike cryptographically secure PRNGs (CSPRNGs), rand() has two critical flaws when used for security purposes:
- Predictable output: Given knowledge of the internal state (seed), all future and past outputs can be computed.
- Weak seed entropy: The seed is often derived from the system time or process ID, both of which have limited entropy and are discoverable.
# VULNERABLE — Crypt::Password <= 0.28
sub _make_salt {
my $salt = '';
$salt .= chr(int(rand(256))) for 1..8; # rand() is NOT cryptographic!
return $salt;
}
# SECURE — what the fix should use
use Crypt::URandom qw(urandom);
sub _make_salt {
return urandom(8); # Uses /dev/urandom — cryptographically secure
}Impact on Stored Password Hashes
When salts are predictable, an attacker who obtains the password hash database can:
- Predict or enumerate the salt space — rather than treating the salt as random, the attacker can calculate the probable salt values based on when accounts were created (timestamp-derived seeds).
- Precompute targeted rainbow tables — with a constrained salt space, targeted precomputation becomes feasible.
- Dramatically accelerate offline cracking — reducing the effective difficulty of brute-forcing passwords.
Severity Justification
The CVSS 9.8 score reflects that this vulnerability undermines the fundamental security property that password hashing is designed to provide. Any application storing passwords via vulnerable Crypt::Password versions should treat its entire password database as potentially compromised if the hashes have been exposed.
Affected Versions
| Module | Affected Versions | Fixed Version |
|---|---|---|
| Crypt::Password (Perl) | <= 0.28 | 0.29+ |
Remediation
Step 1: Update the Module
# Update via CPAN
cpan Crypt::Password
# Or via cpanm
cpanm Crypt::Password
# Verify the installed version
perl -MCrypt::Password -e 'print $Crypt::Password::VERSION, "\n"'Step 2: Force Password Re-Hash on Next Login
Simply updating the module does not fix existing hashes in your database. Previously stored hashes were generated with weak salts and remain vulnerable if the database is compromised. Implement re-hashing on next login:
use Crypt::Password;
sub authenticate_user {
my ($username, $password) = @_;
my $stored_hash = get_user_hash($username);
if (check_password($password, $stored_hash)) {
# Authentication succeeded — check if hash needs upgrading
if (hash_needs_rehash($stored_hash)) {
# Re-hash with new secure salt
my $new_hash = password($password);
update_user_hash($username, $new_hash);
}
return 1; # Authenticated
}
return 0;
}Step 3: Assess Exposure
If your password hash database has ever been exposed (breach, backup leak, insider access):
- Force a password reset for all users — treat stored hashes as cracked
- Notify affected users per your breach notification obligations
- Rotate all API keys, session tokens, and credentials that may have been derived from or stored alongside the user data
- Review access logs for signs of account takeover using cracked passwords
Broader Context: Password Hashing Best Practices
This vulnerability highlights a fundamental rule in cryptographic engineering: use purpose-built, cryptographically secure APIs for security-sensitive operations.
| Use Case | Correct API | Incorrect API |
|---|---|---|
| Password salt generation | /dev/urandom, Crypt::URandom | rand(), srand(time()) |
| Password hashing | bcrypt, Argon2id, scrypt | MD5, SHA-1, unsalted SHA-256 |
| Token generation | Crypt::URandom, urandom(32) | rand(), timestamp-based |
| Key derivation | PBKDF2, Argon2 | Direct hash |
Recommended Perl Password Hashing Libraries
# Preferred: Crypt::Argon2 (Argon2id — current best practice)
use Crypt::Argon2 qw(argon2id_pass argon2id_verify);
my $hash = argon2id_pass($password, 3, '64M', 1, 16);
# Also acceptable: Authen::Passphrase::BlowfishCrypt (bcrypt)
use Authen::Passphrase::BlowfishCrypt;
my $hasher = Authen::Passphrase::BlowfishCrypt->new(cost => 12, salt_random => 1);Detection
| Indicator | Description |
|---|---|
| Crypt::Password version <= 0.28 in CPAN installed modules | Vulnerable version in use |
| Password hashes with low-entropy salts in your database | Evidence of vulnerable hash generation |
| Reuse of salts across multiple accounts | Confirms weak RNG seeding |
# Check installed version
cpan -D Crypt::Password | grep "Installed"
# Or
perl -MCrypt::Password -e 'print $Crypt::Password::VERSION, "\n"'References
- NVD — CVE-2026-16235
- CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator
- OWASP Password Storage Cheat Sheet
- Crypt::Password on CPAN