你如何使用bcrypt在PHP中哈希密码?

我偶尔会听到“使用bcrypt在PHP中存储密码,bcrypt规则”的build议。

但是什么是bcrypt ? PHP不提供任何这样的function,维基百科关于文件encryption实用程序的喋喋不休,Websearch只是揭示了几种不同语言的Blowfish实现。 现在Blowfish也可以通过mcrypt通过PHP获得,但是这对于存储密码有什么帮助? 河豚是一个通用的密码,它有两种方式。 如果可以encryption,则可以解密。 密码需要单向散列函数。

什么是解释?

bcrypt是一个哈希algorithm,可以通过硬件来扩展(通过可configuration的循环次数)。 其缓慢和多轮确保攻击者必须部署大量资金和硬件才能破解密码。 添加到每个密码盐 ( bcrypt需要盐),你可以肯定的是,一个攻击几乎不可行,如果没有可笑的资金或硬件。

bcrypt使用Eksblowfishalgorithm来散列密码。 虽然EksblowfishBlowfish的encryption阶段完全相同,但Eksblowfish的关键调度阶段确保任何后续状态都依赖于salt和key(用户密码),并且在没有两者的知识的情况下不能预先计算状态。 由于这个关键差异, bcrypt是一种单向哈希algorithm。 如果不知道盐,圆和密码 (密码),则无法检索纯文本密码。 [ 来源 ]

如何使用bcrypt:

使用PHP> = 5.5-DEV

密码散列函数现在已经直接编译进PHP> = 5.5 。 您现在可以使用password_hash()创build任何密码的bcrypt散列:

 <?php // Usage 1: echo password_hash('rasmuslerdorf', PASSWORD_DEFAULT)."\n"; // $2y$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx // For example: // $2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a // Usage 2: $options = [ 'cost' => 11 ]; echo password_hash('rasmuslerdorf', PASSWORD_BCRYPT, $options)."\n"; // $2y$11$6DP.V0nO7YI3iSki4qog6OQI5eiO6Jnjsqg7vdnb.JgGIsxniOn4C 

要根据现有的散列validation用户提供的密码,可以使用password_verify()

 <?php // See the password_hash() example to see where this came from. $hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq'; if (password_verify('rasmuslerdorf', $hash)) { echo 'Password is valid!'; } else { echo 'Invalid password.'; } 

使用PHP> = 5.3.7,<5.5-DEV(也是RedHat PHP> = 5.3.3)

在GitHub上有一个兼容库 ,这个库基于原来用C编写的上述函数的源代码,它提供了相同的function。 安装兼容性库后,用法与上述相同(如果仍在5.3.x分支上,则不要使用简写数组符号)。

使用PHP <5.3.7 (DEPRECATED)

您可以使用crypt()函数来生成inputstring的bcrypt散列。 这个类可以自动生成salt并validationinput的现有散列。 如果您使用的PHP版本高于或等于5.3.7,强烈build议您使用内置函数或compat库 。 这个替代scheme仅用于历史目的。

 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 。

那么,你想使用bcrypt? 真棒! 但是,像其他密码学领域一样,你不应该自己去做。 如果您需要担心pipe理密钥,存储盐份或生成随机数字等任何事情,那么您就错了。

原因很简单: 搞砸bcrypt非常容易。 事实上,如果你仔细看看这个页面上的每一段代码,你都会注意到它至less违反了这些常见问题之一。

面对它,密码学很难。

把它留给专家。 把它留给那些正在维护这些库的人。 如果你需要做出决定,你做错了。

相反,只要使用一个库。 有几个取决于您的要求。

图书馆

以下是一些更常见的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地穴

PasswordLib

