{"record":{"id":"17b514f5eb4aff9e","repo":"yiisoft/yii2","slug":"cost-must-be-between-4-and-31","errorCode":null,"errorMessage":"Cost must be between 4 and 31.","messagePattern":"Cost must be between 4 and 31\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"warning","filePath":"framework/base/Security.php","lineNumber":524,"sourceCode":"    /**\n     * Generates a salt that can be used to generate a password hash.\n     *\n     * The PHP [crypt()](https://www.php.net/manual/en/function.crypt.php) built-in function\n     * requires, for the Blowfish hash algorithm, a salt string in a specific format:\n     * \"$2a$\", \"$2x$\" or \"$2y$\", a two digit cost parameter, \"$\", and 22 characters\n     * from the alphabet \"./0-9A-Za-z\".\n     *\n     * @param int $cost the cost parameter\n     * @return string the random salt value.\n     * @throws InvalidArgumentException if the cost parameter is out of the range of 4 to 31.\n     * @deprecated since 2.0.55. This method is no longer used internally\n     * as [[generatePasswordHash()]] now relies on `password_hash()`. Will be removed in 2.2.\n     */\n    protected function generateSalt($cost = 13)\n    {\n        $cost = (int) $cost;\n        if ($cost < 4 || $cost > 31) {\n            throw new InvalidArgumentException('Cost must be between 4 and 31.');\n        }\n\n        // Get a 20-byte random string\n        $rand = $this->generateRandomKey(20);\n        // Form the prefix that specifies Blowfish (bcrypt) algorithm and cost parameter.\n        $salt = sprintf('$2y$%02d$', $cost);\n        // Append the random salt data in the required base64 format.\n        $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22));\n\n        return $salt;\n    }\n\n    /**\n     * Performs string comparison using timing attack resistant approach.\n     * @see https://codereview.stackexchange.com/q/13512\n     * @param string $expected string to compare.\n     * @param string $actual user-supplied string.\n     * @return bool whether strings are equal.","sourceCodeStart":506,"sourceCodeEnd":542,"githubUrl":"https://github.com/yiisoft/yii2/blob/66f00d18a29b520f85e8e8f1e32d1e7e7b556cac/framework/base/Security.php#L506-L542","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (custom Security subclass)\n$salt = $this->generateSalt($this->cost); // $this->cost = null -> (int)null = 0 -> throws\n\n// after\n$cost = max(4, min(31, (int) ($this->cost ?: 13)));\n$hash = Yii::$app->security->generatePasswordHash($password, $cost);","handlingStrategy":"validation","validationCode":"// Before any generateSalt()/custom hashing call\n$cost = (int) $cost;\nif ($cost < 4 || $cost > 31) {\n    throw new \\InvalidArgumentException('bcrypt cost must be 4..31, got ' . $cost);\n}\n// better: avoid generateSalt() entirely\n$hash = Yii::$app->security->generatePasswordHash($password, $cost ?: 13);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["php","yii2","security","bcrypt","deprecated","configuration"],"backgroundTag":"bcrypt-cost-out-of-range","analyzedSha":"66f00d18a29b520f85e8e8f1e32d1e7e7b556cac","analyzedAt":"2026-08-17T05:17:23.470Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}