Weak Password Hash
ID |
java.weak_password_hash |
Severity |
critical |
Resource |
Cryptography |
Language |
Java |
Tags |
CWE:256, CWE:759, CWE:760, CWE:916, NIST.SP.800-53, OWASP:2021:A2, PCI-DSS:6.5.3 |
Description
Weak Password Hashing is a vulnerability that occurs when passwords are hashed using algorithms that are deemed insecure.
This can lead to the exposure of user passwords if an attacker gains access to hashed values and uses techniques such as brute force or dictionary attacks, using tools like Hashcat, L0phtcrack, or John The Ripper.
Rationale
Password hashing is a common technique used to secure user passwords by transforming them into a fixed-size string of characters, which ideally should be irreversible. However, if a weak or deprecated hashing algorithm is used, such as MD5 or SHA-1, attackers can more easily reverse-engineer or crack the hashed values.
Hash algorithms like MD5 and SHA-1 were widely used but are now considered insecure due to vulnerabilities found in their design that allow for collisions, where two different inputs produce the same hash, facilitating the cracking process.
To counter "rainbow table" attacks, which use pre-computed hash tables for password cracking, each password should be combined with a unique, random salt before hashing. This salt is stored alongside the hash.
Modern hardware can compute billions of hashes per second, making brute-force attacks on simple hashes feasible. The matter is to use strong and modern hash algorithms that are "slow enough", specifically designed for password hashing, such as Argon2, scrypt, bcrypt, or PBKDF2.
Modern password hashing algorithms DO NOT protect against (1) weak, easy-to-guess passwords, (2) when the same user password is reused across multiple systems, (3) against default known passwords, or (4) against hardcoding passwords or password leaking vulnerabilities. Your software must use proper password hashing and salt generation, but additional security measures such as multi-factor authentication are recommended. |
Here’s an example of weak password hashing using SHA-1 in a Java application:
import java.security.MessageDigest;
public class WeakHashingExample {
// FLAW: Weak for password hashing, no salt...
public static String hashPassword(String password) throws Exception {
MessageDigest sha1 = MessageDigest.getInstance("SHA-256");
byte[] hashedBytes = sha1.digest(password.getBytes());
return bytesToHex(hashedBytes);
}
public
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
The above code uses SHA-1, a hashing algorithm that is considered outdated and susceptible to attack, to hash passwords.
Remediation
To mitigate against password-guessing attacks, follow these best practices:
Recommended Password Hashing Scheme:
-
Use proven key stretching / password hashing algorithms, CPU- and memory-intensive hash functions, and : PBKDF2, bcrypt, scrypt, or Argon2.
-
For legacy systems where a specific password hashing algorithm cannot be used, use a common hash function (e.g., SHA-256) with many iterations (600,000 as of 2024).
-
Always use a long (at least 128-bit), random, unique salt for each password.
-
Configure work factor parameters to balance security and performance.
-
Optionally, add a secret "pepper" for additional security.
Which Hashing Algorithm to Use?:
Based on the most recent recommendations and research, Argon2id is the preferred modern password hashing algorithm for new implementations in 2025. Here is a ranking of the recommended algorithms, but your mileage may vary:
-
Argon2id: Winner of the 2015 Password Hashing Competition, Argon2id is considered the most secure and future-proof option. It offers a balanced approach to resisting both side-channel and GPU-based attacks.
-
scrypt: Excellent for systems requiring strong protection against hardware-based attacks, particularly in cryptocurrency applications. Originally designed for key derivation.
-
bcrypt: Still widely used and considered secure for general-purpose password hashing in web applications.
-
PBKDF2: While still used in some password managers and recommended by NIST, it is generally considered less secure than the above options for password hashing. For compliance with FIPS 140-1, PBKDF2 with SHA-256 or SHA-512 is recommended.
-
SHA-256 with large iteration counts is not recommended for password hashing, as it’s vulnerable to GPU-based attacks and lacks the memory-hardness of more modern algorithms. Use it as a fallback for legacy systems.
Common Vulnerabilities to Avoid:
Additional Considerations:
-
Users often reuse passwords across systems.
-
High-profile threat actors may use GPU accelerators or even specialized hardware like FPGAs to crack password hashes.
-
Use libraries that automate salt generation when possible.
-
If generating salt manually, use a Cryptographically Secure Pseudo-Random Number Generator (CSPRNG).
-
Salt should be at least 128 bits.
-
Never use predictable information (e.g., username) as salt.
-
Implement constant-time comparison for hash verification to prevent timing attacks.
In Java you may use the Bouncy Castle’s Argon2BytesGenerator
class for password hashing.
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import org.bouncycastle.crypto.params.Argon2Parameters;
import java.security.MessageDigest;
import java.util.Base64;
import java.security.SecureRandom;
public class Argon2Hashing {
public static String hashPassword(String password, byte[] salt) {
// Set realistic values for Argon2 parameters
int parallelism = 2; // Use 2 threads
int memory = 47104; // Use 46 MiB of memory
int iterations = 3; // Run 3 iterations
int hashLength = 32; // Generate a 32 byte (256 bit) hash
var generator = new Argon2BytesGenerator();
// You need to generate a random salt, one per password
var builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
.withSalt(salt)
.withParallelism(parallelism) // Parallelism factor
.withMemoryAsKB(memory) // Memory cost
.withIterations(iterations); // Number of iterations
generator.init(builder.build());
byte[] result = new byte[hashLength];
generator.generateBytes(password.toCharArray(), result);
return Base64.getEncoder().encodeToString(result);
}
public static boolean verifyPassword(String password, String hashedPassword, byte[] salt) {
String generatedHash = hashPassword(password, salt);
byte[] decoded = Base64.getDecoder().decode(generatedHash);
byte[] decodedHashed = Base64.getDecoder().decode(hashedPassword);
return MessageDigest.isEqual(decoded, decodedHashed);
}
public static byte[] randomSalt() {
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[32];
sr.nextBytes(salt);
return salt;
}
}
This approach uses Argon2id with a high iteration count and a secure random salt, and comparisons are done in constant time to prevent timing attacks.
If you are using Spring Security, you can use Spring Security’s built-in Argon2PasswordEncoder
and the PasswordEncoder
interface for password hashing:
@Configuration
public class SecurityConfig {
@Bean public PasswordEncoder passwordEncoder() {
// salt length = 32 bytes, hash length = 64 bytes,
// parallelism = 1, memory = 47104 (46 MiB), iterations = 3
return new Argon2PasswordEncoder(32, 64, 1, 47104, 3);
}
}
then inject it into your services (for example, UserService
):
@Service
public class UserService {
private final PasswordEncoder passwordEncoder;
public UserService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
public void createUser(String username, String password) {
String hashedPassword = passwordEncoder.encode(password);
// Save user with hashed password
// ...
}
public boolean verifyPassword(String password, String storedHash) {
return passwordEncoder.matches(password, storedHash);
}
}
Configuration
The rule has the following configurable parameters:
-
thresholds
, that indicates the different thresholds used for each of the hashing algorithms.
Example:
# The thresholds used for each of the hashing algorithms
thresholds:
# Argon2d iterations
- argon2.iterations: 3
# Argon2d memory to use (in KiB)
- argon2.mem: 12
# Argon2d threads
- argon2.parallel: 1
# Scrypt parameters explained in https://words.filippo.io/the-scrypt-parameters/.
# Recommended values are N=2^17, r=8, p=1
# scrypt cpu factor (N), 131072 means 2^17 (recommended value for 2024)
- scrypt.cpu: 131072
# scrypt memory factor (r): the hash is 2r wider wrt. the core hash function (Salsa20 core). 8 means 1024 bytes
# The real memory usage is 𝑁 × 2𝑟 × 64 = (128 × 𝑁 × 𝑟) bytes
- scrypt.mem: 8
# scrypt parallelization parameter (p), number of instances of mixing function that runs independently for salt to the final PBKDF2.
- scrypt.parallel: 1
# bcrypt strength
- bcrypt.strength: 10
# PBKDF2 iterations. NIST recommends 600,000 iterations for HMAC-SHA-256 as of 2020.
# Never use less than 10,000
- pbkdf2: 600000
# Iterations for Legacy password hashing based on "fast" hash functions such as SHA-256
- fast.hash: 150000
References
-
CWE-256: Plaintext Storage of a Password.
-
CWE-759: Use of a One-Way Hash without a Salt.
-
CWE-760: Use of a One-Way Hash with a Predictable Salt.
-
CWE-916: Use of Password Hash With Insufficient Computational Effort.
-
OWASP - Top 10 2021 Category A02 : Cryptographic Failures.
-
Password Storage, in OWASP Cheat Sheet Series.
-
Steve Thomas, one of the Password Hashing Competition (PHC) judges, maintains this page with minimum settings for multiple password hashing algorithms.
-
Secure Password Hashing in Java: Best Practices and Code Examples in DZone.