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

  1. Stop calling generateSalt(); use Security::generatePasswordHash($password, $cost) or password_hash($password, PASSWORD_BCRYPT, ['cost' => $cost]), which validate the cost themselves.
  2. Sanitize the configured cost before any use: $cost = (int) $cost; if ($cost < 4 || $cost > 31) { $cost = 13; }.
  3. Audit and remove custom Security subclasses that override password hashing, since the modern base class already delegates to password_hash().
  4. 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

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


AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17). Data as JSON: /api/errors/17b514f5eb4aff9e. Report an issue: GitHub.