php - PHP中如何使用bcrypt提供哈希密码?

  显示原文与译文双语对照的内容

我不时听到"使用bcrypt在PHP中存储密码,bcrypt规则"的建议。

但是什么是 bcryptPHP并不提供任何这样的功能,维基百科babbles关于file-encryption实用程序和站点搜索只是显示了一些不同语言的Blowfish的实现。 现在 Blowfish 也可以通过 mcrypt 使用,但这对存储密码有什么帮助? Blowfish 是一个通用的密码,它有两种方式。 如果它可以被加密,它可以被解密。 密码需要单向散列函数。

什么是解释?

时间:

bcrypt 是一个使用硬件( 通过可以配置的rounds数)的哈希算法。 它的缓慢度和多轮次确保了攻击者必须部署大量的资金和硬件才能破解你的密码。 添加到 per-password salt ( bcrypt 需要盐),你可以确信一个攻击实际上是不可行的,而且没有任何荒谬的资金或者硬件。

bcrypt 使用 Eksblowfish 算法来散列密码。 而加密阶段 Eksblowfish和 Blowfish 完全相同,关键计划阶段 Eksblowfish确保任何后续状态取决于盐和关键( 用户密码), 和任何国家可以预先计算的,没有的知识。 因为这个关键的区别, bcrypt 单向散列算法。 不能检索纯文本密码已经不知道盐、轮和关键 ( 密码) 。 [ yf_link_hrqsa2dsmvtd2itior2haorpf53xo5zoovzwk3tjpaxg64thf5sxmzloorzs65ltmvxgs6bzhexxa4tpozxxgl3qojxxm33tl5uhi3lmf5xg6zdfgqxgq5dnnqrd4888_yfr_link Source ]

如何使用 bcrypt:

使用 PHP> = 5.5 -DEV

密码哈希函数现在已经直接构建到 PHP> = 5.5 。 你现在可以使用 password_hash() 创建任何密码的bcrypt 散列:


<?php
//Usage 1:
echo password_hash("rasmuslerdorf", PASSWORD_DEFAULT)."n";
//$2y$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
//For example:
//$2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a

//Usage 2:
$options = array('cost' => 11);
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $options)."n";
//$2y$11$6DP.V0nO7YI3iSki4qog6OQI5eiO6Jnjsqg7vdnb.JgGIsxniOn4C

要验证用户提供的密码是否符合现有哈希,你可以使用 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> = 5.3.3 )

上有一个兼容库GitHub创建基于上述函数的源代码最初用c编写的,它提供了相同的功能。 安装兼容库之后,使用与上面的( - shorthand 数组表示法如果你仍在 5.3. x 分支) 相同。

使用 <5.3.7 ( 已经废弃)

你可以使用 crypt() 函数生成输入字符串的bcrypt散列。 此类可以自动生成salt并根据输入验证现有散列。 如果你正在使用一个版本的php高于或等于 5.3.7,强烈建议你使用内置函数或compat库。 这里选项仅用于历史目的。


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 (1);

 return $output;
 }
}

你可以使用如下代码:


$bcrypt = new Bcrypt(15);

$hash = $bcrypt->hash('password');
$isGood = $bcrypt->verify('password', $hash);

或者,你也可以使用可移植的PHP散列框架

那么,你想使用 bcrypt然而,像其他加密领域一样,你不应该自己做。 ! 如果你需要担心管理密钥,或者存储salt或者生成随机数,那么你就大错特错了。

原因很简单:screw bcrypt 。 事实上,如果你查看页面上几乎所有的代码,你会发现它至少违反了这些常见问题之一。

面对它,加密是困难的。

留给专家。 留给那些需要维护这些库的人。 如果你需要做出决定,你就会犯错。

相反,只需使用一个库。 根据你的需求,有几个存在。

程序库

下面是一些更常见的api的细目。

PHP 5.5 API - ( 适用于 5.3.7 + )

从 PHP 5.5开始,引入了一个新的哈希密码 API 。 还有一个 shim 兼容库维护( 由我) 5.3.7 +。 这是一个peer-reviewed和的优点,可以使用实现。


function register($username, $password) {
 $hash = password_hash($password, PASSWORD_BCRYPT);
 save($user, $hash);
}

function login($username, $password) {
 $hash = loadHashByUsername($username);
 if (password_verify($password, $hash)) {
//login
 } else {
//failure
 }
}

