如何在PHP中使用bcrypt来哈希密码?[重复]
如何在PHP中使用bcrypt来哈希密码?[重复]
这个问题已经有了答案:
每隔一段时间,我都会听到“在PHP中使用bcrypt来存储密码,bcrypt很厉害”的建议。
但是什么是bcrypt
? PHP没有提供任何这样的功能,维基百科胡言乱语的谈论了一个文件加密工具,网络搜索只显示不同语言中Blowfish的几个实现。 现在通过mcrypt
在PHP中也可以使用Blowfish,但这有助于存储密码吗? Blowfish是一种通用密码,它可以双向工作。如果可以加密,那么它也可以被解密。密码需要单向散列函数。
这是什么解释呢?
所以,你想使用bcrypt?太棒了!然而,就像密码学的其他领域一样,你不应该自己做。如果你需要担心像管理密钥、存储盐或生成随机数这样的事情,你就错了。
原因很简单:处理bcrypt实在是太容易出错了。事实上,如果你看一看这个页面上的几乎每一份代码,你都会注意到它在至少一个常见的问题上存在问题。
承认吧,密码学很难。
把它留给专家吧。把它留给那些维护这些库的人。如果你需要做出决定,你就错了。
相反,只需使用一个库。根据你的要求,有几种选择。
库
这是一些更常见的API的详细介绍。
PHP 5.5 API - (可用于5.3.7+)
从PHP 5.5开始,引入了一个新的API来哈希密码。还维护有一个5.3.7+的兼容性库(由我维护)。它的好处是它是一个经过同行评审的,易于使用的实现。
function register($username, $password) { $hash = password_hash($password, PASSWORD_BCRYPT); save($username, $hash); } function login($username, $password) { $hash = loadHashByUsername($username); if (password_verify($password, $hash)) { //login } else { // failure } }
实际上,它的目标是非常简单的。
资源:
- 文档:在PHP.net上
- 兼容性库:在GitHub上
- PHP的RFC:在wiki.php.net上
Zend\Crypt\Password\Bcrypt (5.3.2+)
这是另一个类似于PHP 5.5的API,具有类似的目的。
function register($username, $password) { $bcrypt = new Zend\Crypt\Password\Bcrypt(); $hash = $bcrypt->create($password); save($user, $hash); } function login($username, $password) { $hash = loadHashByUsername($username); $bcrypt = new Zend\Crypt\Password\Bcrypt(); if ($bcrypt->verify($password, $hash)) { //login } else { // failure } }
资源:
- 文档: Zend
- 博客文章: 使用Zend Crypt进行密码哈希
PasswordLib
这是一种略微不同的密码哈希方法。它不仅支持bcrypt,还支持大量哈希算法。主要用于需要支持与遗留系统兼容的上下文。它支持大量哈希算法。且支持5.3.2以上版本的PHP。
function register($username, $password) { $lib = new PasswordLib\PasswordLib(); $hash = $lib->createPasswordHash($password, '$2y$', array('cost' => 12)); save($user, $hash); } function login($username, $password) { $hash = loadHashByUsername($username); $lib = new PasswordLib\PasswordLib(); if ($lib->verifyPasswordHash($password, $hash)) { //login } else { // failure } }
参考:
- 源代码/文档: GitHub
PHPASS
这是一种支持bcrypt且较强的算法的层,如果您无法访问 PHP>=5.3.2,它也会很有用。它可以支持 PHP 3.0+(但不支持bcrypt)。
function register($username, $password) { $phpass = new PasswordHash(12, false); $hash = $phpass->HashPassword($password); save($user, $hash); } function login($username, $password) { $hash = loadHashByUsername($username); $phpass = new PasswordHash(12, false); if ($phpass->CheckPassword($password, $hash)) { //login } else { // failure } }
资源
- 代码: cvsweb
- 项目网站: OpenWall上的网站
- 对 < 5.3.0 算法的评测: 在StackOverflow上
注意:不要使用未托管在 openwall 上的 PHPASS 备选项,它们是不同的项目!!!
关于 BCrypt
你会发现,这些库每一个都返回单个字符串,这是因为 BCrypt 的内部工作原理。关于这个,有很多回答。这里有一些我写的选择,我不会复制/粘贴在这里,而是链接到下面:
- 哈希和加密算法的根本区别 - 解释术语和一些基本信息。
- 关于不使用彩虹表反向哈希 - 基本上说明我们为什么首先要使用 bcrypt...
- 存储 bcrypt 哈希 - 基本上说明了为什么盐和算法包含在哈希结果中。
- 如何更新 bcrypt 哈希的成本 - 基本上是如何选择然后维护 bcrypt 哈希成本。
- 如何使用 bcrypt 哈希长密码 - 解释了 bcrypt 的 72 字符密码限制。
- bcrypt 如何使用盐
- 加盐和加胡椒粉的最佳做法 - 基本上,不要使用“胡椒粉”
- 将旧的
md5
密码迁移到 bcrypt
总结
有很多不同的选择。您选择哪个取决于您。但是,我强烈建议您使用上述其中一个库来处理这些问题。
再次强调,如果你直接使用crypt()
,你可能正在做一些错误的操作。如果你的代码直接使用hash()
(或md5()
或sha1()
),你几乎肯定在做错误的事情。
只需使用库即可...
bcrypt
是一种散列算法,可以通过硬件进行扩展(通过可配置的轮数)。它的缓慢性和多轮确保攻击者必须投入大量资金和硬件才能破解您的密码。此外,使用每个密码的盐(bcrypt需要盐),您可以确信未经荒谬的资金或硬件,攻击几乎不可能发生。
bcrypt
使用Eksblowfish算法对密码进行散列。虽然Eksblowfish和Blowfish的加密阶段完全相同,但Eksblowfish的密钥调度阶段确保任何后续状态都取决于盐和密钥(用户密码),并且没有状态可以在不知道这两者的情况下预先计算。因为这个关键的区别,bcrypt
是一种单向散列算法。您不能在不知道盐、轮数和密钥(密码)的情况下检索纯文本密码。[来源]
如何使用bcrypt:
使用PHP >= 5.5-DEV
现在,密码散列函数已直接集成到PHP >= 5.5中。您现在可以使用password_hash()
创建任何密码的bcrypt
哈希:http://php.net/password_hash
11 ]; echo password_hash('rasmuslerdorf', PASSWORD_BCRYPT, $options)."\n"; // $2y$11$6DP.V0nO7YI3iSki4qog6OQI5eiO6Jnjsqg7vdnb.JgGIsxniOn4C
要验证用户提供的密码是否与现有哈希值匹配,可以使用password_verify()
进行如下操作:
使用 PHP >= 5.3.7,< 5.5-DEV(也适用于 RedHat PHP >= 5.3.3)
在 GitHub 上有一个基于以上函数的原始 C 代码创建的 兼容性库,提供相同的功能。安装兼容性库后,使用与上面相同(如果仍然使用 5.3.x 分支,则不包括简写数组符号)。
使用 PHP < 5.3.7(已弃用)
您可以使用
crypt()
函数生成输入字符串的 bcrypt 哈希。此类可以自动生成盐并验证现有哈希是否与输入匹配。 如果您使用的是高于或等于 5.3.7 版本的 PHP,则强烈建议使用内置函数或兼容库。此选项仅供历史记录。class Bcrypt{ private $rounds; public function __construct($rounds = 12) { if (CRYPT_BLOWFISH != 1) { throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt"); } $this->rounds = $rounds; } public function hash($input){ $hash = crypt($input, $this->getSalt()); if (strlen($hash) > 13) return $hash; return false; } public function verify($input, $existingHash){ $hash = crypt($input, $existingHash); return $hash === $existingHash; } private function getSalt(){ $salt = sprintf('$2a$%02d$', $this->rounds); $bytes = $this->getRandomBytes(16); $salt .= $this->encodeBytes($bytes); return $salt; } private $randomState; private function getRandomBytes($count){ $bytes = ''; if (function_exists('openssl_random_pseudo_bytes') && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL is slow on Windows $bytes = openssl_random_pseudo_bytes($count); } if ($bytes === '' && is_readable('/dev/urandom') && ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) { $bytes = fread($hRand, $count); fclose($hRand); } if (strlen($bytes) < $count) { $bytes = ''; if ($this->randomState === null) { $this->randomState = microtime(); if (function_exists('getmypid')) { $this->randomState .= getmypid(); } } for ($i = 0; $i < $count; $i += 16) { $this->randomState = md5(microtime() . $this->randomState); if (PHP_VERSION >= '5') { $bytes .= md5($this->randomState, true); } else { $bytes .= pack('H*', md5($this->randomState)); } } $bytes = substr($bytes, 0, $count); } return $bytes; } private function encodeBytes($input){ // The following is code from the PHP Password Hashing Framework $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; $output = ''; $i = 0; do { $c1 = ord($input[$i++]); $output .= $itoa64[$c1 >> 2]; $c1 = ($c1 & 0x03) << 4; if ($i >= 16) { $output .= $itoa64[$c1]; break; } $c2 = ord($input[$i++]); $c1 |= $c2 >> 4; $output .= $itoa64[$c1]; $c1 = ($c2 & 0x0f) << 2; $c2 = ord($input[$i++]); $c1 |= $c2 >> 6; $output .= $itoa64[$c1]; $output .= $itoa64[$c2 & 0x3f]; } while (true); return $output; } }
您可以像这样使用此代码:
$bcrypt = new Bcrypt(15); $hash = $bcrypt->hash('password'); $isGood = $bcrypt->verify('password', $hash);
另外,您也可以使用 Portable PHP Hashing Framework。