这是一个稍微不同的密码哈希方法。 PasswordLib不是简单地支持bcrypt,而是支持大量的哈希algorithm。 它主要用于需要支持可能不受控制的传统和不同系统兼容的环境。 它支持大量的哈希algorithm。 并且支持5.3.2+

 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的层,但是也支持一个相当强大的algorithm,如果你没有访问PHP> = 5.3.2的话,这个algorithm是非常有用的。它实际上支持PHP 3.0+(尽pipe不是用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.0algorithm的回顾: 在StackOverflow上

注意:不要使用不在openwall上托pipe的PHPASS选项,它们是不同的项目!

关于BCrypt

如果您注意到,这些库中的每一个都会返回一个string。 这是因为BCrypt如何在内部工作。 关于这个的答案有很多。 这是我写的一个select,我不会在这里复制/粘贴,而是链接到:

  • 散列和encryptionalgorithm之间的根本区别 – 解释术语和它们的一些基本信息。
  • 关于没有彩虹表的逆转散列 – 基本上我们为什么要首先使用bcrypt …
  • 存储bcrypt哈希 – 基本上为什么包含在哈希结果中的salt和algorithm。
  • 如何更新bcrypt哈希的代价 – 基本上如何select,然后维护bcrypt哈希的代价。
  • 如何用bcrypt哈希长密码 – 解释bcrypt的72个字符的密码限制。
  • bcrypt如何使用盐
  • 腌制和密码密码的最佳做法 – 基本上不要使用“辣椒”
  • 将旧的md5密码迁移到bcrypt

包起来

有很多不同的select。 你select哪个取决于你。 不过,我会强烈build议您使用上述库中的一个来处理这个问题。

同样,如果你直接使用crypt() ,你可能做错了什么。 如果你的代码直接使用hash() (或者md5()或者sha1() ),那么你几乎肯定是在做一些错误的事情。

只要使用图书馆…

你会得到很多信息在足够的彩虹表:你需要知道关于安全密码scheme便携式PHP密码哈希框架

我们的目标是用缓慢的方式来散列密码,所以有人得到你的密码数据库将会试图蛮横的强制它(延迟10毫秒来检查密码对你来说没有任何意义,对于试图暴力破解密码的人来说很多)。 Bcrypt速度慢,可以使用一个参数来select它的速度。

您可以使用PHP的crypt()函数和传递适当的Blowfish盐来创build一个单向散列。 整个方程中最重要的是A)algorithm没有受到损害,B) 你正确地使用每个密码 。 不要使用整个应用程序的盐; 这将打开整个应用程序,从一组Rainbow表中进行攻击。

PHP – Crypt函数


编辑:2013.01.15 – 如果您的服务器将支持,请使用martinstoeckli的解决scheme 。


每个人都想把它比现在更复杂。 crypt()函数完成了大部分的工作。

 function blowfishCrypt($password,$cost) { $chars='./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; $salt=sprintf('$2y$%02d$',$cost); //For PHP < PHP 5.3.7 use this instead // $salt=sprintf('$2a$%02d$',$cost); //Create a 22 character salt -edit- 2013.01.15 - replaced rand with mt_rand mt_srand(); for($i=0;$i<22;$i++) $salt.=$chars[mt_rand(0,63)]; return crypt($password,$salt); } 

例:

 $hash=blowfishCrypt('password',10); //This creates the hash $hash=blowfishCrypt('password',12); //This creates a more secure hash if(crypt('password',$hash)==$hash){ /*ok*/ } //This checks a password 

我知道这应该是显而易见的,但请不要使用“密码”作为您的密码。

PHP版本5.5将内置支持BCrypt,函数password_hash()password_verify() 。 其实这些只是函数crypt()包装,并且使它更容易正确使用它。 它负责生成一个安全的随机盐,并提供良好的默认值。

使用这个函数最简单的方法是:

 $hashToStoreInDb = password_hash($password, PASSWORD_BCRYPT); $isPasswordCorrect = password_verify($password, $existingHashFromDb); 

这段代码将使用BCrypt(algorithm2y )对密码进行散列,从操作系统随机源生成一个随机盐,并使用默认的成本参数(此时为10)。 第二行检查用户input的密码是否与已存储的散列值匹配。

如果要更改成本参数,可以这样做,将成本参数增加1,计算散列值所需的时间加倍:

 $hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 11)); 

"cost"参数相反,最好省略"salt"参数,因为该function已经尽其所能地创build了一个密码安全的盐。

对于5.3.7及更高版本的PHP,存在一个兼容包 ,来自做password_hash()函数的同一作者。 对于5.3.7之前的PHP版本,不支持2y crypt() ,Unicode安全的BCryptalgorithm。 可以用2a代替它,这是早期PHP版本的最佳select。

另一种方法是使用scrypt,专门devise为比Colin Percival在他的论文中更优于bcrypt。 在PECL中有一个scrypt PHP扩展 。 理想的情况下,这个algorithm将被放到PHP中,以便它可以被指定为password_ *函数(理想的是“PASSWORD_SCRYPT”),但那还没有。

目前的想法是:哈希应该是最慢的,而不是最快的。 这抑制了彩虹表攻击。

也有相关的,但预防:攻击者不应该有无限的访问您的login屏幕。 为了防止这种情况:设置一个IP地址跟踪表,logging每个命中与URI。 如果超过5次尝试login来自同一个IP地址在任何五分钟的时间,阻止和解释。 第二种方法是像银行那样有一个双层的密码scheme。 在第二轮失败的情况下进行locking可以提高安全性。

总结:通过使用耗时的哈希函数减慢攻击者的速度。 另外,阻止对login访问过多,并添加第二个密码层。

对于OAuth 2密码:

 $bcrypt = new \Zend\Crypt\Password\Bcrypt; $bcrypt->create("youpasswordhere", 10)