PHP Conference Japan 2024

crypt

(PHP 4, PHP 5, PHP 7, PHP 8)

crypt单向字符串哈希

警告

此函数尚不支持二进制安全!

描述

crypt(#[\SensitiveParameter] string $string, string $salt): string

crypt() 使用标准的基于Unix DES 的算法或替代算法返回哈希字符串。password_verify()crypt() 兼容。因此,由 crypt() 创建的密码哈希可以与 password_verify() 一起使用。

在PHP 8.0.0之前,salt 参数是可选的。但是,如果没有 saltcrypt() 会创建一个弱哈希,并引发E_NOTICE 错误。为了更好的安全性,请确保指定足够强的盐值。

password_hash() 使用强哈希,生成强盐值,并自动应用正确的轮数。password_hash() 是一个简单的 crypt() 包装器,并与现有的密码哈希兼容。建议使用 password_hash()

哈希类型由salt参数触发。如果没有提供salt,PHP将自动生成一个标准的两位(DES)salt或一个十二位(MD5)salt,这取决于MD5 crypt()的可用性。PHP设置了一个名为CRYPT_SALT_LENGTH的常量,它指示可用哈希允许的最长有效盐值。

基于标准DES的crypt() 将salt作为输出的前两位字符返回。它也只使用string的前八个字符,因此以相同八个字符开头的较长字符串将生成相同的结果(当使用相同的salt时)。

支持以下哈希类型

  • CRYPT_STD_DES - 标准基于DES的哈希,具有来自字母"./0-9A-Za-z"的两位salt。在salt中使用无效字符将导致crypt()失败。
  • CRYPT_EXT_DES - 扩展的基于DES的哈希。“salt”是一个9个字符的字符串,由一个下划线后跟4个迭代计数字符和4个salt字符组成。这4个字符的字符串中的每一个都编码24位,最低有效字符在前。值063编码为./0-9A-Za-z。在salt中使用无效字符将导致crypt()失败。
  • CRYPT_MD5 - 以$1$开头的十二个字符salt的MD5哈希
  • CRYPT_BLOWFISH - Blowfish 哈希,salt如下:"$2a$"、"$2x$"或"$2y$",一个两位成本参数、"$"和22个来自字母"./0-9A-Za-z"的字符。在salt中使用此范围之外的字符将导致crypt()返回零长度字符串。两位成本参数是底层基于Blowfish的哈希算法的迭代计数的以2为底的对数,必须在04-31范围内,此范围之外的值将导致crypt()失败。“$2x$”哈希可能较弱;“$2a$”哈希兼容并减轻了这种弱点。对于新的哈希,应该使用“$2y$”。
  • CRYPT_SHA256 - 以$5$为前缀的十六个字符salt的SHA-256哈希。如果salt字符串以'rounds=<N>$'开头,则N的数值用于指示哈希循环应执行多少次,类似于Blowfish上的成本参数。默认轮数为5000,最小值为1000,最大值为999,999,999。此范围之外的任何N选择都将被截断到最近的限制。
  • CRYPT_SHA512 - 以$6$为前缀的十六个字符salt的SHA-512哈希。如果salt字符串以'rounds=<N>$'开头,则N的数值用于指示哈希循环应执行多少次,类似于Blowfish上的成本参数。默认轮数为5000,最小值为1000,最大值为999,999,999。此范围之外的任何N选择都将被截断到最近的限制。

参数

string

要哈希的字符串。

注意

使用CRYPT_BLOWFISH算法,将导致string参数被截断到最大长度72字节。

salt

用于哈希的基础盐字符串。如果没有提供,则行为由算法实现定义,并可能导致意外结果。

返回值

返回哈希字符串或长度小于13个字符的字符串,并且保证在失败时与salt不同。

警告

验证密码时,应使用不会受到时序攻击影响的字符串比较函数来比较crypt()的输出与先前已知的哈希值。PHP为此目的提供了hash_equals()

变更日志

版本 描述
8.0.0 salt不再是可选的。

示例

示例 #1 crypt() 示例

<?php
$user_input
= 'rasmuslerdorf';
$hashed_password = '$6$rounds=1000000$NJy4rIPjpOaU$0ACEYGg/aKCY3v8O8AfyiO7CTfZQ8/W231Qfh2tRLmfdvFD6XfHk12u6hMr9cYIA4hnpjLNSTRtUwYr9km9Ij/';

// 以与非PHP软件兼容的方式验证现有的crypt()哈希。
if (hash_equals($hashed_password, crypt($user_input, $hashed_password))) {
echo
"密码验证成功!";
}
?>

备注

注意: 没有解密函数,因为crypt() 使用单向算法。

参见

  • hash_equals() - 抵御时序攻击的安全字符串比较
  • password_hash() - 创建密码哈希
  • 有关更多信息,请参阅您的crypt函数的Unix手册页

添加注释

用户贡献笔记 5条笔记

bob dot orr at mailinator dot com
9年前
此评论页面的第2条评论(截至2015年2月)已有9年历史,并推荐使用phpass。我已经独立审核了该产品的安全性,虽然它仍然被推荐用于密码安全,但实际上它是不安全的,不应该使用。它多年来没有更新(仍然是v0.3版),并且有更好的更新替代方案,例如使用更新的PHP内置password_hash()函数。请大家花几分钟时间确认我说的是否准确(例如,自己查看phpass代码),然后点击向下箭头将phpass评论沉到底部。这样做将提高互联网的安全性。

对于那些想要了解详情的人:phpass源代码中使用md5()和microtime()作为后备方案。它不会终止,而是继续执行代码。作者试图让它在任何地方都能运行的意图是值得赞扬的,但是,在应用程序安全方面,这种立场实际上适得其反。在安全环境中,唯一正确的答案是终止应用程序,而不是回退到可能被利用的弱势地位(通常是通过强制发生这种较弱的立场)。
Marten Jacobs
10年前
据我了解,Blowfish通常被认为是一种安全的哈希算法,即使用于企业用途也是如此(如果我错了,请纠正我)。因此,我创建了使用此算法创建和检查安全密码哈希的函数,并使用(也被认为是密码安全的)openssl_random_pseudo_bytes函数生成盐值。

<?php
/*
* 为给定的密码生成安全哈希。成本传递给Blowfish算法。
* 查看PHP手册中crypt函数的页面以了解更多关于此设置的信息。
*/
function generate_hash($password, $cost=11){
/* 要生成盐值,首先生成足够多的随机字节。因为
* base64为每6位返回一个字符,所以我们应该生成至少22*6/8=16.5字节,所以我们生成17个字节。然后我们获取前
* 22个base64字符
*/
$salt=substr(base64_encode(openssl_random_pseudo_bytes(17)),0,22);
/* 由于Blowfish使用字母表./A-Za-z0-9的盐值,我们必须
* 将base64字符串中的任何'+'替换为'.'。我们不必对'='做任何处理,因为这仅在b64字符串被
* 填充时才会出现,这始终在前22个字符之后。
*/
$salt=str_replace("+",".",$salt);
/* 接下来,创建一个将传递给crypt的字符串,其中包含所有
* 设置,用美元符号分隔
*/
$param='$'.implode('$',array(
"2y", //选择最安全的Blowfish版本(>=PHP 5.3.7)
str_pad($cost,2,"0",STR_PAD_LEFT), //用两位数字添加成本
$salt //添加盐值
));

//现在进行实际的哈希计算
return crypt($password,$param);
}

/*
* 使用generate_hash函数生成的哈希值检查密码。
*/
function validate_pw($password, $hash){
/* 使用可用的哈希值作为选项参数重新生成哈希值应该
* 如果传递相同的密码,则会产生相同的哈希值。
*/
return crypt($password, $hash)==$hash;
}
?>
kaminski at istori dot com
13年前
这是一个为CRYPT_BLOWFISH哈希类型生成伪随机盐值的表达式

<?php $salt = substr(str_replace('+', '.', base64_encode(pack('N4', mt_rand(), mt_rand(), mt_rand(), mt_rand()))), 0, 22); ?>

它适用于mt_getrandmax() == 2147483647的系统。

生成的盐值长度为128位,填充到132位,然后用22个base64字符表示。(CRYPT_BLOWFISH只使用128位作为盐值,即使22个base64字符中有132位。如果你检查CRYPT_BLOWFISH的输入和输出,你会发现它忽略了输入的最后四位,并在输出中将其设置为零。)

请注意,mt_rand()返回的四个32位字的高位始终为零(因为mt_getrandmax == 2^31),所以只有124位是伪随机的。在我的应用程序中,我认为这是可以接受的。
steve at tobtu dot com
11年前
要生成盐值,请使用mcrypt_create_iv()而不是mt_rand(),因为无论你调用mt_rand()多少次,它最多只有32位的熵。在大约2^16个用户之后,你将开始看到盐值冲突。mt_rand()的种子设置很差,所以这种情况应该会更早发生。

对于bcrypt,这实际上将生成一个128位的盐值
<?php $salt = strtr(base64_encode(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM)), '+', '.'); ?>

