我不时听到"使用bcrypt将密码存储在php中,使用bcrypt规则"的建议。
但什么是bcrypt?php没有提供任何这样的功能,维基百科喋喋不休地谈论一个文件加密实用程序,而网络搜索只显示了几种不同语言的Blowfish实现。现在,Blowfish也可以通过mcrypt在PHP中使用,但这对存储密码有何帮助?河豚是一种通用密码,它有两种工作方式。如果可以加密,就可以解密。密码需要一个单向散列函数。
解释是什么?
- 这个问题已经在前面讨论过,他们使用标准库的建议非常好。安全性是一个复杂的问题,通过使用一个由知道他们在做什么的人设计的包,你只是在帮助自己。
- @这个页面甚至没有提到bcrypt,更不用说解释它是什么。
- 那是真的。但是,我根据问题的标题包含了这个链接,问题的答案是。关于BCRYPT的实际细节,您可以查看这篇期刊论文。但是,要知道你的问题非常广泛;你要求对整个研究领域做一个简要的解释(我很容易承认,我对自己并不熟悉)。
- @我不需要解释它是如何工作的。我只想知道它是什么。因为我在网络上搜索到的关键词"bcrypt"下的任何东西都不能用来散列密码。无论如何都不是直接的,也不是在PHP中。好吧,现在我明白了,它实际上是一个"phpass"包,使用Blowfish用一个从您的密码派生的密钥加密您的密码(本质上是用密码本身加密)。但将其称为"bcrypt"是严重误导,这是我想在这个问题中澄清的。
- @Vilx:我已经添加了更多的信息来解释为什么bcrypt是单向散列算法,而不是我的答案中的加密方案。有一种完全错误的观念认为,bcrypt只是吹牛,实际上它有一个完全不同的密钥调度,确保在不知道密码的初始状态(salt、rounds、key)的情况下无法从密码文本中恢复纯文本。
- 如何使用php 5.5密码散列函数
- 另请参见OpenWall的便携式PHP密码哈希框架(phpass)。它可以抵御许多常见的用户密码攻击。
bcrypt是一种散列算法,可通过硬件(通过可配置的轮数)进行扩展。它的缓慢和多轮确保攻击者必须部署大量资金和硬件才能破解您的密码。再加上每个密码的盐类(bcrypt需要盐类),您可以确定,如果没有足够的资金或硬件,攻击实际上是不可行的。
bcrypt使用eksblowfish算法散列密码。虽然Eksblowfish和Blowfish的加密阶段完全相同,但Eksblowfish的密钥调度阶段确保任何后续状态都依赖于salt和密钥(用户密码),并且在不了解两者的情况下,无法预计算任何状态。由于这一关键区别,bcrypt是一种单向散列算法。如果不知道salt、rounds和key(密码),则无法检索纯文本密码。[来源]
如何使用bcrypt:使用php>=5.5-dev
密码散列函数现在已经直接内置到php>=5.5中。您现在可以使用password_hash()创建任何密码的bcrypt哈希:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php
// Usage 1:
echo password_hash('rasmuslerdorf', PASSWORD_DEFAULT)."
";
// $2y$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// For example:
// $2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a
// Usage 2:
$options = [
'cost' => 11
];
echo password_hash('rasmuslerdorf', PASSWORD_BCRYPT, $options)."
";
// $2y$11$6DP.V0nO7YI3iSki4qog6OQI5eiO6Jnjsqg7vdnb.JgGIsxniOn4C |
要根据现有哈希验证用户提供的密码,可以使用password_verify(),如下所示:
1 2 3 4 5 6 7 8 9
| <?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编写的上述函数的源代码创建的,它提供了相同的功能。一旦安装了兼容性库,用法与上面的用法相同(如果您仍然在5.3.x分支上,则减去简短的数组符号)。
使用php<5.3.7(已弃用)
可以使用crypt()函数生成输入字符串的bcrypt散列。此类可以自动生成salts并根据输入验证现有哈希。如果您使用的PHP版本高于或等于5.3.7,强烈建议您使用内置函数或compat库。该替代方案仅用于历史目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| 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;
}
} |
您可以这样使用此代码:
1 2 3 4
| $bcrypt = new Bcrypt (15);
$hash = $bcrypt->hash('password');
$isGood = $bcrypt->verify('password', $hash); |
或者,您也可以使用可移植的PHP哈希框架。
- 当第三个选项用于随机字节生成时,您的实现会显示为segfault。我研究了php.net/crypt,生成随机字节的最佳选项之一非常简单。看看我的重写:gist.github.com/1070401
- @邪恶的跳蚤:mt_rand()不能在安全敏感的环境中使用。此外,此代码已在多个环境中使用,没有任何问题,并且不应使用php脚本segfault。您可能需要研究在您的环境中重新编译PHP。
- 在我的环境中,它似乎是对base64编码的segfault。我认为mt_rand更安全,因为随机种子不是microtime(),这太容易预测了。我认为MD5散列是不安全的,因为它只有一轮;只有Bcrypt本身使它安全(我们希望如此)。
- @邪恶的跳蚤:很抱歉让你失望,但是mt_rand()也使用当前时间和当前进程ID来播种。请参阅/ext/standard/php_rand.h中的GENERATE_SEED()。
- 好吧,那它们就跟彼此一样安全了。
- @邪恶的跳蚤:几乎不可能预测精确的毫秒和生成过程的过程ID。
- 呃,什么?我是说,任何人都可以编辑和更改上面的代码,所以它不是一个好地方。
- @tchalvak:这就是堆栈溢出具有编辑历史记录的原因。你可以看到谁编辑了什么。
- @安德鲁,你介意我在写的程序中使用这个代码吗?
- @迈克:说吧,就是因为这个原因!
- 对于任何认为需要在getsalt函数中修改$salt字符串开头的人来说,这是不必要的。2a美元是地穴河豚盐的一部分。来自文档:"河豚用盐散列,如下所示:"$2a$",两位数的成本参数,"$",以及字母表中的22位数。
- 事实上,唯一一个可以用硬件扩展的算法是不太正确的。即使从PHP的crypt函数的描述来看,CRYPT_SHA265和CRYPT_SHA512也有可配置的轮数。不过,河豚有一些优势(因为在GPU上不容易并行化,因为需要4kb的RAM)。稍后的开发是scrypt,它还需要可配置的(大量)内存来运行,这使得暴力强制的成本更高。
- @ Pa?Lo Ebermann:SHA256和SHA512本身没有。crypt()采用关键加强来达到同样的效果。你不能只吃一轮bcrypt。关键强化是Eksblowfish算法的一个组成部分。
- @andrewmoore:根据crypt()的php手册,CRYPT_BLOWFISH盐的成本为2位数,"两位数的成本参数是底层的基于blowfish的哈希算法的迭代计数的底2对数,必须在04-31范围内,超出此范围的值将导致crypt()失败。"但是,在Bcrypt::getSalt()中,您直接将$this->rounds插入钥匙。我认为这应该是log($this->rounds, 2),不是吗?
- 我也同意@tchalvak的观点,如果你想让人们使用它,你应该把它变成一个开放源码库,作为常规的工具(VCS存储库、bug跟踪器、项目页面)。然后,人们会更倾向于讨论和分析它的漏洞(并可能提交补丁来修复漏洞)。
- 除了连接到散列之外,您似乎没有在任何地方实际使用rounds。没有基于它的循环。
- @Matthewflaschen:crypt()执行字符串散列的实际工作。它使用所提供的盐(带有$2a$rounds$标识符)来确定它必须执行的轮次数量。
- @安德鲁莫尔,谢谢。但看起来,Drrcknlsn应该做一个log是对的。
- @马修夫拉辛:再说一遍,不,埃多克斯1〔7〕负责。bcrypt算法的实现全部由crypt()处理,而不是由上述代码处理。我的代码只以crypt()期望的格式生成适当的salt,以便检查/散列字符串。
- @Matthewflaschen:您提供的数字(作为开发人员)已经是以2为底的对数。试试你的代码,它会坏的。
- @anderwmoore,所以您希望$rounds构造函数参数是以2为底的对数?没关系,只是名字有误导性。你应该更清楚地说,这是整数的对数。因此,我建议调用构造函数参数$log_2_rounds。
- 注意:这只在您有php 5.3+的情况下才有效!我有php 5.2.17,并得到Error 500,因为如果CRYPT_BLOWFISH不可用,就会抛出异常。
- @Ozzy看了openwall.com/phpass,它有一些3<=php<5.3的回退散列方案。
- 疯狂的itoa64和使用base64_encode有什么意义吗?字符数是相同的,但值是完全不同的。前者在某种程度上更安全吗?
- 那么,嗯,我们把盐放在哪里?
- @JeffDavis,salt已经是生成散列的一个组成部分了。
- @Ozzy您也可以安装suhosin补丁,让crypt_Blowfish在php 5.2上可用。
- @等等,crypt($input, $this->getSalt())的结果和crypt($input, $existingHash)的结果一样,$existingHash是我们从第一件事得到的结果吗?这对我来说毫无意义
- @andylobel:盐存在于完整哈希中,因此可以使用原始的盐重新计算哈希,以给出相同的结果。$2a$$
- @所以在$existingHash中,crypt函数只使用$2a$$?实际上不需要部分吗?感谢您的回复;-)
- @andrewmoore如果我有一个电子邮件/密码登录,我可能会先检查电子邮件是否匹配,然后从mysql行获取相关的密码,然后检查验证过程,对吗?
- @anderwliu即使使用同一个词也会改变,因为每次调用hash()函数时都会生成一个新的随机salt,使得每次的总体结果都不同
- @Andylobel我知道,我是这么说的,我想我拼错了hash。我在问,在MySQL数据库中检查它的最佳方法是什么。
- @anderwliu:调用$bcrypt->verify()函数,纯文本作为第一个参数,哈希存储在数据库中作为第二个参数。
- @Andrewliu我按照你说的方法做,检查电子邮件并同时从数据库中获取哈希,然后根据用户登录时输入的密码哈希检查哈希。
- @andrewmoore您能否评论一下这两者之间的区别:yiiframework.com/wiki/292/secure password hashing with bcryp&zwnj;&8203;t和您上面的密码hashing,并说哪一个更好,谢谢;-)
- @andylobel:起重由crypt()完成。没有比这更好的了。
- @很抱歉,我喜欢你先核实一下的想法。但如果是真的,验证输出1?它有两个参数?我对你的方法有点困惑。还是我有不同的想法?感谢您的回复!
- @另外,我还尝试了我的方法,检查电子邮件,然后从数据库中获取哈希密码来检查使用verify(),似乎仍然不匹配。=请帮帮我
- @Andrewliu:$bcrypt->verify($passwordAsProvidedByTheUser, $hashInTheDB);返回true为有效。
- @andrewmoore在我所说的链接中生成bytes的方法与上面的方法不同;哪种方法更好?抱歉,哈哈,这是我最后一个问题
- @很好,谢谢,只要我的php版本大于等于5.3.0,而且我不在Windows服务器上,我就可以摆脱生成字节的其他回退。
- @安德鲁莫尔,是的,这正是我想要检查验证的方式。我甚至试过模仿verify(),但结果不是真的或1。我对用户输入进行了散列,然后用用户电子邮件检查数据库,从注册中获取"散列"密码,并通过验证进行了检查。
- @andrewliu确保数据库中的字段足够长,可以保存密码哈希varchar(60),我犯了一段时间的错误lol,并检查其true是否使用var_dump($bcrypt->verify('password', $hash))。
- -1因为你的备份RNG根本不是随机的(只基于系统时间是不好的随机性)。将其更改为至少使用经过审核的prng算法,如mt。
- @ircmaxell:mt_rand()的播种方式与5.3完全相同(getmypid()可用于所有系统…你应该知道,你毕竟是一个ZCE)。可以说,这里加密安全的salt的好处非常小;在哈希中,salt不被认为是秘密。只需强制攻击者为每个密码生成不同的彩虹表(而不是对所有密码都使用相同的表)。能够猜测下一个salt不会给攻击者带来任何好处(它是哈希中的纯文本)。
- @起初我认为这也是问题所在,但我将数据库字段设置为text。当我有机会的时候我会尝试使用var_dump
- @Andylobel Yup,bool(false)。
- @Andrewmoore嗨,我已经就我的问题提出了一个问题,我想知道你是否能发现我遗漏的东西?我真的很绝望,这是我登录页面上唯一需要做的事情(stackoverflow.com/questions/11481199/&hellip;)非常感谢!
- @andrewliu你的代码中的syntax没有任何问题,它不起作用的唯一原因是$pass_l或$chk_pass不是他们所认为的那样;-)所以我一直在评论针对另一个人loool的东西,如果你想的话,我可以和你合作帮助你;p
- @Andylobel抱歉,我忘了添加一个变量$hash_1,它是$pass_1的散列值,然后它应该与andrew moore在上述评论中所说的verify($hash_1, $chk_pass)进行检查。
- @提姆彼得森:你用了多少发子弹?更多回合=更多复杂性=更多时间…
- @andrewmoore我确实复制并粘贴了您的代码到test.php文件中。所以我猜是$rounds=12?然而,当我把它减到$rounds=1时,它仍然需要大约10秒。我还需要做些什么来让时间小于1秒吗?对于消费类Web应用程序,10秒的时间太长。
我是唯一一个没有看到hash函数中使用的舍入值的人吗?忽略它-它被指定为哈希的一部分。
- @很抱歉,我现在看到你的代码中指定的$rounds=15在哪里:$bcrypt = new Bcrypt(15);。如果您将其更改为$bcrypt = new Bcrypt(4);,那么它几乎立即返回真值。
- @蒂姆彼得森:你应该选择一些能产生200-250毫秒工作时间的轮数。bcrypt之所以安全,部分原因是它速度慢。你必须确保有许多回合保持这一特性。
- @非常感谢,从Bcrypt(4)到Bcrypt(9)运行microtime(),时间从0.010到0.314。所以我可能会这么做。
- 哦,我的上帝。不要使用没有上传到某个地方的加密代码,这些加密代码要与你可以识别为加密真正权威的人联系在一起,经过他们的批准和同行审查。这不是关于开放源代码和封闭源代码。无论上传到何处,都应提供审查和验证来源的可视性。这是承认我们中的大多数人没有批判密码的能力,也不让盲人领导盲人。我真的应该依靠维基上匿名的投票来告诉我是否在损害我客户的数据?因为这是所有非加密专家都能做到的。
- @迈克尔:好东西crypt()是同行评审和验证的。上面的代码调用php的crypt(),后者调用posix crypt()函数。上面所有的代码更多的是在调用crypt()之前生成一个随机的salt(它不需要加密安全,salt不被认为是秘密)。也许在打电话给狼之前,你应该自己做点调查。
- @很抱歉。我一直在做研究。我刚刚犯了一个错误,把您的salt生成代码误认为是哈希创建代码。也许你能摆脱盐业?当我想象人们养成了复制/粘贴一大堆加密代码的习惯时,我真的很不安。你需要用一只专注的眼睛来区分你所做的和引起这场争论的事情:news.ycombinator.com/item?ID=2654586
- @Andrew很抱歉,我也会编辑评论,但是链接不见了,所以我只能删除(我讨厌这样做,因为这样做会使回复变得毫无意义)。但是如果你想删掉我会删掉的
- 请注意,这个答案虽然不错,但开始显示它的年龄。此代码(与依赖crypt()的任何PHP实现一样)受到5.3.7之前版本的安全漏洞的影响,并且在5.3.7之后(非常轻微)效率低下-有关问题的详细信息可在此处找到。请注意,新的密码散列API(向后兼容lib)现在是在应用程序中实现bcrypt密码散列的首选方法。
- 嗨,Andrew,我刚刚成功地将它作为短期解决方案应用到我的CodeIgniter中,直到5.5完全稳定,但是在此之前我使用了密码compat,数据库中首选的varchar长度为75…密码长度是多少?-还是60岁吗?
- @安德鲁莫尔,如果我在回合中低于4,为什么散列是空的?我现在会注意到这一点,因为它不会在我的php5.3.14 mac安装上生成哈希。但这是什么原因呢?
- @spankmaster79:"两位数的成本参数是底层基于Blowfish的哈希算法的迭代计数的底2对数,必须在04-31范围内,超出此范围的值将导致crypt()失败。"
- @Andrew很好,我已经调整了代码,如果代码低了就把它设置为4。
- 人们问盐为什么以$2a开头,为什么哈希本身包含盐,为什么这个crypt($input,$this->getsalt())和crypt($input,$existinghash)是一样的,应该看看crypt函数的起源:en.wikipedia.org/wiki/crypt_
- 是否只有我一个人,或者可以用getSalt内的$salt .= str_replace('+', '.', base64_encode($bytes))替代encodeBytes功能?
- "//openssl在Windows上很慢"不要这样做。另外,salt的生成非常糟糕(它是一个22个字符的base-64字符串,而不是22个随机字符)。
- 从7.0 php开始,salt已经贬值!看到这里
- 老实说,我仍然在问自己,为什么php.net上的脚本/文档编写人员首先会将."
"包含在密码hash()中。这个问题一直没有得到回答,人们仍在使用/添加."
"到他们的代码中,并在使用password_verify()时默默地失败。需要更新此答案,以反映这一点,并引用这一重要贡献说明php.net/manual/en/function.password hash.php 121017,并将其归功于jay blanchard。
- @Fred II——很难相信有能力的程序员不会意识到换行只是为了echo的利益,它不是哈希的一部分。
- @巴玛,你知道,我知道,还有许多其他"有能力的程序员"知道。但是那些不知道的呢?你需要站在盒子外面思考,把自己放在盒子里,穿上鞋子走一会儿;-)
- 在我看来,你只能期待这么多防御性的文件。我想他们需要找到每一个与新行呼应的例子并加以修正。
- @Barmar就是这样;新手几乎不会阅读整个文档和/或深入查看用户提供的解释换行符问题的注释。我会告诉你什么;我会编一个90年代在3dstudio中创建的动画,还有一些我重新创建的图形,还有一些我从位图转换成矢量并清理我为使绘图仪明白而设计的徽标,然后我只会给你一个它的图像表示,看看你能不能弄清楚我是怎么做到的。那些(X年前的);-)我希望我能在这里得到一个信息。
- 为什么在$2y$11$6DP.V0nO7YI3iSki4qog6OQI5eiO6Jnjsqg7vdnb.JgGIsxniOn4C和$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq&zwnj;&8203;中给出了完全不同的格式?这只会混淆谁在学习这些东西。
那么,你想用bcrypt吗?令人惊叹的!然而,像其他领域的密码学一样,你不应该自己去做。如果您需要担心管理密钥、存储盐或生成随机数之类的问题,那么您就错了。
原因很简单:把Bcrypt搞砸太容易了。事实上,如果您查看这个页面上的几乎每一段代码,您会发现它至少违反了这些常见问题中的一个。
面对现实,密码学很难。
留给专家们。把它留给负责维护这些图书馆的人。如果你需要做一个决定,你就错了。
相反,只要使用一个图书馆。根据您的需求,有几个存在。
图书馆
下面是一些更常见的API的细分。
php 5.5 api-(可用于5.3.7+)
从php 5.5开始,引入了一个新的密码散列API。还有一个填充程序兼容性库(由我维护)用于5.3.7+。这有一个好处,就是被同行评审,并且实现简单易用。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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上
zendcryptpasswordcrypt(5.3.2+)
这是另一个类似于PHP5.5One的API,具有类似的用途。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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+
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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
}
} |
参考文献:
磷灰石
这是一个支持bcrypt的层,但也支持一个相当强大的算法,如果您没有访问php>=5.3.2…它实际上支持php3.0+(尽管不支持bcrypt)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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
- 工程地点:明墙
- <5.3.0算法综述:关于stackoverflow
注意:不要使用不在OpenWall上托管的phpass替代方案,它们是不同的项目!!!!
关于BCrypt
如果您注意到,这些库中的每一个都返回一个字符串。这是因为Bcrypt是如何在内部工作的。关于这个问题有很多答案。以下是我编写的选择,我不会在此处复制/粘贴,而是链接到:
- 散列算法和加密算法之间的根本区别-解释术语和有关它们的一些基本信息。
- 关于在没有彩虹表的情况下反转哈希-基本上为什么我们首先应该使用bcrypt…
- 存储bcrypt散列-基本上为什么散列结果中包含salt和算法。
- 如何更新bcrypt散列的成本——基本上是如何选择并维护bcrypt散列的成本。
- 如何使用bcrypt散列长密码-解释bcrypt的72字符密码限制。
- Bcrypt如何使用盐
- 盐渍和胡椒密码的最佳实践-基本上,不要使用"胡椒"。
- 将旧的md5密码迁移到bcrypt
包扎
有许多不同的选择。你选择哪一个取决于你自己。但是,我强烈建议您使用上述库中的一个来为您处理这个问题。
同样,如果您直接使用crypt(),则可能是做错了什么。如果您的代码直接使用hash()或md5()或sha1(),那么您几乎肯定在做错误的事情。
只要用图书馆…
- 盐必须是随机生成的,但是它不需要来自安全的随机源。盐不是秘密。能够猜测下一个salt并没有真正的安全影响;只要它们来自足够大的数据池,能够为每个编码的密码生成不同的salt,就可以了。记住,盐是用来防止使用彩虹表,如果你的大麻进入坏人手中。他们不是秘密。
- @安德烈完全正确!然而,盐必须有足够的熵才能在统计上独一无二。不仅在应用程序中,而且在所有应用程序中。因此,mt_rand()具有足够高的周期,但种子值仅为32位。因此,使用mt_rand()可以有效地将熵限制为32位。由于生日问题,这意味着你有50%的机会碰撞,只有7K生成的盐(全球)。由于bcrypt接受128位的盐,最好使用能够提供全部128位;-)的源。(128位时,在2E19散列时发生碰撞的概率为50%……
- @ircmaxell:包含"足够大的数据池"。然而,您的源不必是一个非常高的熵源,只需足够高的128位。但是,如果您已经耗尽了所有可用的资源(没有openssl等),并且您唯一的回退是mt_rand(),那么它仍然比替代方法(即rand())更好。
- @安德鲁:当然。没有争论过。只是mt_rand和uniqid不是第一选择…
- ircmaxell,非常感谢您为5.3.xx提供的密码兼容库,我们以前不需要这个,但现在我们需要,在5.3.xx PHP服务器上,感谢您明确建议不要尝试自己做这个逻辑。
- 就我个人而言,我觉得这么多人对如何正确/安全地使用密码技术做许多事情几乎一无所知的原因之一是,他们总是被告知不要自己尝试去做。当然,密码学很难得到正确的答案,但这正是人们练习密码的更多原因,提出更多关于密码的问题,并学习如何得到正确的答案。只是我的两分钱…:)
使用彩虹表,您将获得足够多的信息:关于安全密码方案或可移植的PHP密码散列框架,您需要了解什么。
其目标是用一些慢的东西散列密码,这样,获得密码数据库的人会因为试图强行执行密码而死掉(检查密码的10毫秒延迟对您来说毫无意义,对于试图强行执行密码的人来说是很大的延迟)。bcrypt很慢,可以与参数一起使用来选择它的速度。
- @阿赫:放慢速度并不是问题。只有当您作为开发人员执行强密码策略失败时,才会使用暴力变形。
- 强制执行您想要的任何东西,用户将设法搞砸并在多个东西上使用相同的密码。因此,您必须尽可能地保护它,或者实现一些不需要存储任何密码(SSO、OpenID等)的功能。
- 您可以通过阻止"用户"在一天内N次尝试次数超过合理次数(如50次)而无法猜到他们的密码,从而更容易地防止野蛮密码攻击。
- @Arkh,你是说,通过使用相对较慢的哈希算法(总体来说仍然相当快),我可以阻止黑帽子访问他已经熟悉密码的用户帐户,因为这也是用户的myspace密码,用户昨天被钓鱼了?
- 不。密码散列是用来防止一次攻击:有人偷了你的数据库,想得到明文登录+密码。
- @阿克:那是什么阻止他们使用比你更快的机器呢?或者通过计算机网络分配负载?云是便宜的,处理是便宜的,因此不要浪费它,希望它会减慢某人。
- @我鼓励你在通过phpass调优后尝试破解一些简单的密码,这样在你的网络服务器上计算密码需要1毫秒到10毫秒。
- @阿克什:埃多克斯1〔0〕还是埃多克斯1〔0〕。
- 同意。但是,将QWERTY用作密码的用户类型,也是将在他(和攻击者)可以轻松读取的地方标记任何复杂密码的用户类型。使用bcrypt的目的是,当数据库违背您的意愿公开时,与一次性使用sha512相比,向那些拥有某些密码(如^$$&zl6-&163;)的用户发送密码会更加困难。
- @coreyward值得注意的是,这样做比完全不阻塞更有害;这很容易被视为"拒绝服务"的载体。只要开始向任何已知帐户发送错误登录信息,就可以非常容易地干扰许多用户。拖延攻击者比直接拒绝访问要好,尤其是当它是付费客户时。
您可以使用bcrypt创建一个单向散列,使用php的crypt()函数并传入适当的河豚盐。整个等式中最重要的一点是:a)算法没有被破坏;b)您正确地修改了每个密码。不要使用应用程序范围内的salt;这会打开整个应用程序,使其从一组彩虹表进行攻击。
php-crypt函数
- 这是正确的方法——使用PHP的crypt()函数,它支持几种不同的密码散列函数。确保您没有使用CRYPT_STD_DES或CRYPT_EXT_DES-任何其他支持的类型都可以(包括bcrypt,名称为CRYPT_BLOWFISH)。
- 通过"回合"选项,sha也确实有一个成本参数。在使用它的时候,我也没有看到任何理由支持Bcrypt。
- 实际上,一个密码的sha-1(或md5)仍然可以很容易地强制执行,无论是否使用盐(盐对彩虹表有帮助,而不是强制执行)。使用BCRIPT。
- 当每个人都在说php的crypt()时,他们似乎都说"bcrypt",这让我很不安。
- 帕尼克为什么?该算法称为bcrypt。crypt公开了几个密码散列,其中bcrypt对应于CRYPT_BLOWFISH常量。Bcrypt目前是crypt支持的最强的算法,它支持的其他一些算法非常弱。
编辑:2013.01.15-如果您的服务器支持它,请使用Martinstoeckli的解决方案。
每个人都想让事情变得更复杂。crypt()函数可以完成大部分工作。
1 2 3 4 5 6 7 8 9 10 11
| 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);
} |
例子:
1 2 3
| $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 |
我知道这很明显,但请不要用"密码"作为密码。
- 可以改进salt的创建(使用操作系统的随机源),否则它对我来说很好。对于较新的PHP版本,最好使用2y而不是2a。
- 用mcrypt_create_iv($size, MCRYPT_DEV_URANDOM)作为盐的来源。
- 当我有时间的时候,我会仔细看看mcrypt_create_iv(),如果没有其他的东西的话,它会稍微提高性能。
- @codesinchaos我尝试用mcrypt_create_iv(22, MCRYPT_DEV_URANDOM)替换for循环,但它生成的值不在64个字符的字母表中。
- 添加base64编码并转换为自定义字母bcrypt使用。mcrypt_create_iv(17, MCRYPT_DEV_URANDOM)、str_replace('+', '.', base64_encode($rawSalt))、$salt=substr($salt, 0, 22);。
- @Jonhulka—看看PHP的兼容性包[127行],这是一个简单的实现。
- @codesinchaos因为不是所有系统都默认有php mcrypt(我的Ubuntu服务器没有),也不是每个人都有在他们的服务器上安装软件包的特权,所以我将这个作为这些情况的替代方案。
- 可能的快捷方式,请尝试使用$salt .= uniqid($salt,true);,而不是mt rand()。免责声明:查看SO关于Uniqid的讨论。
- 应注意,使用示例password作为密码,即使在将$hash=blowfishCrypt('password1',12);与if(crypt('password',$hash)==$hash)结合使用时,也会生成true,因此应使用另一个示例和/或在该答案中注明。但是,使用$hash=blowfishCrypt('passworx',12);将是错误的,不匹配。
- @Fred II-我刚刚测试过:$hash=blowfishCrypt('password1',12); echo json_encode(crypt('password',$hash)==$hash);输出"假"
- @所以我想使用/添加json_encode会有帮助。
- @fred ii-no:json_encode只是将值转换为字符串。
PHP的5.5版将内置对bcrypt、函数password_hash()和password_verify()的支持。实际上,这些只是围绕函数crypt()的包装纸,应该更容易正确地使用它。它负责生成安全的随机salt,并提供良好的默认值。
使用此功能的最简单方法是:
1 2
| $hashToStoreInDb = password_hash($password, PASSWORD_BCRYPT);
$isPasswordCorrect = password_verify($password, $existingHashFromDb); |
此代码将使用bcrypt(算法2y散列密码),从操作系统随机源生成随机salt,并使用默认的成本参数(目前为10)。第二行检查用户输入的密码是否与已存储的哈希值匹配。
如果要更改成本参数,可以这样做,将成本参数增加1,使计算哈希值所需的时间加倍:
1
| $hash = password_hash ($password, PASSWORD_BCRYPT , array("cost" => 11)); |
与"cost"参数相比,最好省略"salt"参数,因为函数已经尽其所能创建一个加密安全的salt。
对于php版本5.3.7及更高版本,存在一个兼容性包,来自制作password_hash()函数的同一作者。对于5.3.7之前的PHP版本,不支持使用2y的crypt(),即unicode安全bcrypt算法。可以用2a代替它,这是早期PHP版本的最佳选择。
- 在我读到这篇文章后,我的第一个想法是"你如何储存产生的盐"?在浏览文档之后,password_hash()函数最终生成一个字符串,存储加密方法、salt和生成的哈希。所以,它只是将它所需要的一切存储在一个字符串中,以便密码_verify()函数工作。只是想提一下这一点,因为它可以帮助其他人看到这一点。
- @Jzimmerman2011——确切地说,在另一个答案中,我试图用一个例子来解释这种存储格式。
另一种选择是使用scrypt,这是科林·珀西瓦尔在他的论文中特别设计的优于bcrypt的方法。PECL中有一个scrypt-php扩展。理想情况下,该算法将被引入到PHP中,这样就可以为密码函数(理想情况下为"password_scrypt")指定它,但目前还没有。
当前的想法:哈希应该是最慢的,而不是最快的。这将抑制彩虹表攻击。
同样相关,但要注意:攻击者不应该对您的登录屏幕有无限的访问权限。要防止这种情况发生:设置一个IP地址跟踪表,记录每次命中和URI。如果在任何5分钟内有5次以上的登录尝试来自同一个IP地址,请阻止并解释。第二种方法是采用两层密码方案,就像银行那样。在第二道关卡上锁定失败可以提高安全性。
总结:通过使用耗时的哈希函数来降低攻击者的速度。此外,阻止太多访问您的登录,并添加第二层密码。
- 我认为他们假设攻击者已经设法通过其他手段窃取我的DB,现在正试图把密码拿出来,以便在PayPal或其他东西上试用。
- 在2012年的中途,这个答案仍然是不确定的,一个缓慢的哈希算法如何防止彩虹表攻击?我以为随机字节范围salt有吗?我一直认为哈希算法的速度决定了在特定的时间内,它们可以根据从您那里得到的哈希值发送多少次迭代。而且永远不要在登录失败时阻止用户。相信我,你的用户会厌倦的,通常在某些网站上,我需要登录近5次,有时更多,在我记住我的密码之前。第二层也不起作用,但使用手机代码的两步身份验证可以。
- @Sammaye我同意这一点。我在5次失败的登录尝试上设置了一个块,然后快速提升到7次,然后10次,现在20次。任何普通用户都不应该有20次失败的登录尝试,但它的低值足以轻易阻止暴力攻击
- @Brucealdridge我个人认为最好是让你的脚本在7次登录失败后随机暂停一段时间,并显示一个验证码而不是阻止。拦网是一种非常具有侵略性的行动。
- @Sammaye我同意永久挡块是坏的。我指的是一个随尝试失败次数而增加的临时块。
对于OAuth 2密码:
1 2
| $bcrypt = new \Zend\ Crypt\Password\Bcrypt ;
$bcrypt->create("youpasswordhere", 10) |