spring-projects/spring-security · error · IllegalArgumentException
Bad number of rounds
Error message
Bad number of rounds
What it means
roundsForLogRounds converts the bcrypt cost factor (log_rounds) to an iteration count and throws this IllegalArgumentException when log_rounds is outside the valid 4..31 range. Bcrypt requires at least 2^4 iterations; the upper bound prevents overflow of the 1L << log_rounds computation.
Source
Thrown at crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java:515
lr[0] ^= streamtoword(data, doffp);
lr[1] ^= streamtoword(data, doffp);
encipher(lr, 0);
this.P[i] = lr[0];
this.P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2) {
lr[0] ^= streamtoword(data, doffp);
lr[1] ^= streamtoword(data, doffp);
encipher(lr, 0);
this.S[i] = lr[0];
this.S[i + 1] = lr[1];
}
}
static long roundsForLogRounds(int log_rounds) {
if (log_rounds < 4 || log_rounds > 31) {
throw new IllegalArgumentException("Bad number of rounds");
}
return 1L << log_rounds;
}
/**
* Perform the central password hashing step in the bcrypt scheme
* @param password the password to hash
* @param salt the binary salt to hash with the password
* @param log_rounds the binary logarithm of the number of rounds of hashing to apply
* @param sign_ext_bug true to implement the 2x bug
* @param safety bit 16 is set when the safety measure is requested
* @return an array containing the binary hashed password
*/
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, boolean sign_ext_bug, int safety,
boolean for_check) {
int cdata[] = bf_crypt_ciphertext.clone();
int clen = cdata.length;
View on GitHub (pinned to 96852e8860)
Solutions
- Pass a log-rounds value between 4 and 31 (10 is the common default) to BCryptPasswordEncoder/gensalt
- If strength comes from config, clamp or validate it: Math.max(4, Math.min(31, strength))
- Fix the stored hash or regenerate it if its cost segment is corrupt
Example fix
// before
int strength = Integer.parseInt(props.getProperty("bcrypt.strength", "0"));
// after
int strength = Math.max(4, Math.min(31, Integer.parseInt(props.getProperty("bcrypt.strength", "10")))); Defensive patterns
Strategy: validation
Validate before calling
static int sanitizeStrength(int strength) {
if (strength < 4 || strength > 31) throw new IllegalArgumentException("bcrypt strength must be 4..31, got " + strength);
return strength;
} Try / catch
try {
String salt = BCrypt.gensalt(strength);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("Bad number of rounds")) {
strength = 10; // fall back to default cost
salt = BCrypt.gensalt(strength);
} else throw e;
} Prevention
- Provide a sane default strength (10) in configuration with explicit bounds
- Clamp or fail-fast on config-loaded strength values at startup
- Remember strength is a log2 cost factor, not a linear count
When it happens
Trigger: Passing a cost factor below 4 or above 31 to gensalt (e.g. gensalt(2) or gensalt(40)), or parsing a hash whose cost segment is outside that range via hashpw -> crypt_raw -> roundsForLogRounds.
Common situations: Misreading strength as a linear value (using 10..16 incorrectly is fine, but 0/1/2 is not); loading a configurable strength from properties where the default is 0; a stored hash with a corrupted cost field.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Invalid len
- Invalid maxolen
- Invalid encoded Argon2-hash
- Amount of performance parameters invalid
- Invalid memory parameter
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/7a3bbafcdb641f75.
Report an issue: GitHub.