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

  1. Pass a log-rounds value between 4 and 31 (10 is the common default) to BCryptPasswordEncoder/gensalt
  2. If strength comes from config, clamp or validate it: Math.max(4, Math.min(31, strength))
  3. 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

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


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/7a3bbafcdb641f75. Report an issue: GitHub.