搜索
首页后端开发php教程理解php Hash函数,增强密码安全_PHP

1.声明
密码学是一个复杂的话题,我也不是这方面的专家。许多高校和研究机构在这方面都有长期的研究。在这篇文章里,我希望尽量使用简单易懂的方式向你展示一种安全存储Web程序密码的方法。
2.“Hash”是做什么的?
“Hash将一段数据(小数据或大数据)转换成一段相对短小的数据,如字符串或整数。”
这是依靠单向hash函数来完成的。所谓单向是指很难(或者是实际上不可能)将其反转回来。一个常见的hash函数的例子是md5(),它流行于各种计算机语言和系统。
复制代码 代码如下:
$data = "Hello World";
$hash = md5($data);
echo $hash; // b10a8db164e0754105b7a99be72e3fe5

使用md5()运算出来的结果总是32个字符的字符串,不过它只包含16进制的字符,从技术上来说它也可以用128位(16字节)的整形数来表示。你可以使用md5()来处理很长的字符串和数据,但是你始终得到的是一个固定长度的hash值,这也可能可以帮助你理解为什么这个函数是“单向”的。
3.使用Hash函数来存储密码
典型的用户注册过程:
用户填写注册表单,其中包含密码字段;
程序将所有用户填写的信息存储到数据库中;
然而密码在存储到数据库前通过hash函数加密处理;
原始的密码不再存储在任何地方,或者说它被丢弃了。
用户登录过程:
用户输入用户名和密码;
程序将密码通过以注册相同的hash函数进行加密;
程序从数据库查到用户,并读取hash后的密码;
程序比较用户名和密码,如果匹配则给用户授权。
如何选择合适的方法来加密密码,我们将在文章的后面讨论这个问题。
4.问题1:hash碰撞
hash碰撞是指对两个不同的内容进行hash得到了相同的hash值。发生hash碰撞的可能性取决于所用的hash算法。
如何产生?
举个例子,一些老式程序使用crc32()来hash密码,这种算法产生一个32位的整数作为hash结果,这意味着只有2^32 (即4,294,967,296) 种可能的输出结果。
让我们来hash一个密码:
复制代码 代码如下:
echo crc32('supersecretpassword');
// outputs: 323322056

现在我们假设一个人窃取了数据库,得到了hash过的密码。他可能不能将323322056还原为‘supersecretpassword',然而他可以找到另一个密码,也能被hash出同样的值。这只需要一个很简单的程序:
复制代码 代码如下:
set_time_limit(0);
$i = 0;
while (true) {
if (crc32(base64_encode($i)) == 323322056) {
echo base64_encode($i);
exit;
}
$i++;
}

这个程序可能需要运行一段时间,但是最终它能返回一个字符串。我们可以使用这个字符串来代替‘supersecretpassword',并使用它成功的登录使用该密码的用户帐户。
比如在我的电脑上运行上面的程序几个月后,我得到了一个字符串:‘MTIxMjY5MTAwNg=='。我们来测试一下:
复制代码 代码如下:
echo crc32('supersecretpassword');
// outputs: 323322056
echo crc32('MTIxMjY5MTAwNg==');
// outputs: 323322056

如何解决?
现在一个稍强一点的家用PC机就可以一秒钟运行十亿次hash函数,所以我们需要一个能产生更大范围的结果的hash函数。比如md5()就更合适一些,它可以产生128位的hash值,也就是有340,282,366,920,938,463,463,374,607,431,768,211,456种可能的 输出。所以人们一般不可能做那么多次循环来找到hash碰撞。然而仍然有人找到方法来做这件事情,详细可以查看例子。
sha1()是一个更好的替代方案,因为它产生长达160位的hash值。
5.问题2:彩虹表
即使我们解决了碰撞问题,还是不够安全。
“彩虹表通过计算常用的词及它们的组合的hash值建立起来的表。”
这个表可能存储了几百万甚至十亿条数据。现在存储已经非常的便宜,所以可以建立非常大的彩虹表。
现在我们假设一个人窃取了数据库,得到了几百万个hash过的密码。窃取者可以很容易地一个一个地在彩虹表中查找这些hash值,并得到原始密码。虽然不是所有的hash值都能在彩虹表中找到,但是肯定会有能找到的。
如何解决?
我们可以尝试给密码加点干扰,比如下面的例子:
复制代码 代码如下:
$password = "easypassword";
// this may be found in a rainbow table
// because the password contains 2 common words
echo sha1($password); // 6c94d3b42518febd4ad747801d50a8972022f956
// use bunch of random characters, and it can be longer than this
$salt = "f#@V)Hu^%Hgfds";
// this will NOT be found in any pre-built rainbow table
echo sha1($salt . $password); // cd56a16759623378628c0d9336af69b74d9d71a5