真的,它的目的是非常简单。

资源:

ZendCryptPasswordBcrypt ( 5.3 。2+ )

这是另一个类似于 PHP 5.5的API,并具有类似的用途。


function register($username, $password) {
 $bcrypt = new ZendCryptPasswordBcrypt();
 $hash = $bcrypt->create($password);
 save($user, $hash);
}

function login($username, $password) {
 $hash = loadHashByUsername($username);
 $bcrypt = new ZendCryptPasswordBcrypt();
 if ($bcrypt->verify($password, $hash)) {
//login
 } else {
//failure
 }
}

资源:

PasswordLib

这是一个稍微不同的密码散列方法。 而不是简单地支持 bcrypt PasswordLib支持大量的散列算法。 它主要在你需要支持与你的控制之外的遗留和异类系统兼容的环境中。 它支持大量哈希算法。 并支持 5.3.2 +


function register($username, $password) {
 $lib = new PasswordLibPasswordLib();
 $hash = $lib->createPasswordHash($password, '$2y$', array('cost' => 12));
 save($user, $hash);
}

function login($username, $password) {
 $hash = loadHashByUsername($username);
 $lib = new PasswordLibPasswordLib();
 if ($lib->verifyPasswordHash($password, $hash)) {
//login
 } else {
//failure
 }
}

参考:

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
 }
}

资源

注意:不要使用没有托管在 openwall PHPASS替代品,他们是不同的项目!

关于 BCrypt

如果你注意到,这些库中的每一个都返回一个字符串。 这是因为BCrypt如何内部工作。 还有很多关于这个问题的答案。 下面是我所写的选择,我不会在这里复制/粘贴,但链接到:

卷起

有许多不同的选择。 你所选择的取决于你自己。 然而,我将高度建议你使用一个以上的图书馆为你处理这个。

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

只需使用一个库。。

你会得到很多信息这里的或者在这里。

目标是散列密码的东西慢,所以有人得到你的密码数据库将试图 bruteforce ( 10延迟女士检查密码是什么,很多人试图bruteforce它) 死去。 Bcrypt很慢,可以用一个参数来选择它的速度。

你可以使用bcrypt函数的crypt() 创建一个单向散列,并传入一个适当的Blowfish salt 。 整个等式中最重要的是,该算法还没有被破坏,并且为每个密码分配了 不要使用 application-wide salt ;这将打开整个应用程序来攻击一组彩虹表。

PHP - Crypt函数


编辑:2013.01.15-如果你的服务器将会支持它,使用解决martinstoeckli代替。


每个人都想让这一切变得更加复杂。 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

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

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

使用这里功能的最简单方法是:


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

这里代码将用 BCrypt ( 算法 2y ) 散列密码,生成来自操作系统随机源的随机 salt,并使用默认成本参数( 现在是 10 ) 。 第二次行检查,如果用户输入的密码与已经存储的hash-value相匹配。

要更改成本参数,可以这样做,增加成本参数 1,加倍计算哈希值所需的时间:


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

"cost" 参数相比,最好忽略 "salt" 参数,因为函数已经尽力创建一个加密的安全 salt 。

对于 PHP 5.3.7和更高版本,存在一个兼容包,来自同一作者的password_hash() 函数。 对于 5.3.7之前的PHP版本,没有支持 crypt()2y,unicode安全BCrypt算法。 可以用 2a 代替它,它是早期PHP版本的最佳替代。

另一种是使用scrypt,专门设计由科林·珀西瓦尔优于 bcrypt 他的论文。 有一个 scrypt PECL php扩展。 理想情况下该算法将滚到php,以便它可以为password_*指定函数( 理想为"password_scrypt"), 但是,是不存在的。

当前的想法:哈希应该是最慢的,而不是最快的。 这将取消彩虹表攻击。

也相关,但防范:攻击者绝不可以无限访问你的登录屏幕。 为了防止这种情况发生:设置一个IP地址跟踪表,它记录每一个命中和 URI 。 如果超过 5分钟的尝试在任何五分钟内来自同一个IP地址,请使用解释。 第二种方法是使用two-tiered密码方案,比如银行。 在第二个关口上放置lock-out失败会增强安全性。

摘要:使用耗时的哈希函数来降低攻击者的速度。 此外,对登录的访问过多,并添加第二个密码层。

...