什么是MD5
md5
是一种密码散列函数,也叫密码散列算法。 密码散列函数
是一种单向散列函数,它可以将给定的数据提取出信息摘要
,也就是给定数据的指纹信息
。结果的摘要信息格式是一致的,通常用一个短的随机字母和数字组成的字符串来代表。
密码散列函数的特点
- 对于任何一个给定的消息,它都很容易就能运算出散列数值。
- 难以用散列数值推算出原始数据。
- 数据变动(哪怕很微小),散列数值也会发生很大的变动。
- 单向散列函数生成的信息摘要是不可预见的。
算数模型为: h = H(M) h为散列数值结果 H为散列函数 M为原始数据 模型特点
- h需要有固定的长度,即生成的散列数值格式需要一致,跟原始数据M的长度和格式无关
- 给定h和H,很难甚至根本无法计算出原始数据M
- 给定H,找到M1和M2,使得 H(M1) = H(M2) 在计算上是不可行的 (但是这不代表不存在散列数值相等的M1和M2,只是想通过计算得出是不可行的)
MD5的应用
一致性验证 在UNIX下有很多软件在下载的时候都提供了一个后缀为.md5的文件,这个文件通常的内容只有一行,格式大概为: MD5 (xxx.tar.gz) = 38b8c2c1093dd0fec383a9d9ac940515
。 这是软件或者下载包的md5散列数值,我们可以计算我们下载的包的散列数值,并与该值进行对比,只有数值相同的才是正确、安全的下载。 这是防止软件被篡改,或者在传输过程造成的文件损坏,只要数据内部结构产生微小的变化,散列数值的结果就会发生很大的变动。 安全访问认证 当我们在程序中保存用户密码的时候,如果我们采用明文储存,当服务器权限或者管理员账号泄露,用户的密码就会被查询出来,根据我们的习惯,我们往往会在多个不同系统中使用相同的密码,这会造成更大的影响。 我们可以将用户的密码进行md5加密储存,在用户登录的时候,将输入内容进行md5加密,与储存的数值对比,这样子就可以在不需要知道用户的明文密码请求下完成认证验证。 当然这也不是绝对安全的,常见的方式有:字典反查、暴力穷举 暴力穷举先设定一个范围,并在这个范围内逐一地对数据进行验证,需要的运算量和时间比较大。 黑客往往拥有强大的彩虹表
,这就是密码字典。这种表是为了破解密码的散列值而准备的,它将提前计算好的散列数值储存起来,通常都是100G以上。 当黑客拿到了hash散列数值,它可以通过在彩虹表中反查出对应该散列数值的原文,这样子就可以直接登录系统进行操作。
php中md5函数的漏洞
在PHP中,我们也常将md5哈希字符串进行对比,然而却没有在意处理的细节,导致漏洞的出现。 我们在运行以下的php脚本
<?php
$str = md5('QNKCDZO');
var_dump($str == '0');
打印出来的结果是:bool(true) 是不是与我们预想中的情况不一样,这明显是两个不一样的字符串,为什么会得到相等的结果。 我们将$str的值打印出来得到:0e830400451993494058024219903391
为什么"0e830400451993494058024219903391" == "0"
会得到true的结果,这是因为PHP的语言特性,导致了问题的发生。
php是弱类型语言
因为php是弱类型语言,在使用==
进行对比的时候,只判断两个参数的值,而不判断参数的类型。 我们运行该脚本,也一样能得到true的结果
<?php
var_dump("0e830400451993494058024219903391" == 0);
0e代表什么
除了以上demo的QNKCDZO
,以下的字符进行MD5运行后的哈希值也会出现一样的问题
QNKCDZO => 0e830400451993494058024219903391
240610708 => 0e462097431906509019562988736854
s878926199a => 0e545993274517709034328855841020
s155964671a => 0e342768416822451524974117254469
s214587387a => 0e848240448830537924465865611904
s214587387a => 0e848240448830537924465865611904
这些值的md5哈希结果全都是以0e
开头的,我们来看看0e代表的是什么 首先我们了解一下科学计数法。 这是一种计数的写法,把一个数表示成a与10的n次幂相乘的形式(1≤a<10,n为整数)
比如将650000记成 6.5E+5
,在支持科学计数法的计算器中都可以测试,我们手机自带的计算器一般都有该功能。 但是在输入的时候要把+
号省略,并且显示的E是小写的e
在PHP中 以下几种写法的结果相同
<?php
echo 6.5E+5;
echo "\n";
echo 6.5E5;
echo "\n";
echo 6.5e5;
echo "\n";
那么就可以来解释我们上面出现的问题了,以0e开头的数,如果是按科学计数法来计算,不管后面的幂是多大,它的值永远是等于0的。 所以0e830400451993494058024219903391 == 0
php对比数据时的类型选择
由于php是弱类型语言,在处理变量的时候,php内部会根据需要转换数据的格式
<?php
$str = "100";
var_dump($str); // string(3) "100"
echo ($str - 99); // 1
以上例子中,当一个字符串变量需要进行数值运算的时候,php先把它变成了一个数值类型,再计算。 那么我们一开始遇到问题的时候的==
比较运算符号中,php也会根据场景将值转换为对应格式来比较
- 如果比较的数据中,有布尔值,则转为布尔值比较,布尔值比较有一个规则:true> false
- 如果比较的数据中,有数字值,就转为数字值比较
- 如果比较的数据中,两边的值都为 纯数字字符串 ,就转为数字值比较
- 如果以上都不符合,则按常规字符串比较
那么当我们 “0e830400451993494058024219903391” == “0” 的时候,符合第三点要求,两边都是数字字符串,会转为数字值比较,所以得到的结果是true。
0e830400451993494058024219903391 === 0
是错误的哦! 因为科学计数法在php中会转为float类型 可以通过var_dump(0e830400451993494058024219903391)
查看类型;
问题以及解决
假设有一个会员账号设置的密码是 240610708
,那么登录的时候如果输入s155964671a
或者其他的值(上面有列举了一些),他也是能登录成功的。 那么需要我们如何处理呢 我们将用户的密码md5储存在数据库中,取出来之后应该是string类型的,我们应该使用恒等运算符
,来让php脚本限定两个参数的类型。
<?php
var_dump("0e830400451993494058024219903391" === "0e342768416822451524974117254469")
脚本将会得到不相等的结果。 在php中,使用比较运算符的时候需要考虑数据类型的问题,防止特殊数据影响了判断的结果。
提示
关于MD5在PHP中的使用注意事项 将会有一篇新的文章罗列讲解,有兴趣可以在博客内搜索看一下。
扫描二维码,分享此文章