在这里我们所做的只是在每个密码前附加上一个干扰字符串后进行hash,只要附加的字符串足够复杂,hash后的值肯定是在预建的彩虹表中找不到的。不过现在还是不够安全。
6.问题3:还是彩虹表
注意,彩虹表可能在窃取到干拢字符串后重头开始建立。干扰字符串一样也可能被和数据库一起被窃取,然后他们可以利用这个干扰字符串从头开始创建彩虹表,如“easypassword”的hash值可能在普通的彩虹表中存在,但是在新建的彩虹表里,“f#@V)Hu^%Hgfdseasypassword”的hash值也会存在。
如何解决?
我们可以对每个用户使用唯一的干扰字符串。一个可用的方案就是使用用户在数据库中的id:
复制代码 代码如下:
$hash = sha1($user_id . $password);

这种方法的前提是用户的id是一个不变的值(一般应用都是这样的)
我们也可以为每个用户随机生成一串唯一的干扰字符串,不过我们也需要将这个串存储起来:
复制代码 代码如下:
// generates a 22 character long random string
function unique_salt() {
return substr(sha1(mt_rand()),0,22);
}
$unique_salt = unique_salt();
$hash = sha1($unique_salt . $password);
// and save the $unique_salt with the user record
// ...

这种方法就防止了我们受到彩虹表的危害,因为每一个密码都使用一个不同的字符串进行了干扰。攻击者需要创建和密码数量一样的彩虹表,这是很不切实际的。
7.问题4:hash速度
大部分hash算法在设计时就考虑了速度问题,因为它一般用来计算大数据或文件的hash值,以验证数据的正确性和完整性。
如何产生?
如前所述,现在一台强劲的PC机可以一秒运算数十亿次,很容易用暴力破解法去尝试每个密码。你可能会以为8个以上字符的密码就可以避免被暴力破解了,但是让我们来看看是否真是这样:
如果密码可以包含小写字母,大写字母和数字,那就有62(26+26+10)个字符可选;
一个8位的密码有62^8种可能组合,这个数字略大于218万亿。
以一秒钟运算10亿次hash值的速度计算,这只需要60小时就可以解决。
对于一个6位的密码,也是很常用的密码,只需要1分钟就可以破解。要求9到10位的密码可能会比较安全了,不过这样有的用户可能会觉得很麻烦。
如何解决?
使用慢一点的hash函数。
“假设你使用一个在相同硬件条件下一秒钟只能运行100万次的算法来代替一秒10亿次的算法,那么攻击者可能需要要花1000倍的时间来做暴力破解,60小只将会变成7年!”
你可以自己实现这种方法:
复制代码 代码如下:
function myhash($password, $unique_salt) {
$salt = "f#@V)Hu^%Hgfds";
$hash = sha1($unique_salt . $password);
// make it take 1000 times longer
for ($i = 0; $i $hash = sha1($hash);
}
return $hash;
}

你也可以使用一个支持“成本参数”的算法,比如 BLOWFISH。在php中可以用crypt()函数实现:
复制代码 代码如下:
function myhash($password, $unique_salt) {
// the salt for blowfish should be 22 characters long
return crypt($password, '$2a$10.$unique_salt');
}

这个函数的第二个参数包含了由”$”符号分隔的几个值。第一个值是“$2a”,指明应该使用BLOWFISH算法。第二个参数“$10”在这里就是成本参数,这是以2为底的对数,指示计算循环迭代的次数(10 => 2^10 = 1024),取值可以从04到31。
举个例子:
复制代码 代码如下:
function myhash($password, $unique_salt) {
return crypt($password, '$2a$10.$unique_salt');
}
function unique_salt() {
return substr(sha1(mt_rand()),0,22);
}
$password = "verysecret";
echo myhash($password, unique_salt());
// result: $2a$10$dfda807d832b094184faeu1elwhtR2Xhtuvs3R9J1nfRGBCudCCzC

结果的hash值包含$2a算法,成本参数$10,以及一个我们使用的22位干扰字符串。剩下的就是计算出来的hash值,我们来运行一个测试程序:
复制代码 代码如下:
// assume this was pulled from the database
$hash = '$2a$10$dfda807d832b094184faeu1elwhtR2Xhtuvs3R9J1nfRGBCudCCzC';
// assume this is the password the user entered to log back in
$password = "verysecret";
if (check_password($hash, $password)) {
echo "Access Granted!";
} else {
echo "Access Denied!";
}
function check_password($hash, $password) {
// first 29 characters include algorithm, cost and salt
// let's call it $full_salt
$full_salt = substr($hash, 0, 29);
// run the hash function on $password
$new_hash = crypt($password, $full_salt);
// returns true or false
return ($hash == $new_hash);
}

运行它,我们会看到”Access Granted!”
8.整合起来
根据以上的几点讨论,我们写了一个工具类:
复制代码 代码如下:
class PassHash {
// blowfish
private static $algo = '$2a';
// cost parameter
private static $cost = '$10';
// mainly for internal use
public static function unique_salt() {
return substr(sha1(mt_rand()),0,22);
}
// this will be used to generate a hash
public static function hash($password) {
return crypt($password,
self::$algo .
self::$cost .
'$'. self::unique_salt());
}
// this will be used to compare a password against a hash
public static function check_password($hash, $password) {
$full_salt = substr($hash, 0, 29);
$new_hash = crypt($password, $full_salt);
return ($hash == $new_hash);
}
}