*** 细节讨论 ***
22个字符的盐值的最后一个字符是2位。
base64_encode()将有这四个字符“AQgw”
bcrypt将有这四个字符“.Oeu”

你不需要进行完整的转换,因为它们“舍入”到不同的字符
echo crypt('', '$2y$05$.....................A') . "\n";
echo crypt('', '$2y$05$.....................Q') . "\n";
echo crypt('', '$2y$05$.....................g') . "\n";
echo crypt('', '$2y$05$.....................w') . "\n";

$2y$05$......................J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq
$2y$05$.....................O/jw2XygQa2.LrIT7CFCBQowLowDP6Y.
$2y$05$.....................eDOx4wMcy7WU.kE21W6nJfdMimsBE3V6
$2y$05$.....................uMMcgjnOELIa6oydRivPkiMrBG8.aFp.
jette at nerdgirl dot dk
11年前
crypt()函数不能正确处理加号。例如,如果在登录函数中使用crypt,请首先对密码使用urlencode,以确保登录过程可以处理任何字符。

<?php
$user_input
= '12+#æ345';
$pass = urlencode($user_input));
$pass_crypt = crypt($pass);

if (
$pass_crypt == crypt($pass, $pass_crypt)) {
echo
"Success! Valid password";
} else {
echo
"Invalid password";
}
?>
To Top