yiisoft/yii2 · warning · InvalidArgumentException
Cost must be between 4 and 31.
Error message
Cost must be between 4 and 31.
What it means
Thrown by the deprecated Security::generateSalt() when the bcrypt cost parameter, cast to int, is below 4 or above 31. Since 2.0.55 the method is no longer used internally because generatePasswordHash() relies on password_hash(); the exception now only fires in subclasses or copied legacy code that still call it. Note that a non-numeric string cost casts to 0 and also throws.
Source
Thrown at framework/base/Security.php:524
/**
* Generates a salt that can be used to generate a password hash.
*
* The PHP [crypt()](https://www.php.net/manual/en/function.crypt.php) built-in function
* requires, for the Blowfish hash algorithm, a salt string in a specific format:
* "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters
* from the alphabet "./0-9A-Za-z".
*
* @param int $cost the cost parameter
* @return string the random salt value.
* @throws InvalidArgumentException if the cost parameter is out of the range of 4 to 31.
* @deprecated since 2.0.55. This method is no longer used internally
* as [[generatePasswordHash()]] now relies on `password_hash()`. Will be removed in 2.2.
*/
protected function generateSalt($cost = 13)
{
$cost = (int) $cost;
if ($cost < 4 || $cost > 31) {
throw new InvalidArgumentException('Cost must be between 4 and 31.');
}
// Get a 20-byte random string
$rand = $this->generateRandomKey(20);
// Form the prefix that specifies Blowfish (bcrypt) algorithm and cost parameter.
$salt = sprintf('$2y$%02d$', $cost);
// Append the random salt data in the required base64 format.
$salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22));
return $salt;
}
/**
* Performs string comparison using timing attack resistant approach.
* @see https://codereview.stackexchange.com/q/13512
* @param string $expected string to compare.
* @param string $actual user-supplied string.
* @return bool whether strings are equal.View on GitHub (pinned to 66f00d18a2)
Solutions
- Stop calling generateSalt(); use Security::generatePasswordHash($password, $cost) or password_hash($password, PASSWORD_BCRYPT, ['cost' => $cost]), which validate the cost themselves.
- Sanitize the configured cost before any use: $cost = (int) $cost; if ($cost < 4 || $cost > 31) { $cost = 13; }.
- Audit and remove custom Security subclasses that override password hashing, since the modern base class already delegates to password_hash().
- Keep cost at sane values (10-13); each +1 doubles runtime.
Example fix
// before (custom Security subclass) $salt = $this->generateSalt($this->cost); // $this->cost = null -> (int)null = 0 -> throws // after $cost = max(4, min(31, (int) ($this->cost ?: 13))); $hash = Yii::$app->security->generatePasswordHash($password, $cost);
Defensive patterns
Strategy: validation
Validate before calling
// Before any generateSalt()/custom hashing call
$cost = (int) $cost;
if ($cost < 4 || $cost > 31) {
throw new \InvalidArgumentException('bcrypt cost must be 4..31, got ' . $cost);
}
// better: avoid generateSalt() entirely
$hash = Yii::$app->security->generatePasswordHash($password, $cost ?: 13); Prevention
- Treat generateSalt() as dead code: it is deprecated since 2.0.55 and removed in 2.2
- Let password_hash()/generatePasswordHash() validate the cost instead of pre-building salts
- Validate numeric config values at config load time, not deep inside hashing code
When it happens
Trigger: A custom Security subclass overriding generatePasswordHash() and forwarding a config-supplied cost that is null, an empty string (casts to 0), or a number outside 4..31; calling $this->generateSalt(32) or generateSalt(3) directly; a cost read from a missing params/config key via ArrayHelper::getValue() returning null.
Common situations: Pre-2.0.55 code copied into a current project; a 'cost' => 32 tuning attempt for extra security; cost configured as a string like '13x' or left null after a config refactor; upgrading Yii2 and not removing the old Security subclass override.
Related errors
- Password must be a string and cannot be empty.
- Hash is invalid.
- Encryption requires the OpenSSL PHP extension
- {cipher} is not an allowed cipher
- Invalid parameters to hash_hkdf()
AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17).
Data as JSON: /api/errors/17b514f5eb4aff9e.
Report an issue: GitHub.