以下是注册时的用法:
复制代码 代码如下:
// include the class
require ("PassHash.php");
// read all form input from $_POST
// ...
// do your regular form validation stuff
// ...
// hash the password
$pass_hash = PassHash::hash($_POST['password']);
// store all user info in the DB, excluding $_POST['password']
// store $pass_hash instead
// ...

以下是登录时的用法:
复制代码 代码如下:
// include the class
require ("PassHash.php");
// read all form input from $_POST
// ...
// fetch the user record based on $_POST['username'] or similar
// ...
// check the password the user tried to login with
if (PassHash::check_password($user['pass_hash'], $_POST['password']) {
// grant access
// ...
} else {
// deny access
// ...
}

9.加密是否可用
并不是所有系统都支持Blowfish加密算法,虽然它现在已经很普遍了,你可以用以下代码来检查你的系统是否支持:
复制代码 代码如下:
if (CRYPT_BLOWFISH == 1) {
echo "Yes";
} else {
echo "No";
}

不过对于php5.3,你就不必担心这点了,因为它内置了这个算法的实现。
结论
通过这种方法加密的密码对于绝大多数Web应用程序来说已经足够安全了。不过不要忘记你还是可以让用户使用安全强度更高的密码,比如要求最少位数,使用字母,数字和特殊字符混合密码等。
声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
继续使用PHP:耐力的原因继续使用PHP:耐力的原因Apr 19, 2025 am 12:23 AM

PHP仍然流行的原因是其易用性、灵活性和强大的生态系统。1)易用性和简单语法使其成为初学者的首选。2)与web开发紧密结合,处理HTTP请求和数据库交互出色。3)庞大的生态系统提供了丰富的工具和库。4)活跃的社区和开源性质使其适应新需求和技术趋势。

PHP和Python:探索他们的相似性和差异PHP和Python:探索他们的相似性和差异Apr 19, 2025 am 12:21 AM

PHP和Python都是高层次的编程语言,广泛应用于Web开发、数据处理和自动化任务。1.PHP常用于构建动态网站和内容管理系统,而Python常用于构建Web框架和数据科学。2.PHP使用echo输出内容,Python使用print。3.两者都支持面向对象编程,但语法和关键字不同。4.PHP支持弱类型转换,Python则更严格。5.PHP性能优化包括使用OPcache和异步编程,Python则使用cProfile和异步编程。

PHP和Python:解释了不同的范例PHP和Python:解释了不同的范例Apr 18, 2025 am 12:26 AM

PHP主要是过程式编程,但也支持面向对象编程(OOP);Python支持多种范式,包括OOP、函数式和过程式编程。PHP适合web开发,Python适用于多种应用,如数据分析和机器学习。

PHP和Python:深入了解他们的历史PHP和Python:深入了解他们的历史Apr 18, 2025 am 12:25 AM

PHP起源于1994年,由RasmusLerdorf开发,最初用于跟踪网站访问者,逐渐演变为服务器端脚本语言,广泛应用于网页开发。Python由GuidovanRossum于1980年代末开发,1991年首次发布,强调代码可读性和简洁性,适用于科学计算、数据分析等领域。

在PHP和Python之间进行选择:指南在PHP和Python之间进行选择:指南Apr 18, 2025 am 12:24 AM

PHP适合网页开发和快速原型开发,Python适用于数据科学和机器学习。1.PHP用于动态网页开发,语法简单,适合快速开发。2.Python语法简洁,适用于多领域,库生态系统强大。

PHP和框架:现代化语言PHP和框架:现代化语言Apr 18, 2025 am 12:14 AM

PHP在现代化进程中仍然重要,因为它支持大量网站和应用,并通过框架适应开发需求。1.PHP7提升了性能并引入了新功能。2.现代框架如Laravel、Symfony和CodeIgniter简化开发,提高代码质量。3.性能优化和最佳实践进一步提升应用效率。

PHP的影响:网络开发及以后PHP的影响:网络开发及以后Apr 18, 2025 am 12:10 AM

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP类型提示如何起作用,包括标量类型,返回类型,联合类型和无效类型?PHP类型提示如何起作用,包括标量类型,返回类型,联合类型和无效类型?Apr 17, 2025 am 12:25 AM

PHP类型提示提升代码质量和可读性。1)标量类型提示:自PHP7.0起,允许在函数参数中指定基本数据类型,如int、float等。2)返回类型提示:确保函数返回值类型的一致性。3)联合类型提示:自PHP8.0起,允许在函数参数或返回值中指定多个类型。4)可空类型提示:允许包含null值,处理可能返回空值的函数。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。