首页 > 代码库 > PHP-密码学算法及其应用-散列函数

PHP-密码学算法及其应用-散列函数

转自http://www.smatrix.org/bbs/simple/index.php?t5591.html

//////////////////////////////////////////////////////////////////////////////
目录
1.    PHP的散列函数及其应用
2.    PHP中的对称密码算法及其应用
3.    PHP的公钥密码算法及其应用
///////////////////////////////////////////////////////////////////////////////
序: 这么几年一直在潜心修炼内功,没有在网上写点东西,安全矩阵作为一个国内少有的以密码学和信息安全理论研究为特色的学术技术研究组织,在国内推广密码学应用是我们的责任,虽然这个道路比较艰辛和漫长,但总是需要人来做的。我计划对几大主流的脚本语言分别进行阐述,系统总结,力图做到普通的脚本开发者和网友能够看懂。这是第一篇关于PHP的,以下假设读者已经具有PHP的基本知识。
1.    PHP的散列函数及其应用
1.1    什么是散列函数
散列函数又被称为hash函数(哈希函数),也称杂凑函数,就是把任意长的输入消息串变化成固定长的输出串的一种函数。这个输出串就叫做该消息的哈希值(或称为杂凑值)。一般用于产生消息摘要,进行数据消息的完整性检验。严格意义上来讲,散列函数并不是密码算法,因为它是单向不可逆的。一个散列函数有以下特点:(1)输入长度是任意的,而输出长度是固定的,长度应该足够的长,以便抵抗生日攻击(关于生日攻击请百度)。(2)对每一个给定的输入,正向计算输出即散列值是很容易的,但逆向寻找碰撞时困难的。
Hash函数主要用于完整性校验和提高数字签名的有效性,在密码协议中有着非常重要的应用,目前已有很多成熟的方案。关于具体的密码学方面的知识可以到安全矩阵上去下载一些书来看。
常见散列函数(Hash函数)有两种:
(1)、MD5(Message Digest Algorithm 5):是RSA数据安全公司开发的一种单向散列算法,MD5被广泛使用,可以用来把不同长度的数据块进行暗码运算成一个128位的数值;
(2、SHA(Secure Hash Algorithm)这是一种较新的散列算法,可以对任意长度的数据运算生成一个160位的数值。
目前在理论上MD5和SHA已经被破解,但是在实际应用中并非那么简单,只要合理应用总是可行的。另外以下这些也被认为具有散列函数的功能。
(1)、MAC(Message Authentication Code):消息认证代码,是一种使用密钥的单向函数,可以用它们在系统上或用户之间认证文件或消息。HMAC(用于消息认证的密钥散列法)就是这种函数的一个例子。
(2)、CRC(Cyclic Redundancy Check):循环冗余校验码,CRC校验由于实现简单,检错能力强,被广泛使用在各种数据校验应用中。
(3)、自产山寨版和基于一些对称加密算法的散列函数。
1.2 PHP中散列函数及其应用
Php中的最常用的散列函数有这么几个:
md5(),md5_file(), sha1(),sha1_file().
其实还有更为强悍和灵活的crypt和mhash系列。首先我们从简单常用的开始,先看一个列子:

<?php    $str="security matrix is a website about cryptograhy.";    $hashmd5=md5($str);    $hashsha1=sha1($str);    echo "md5 hash value is:".$hashmd5;    echo ‘<br>‘;    echo "sha1 hash value is:$hashsha1";    //最后的结果是:    //md5 hash value is:c4558a37e61eb1db03f7270d4f674ae6    //sha1 hash value is:d10155e45624bf84b4d1fdf4e6e47e067b7a4e1a

很 显然,我们的字符串经过这两个函数后得到一系列没有规律的16进制序列。注意到SHA1产生的序列比MD5多了8个字符,总共SHA1这是因为SHA1是 160位的散列,而MD5是128位的散列。密码学中所说的位是指二进制的位数,一个16进制占4个二进制位,所以SHA1是16进制40位,MD5是 16进制32位。这两个函数的原型是:

string md5 ( string str [, bool raw_output] )string sha1 ( string str [, bool raw_output] )

其中有个可选参数raw_output是布尔类型的,默认是false,如果此处写1或者true等能够保证这个值为真的表达式。则输出时真实的消息摘要。看同样的例子:

<?php    $str="security matrix is a website about cryptograhy.";    $hashmd5=md5($str,1);    $hashsha1=sha1($str,1);    echo "md5 hash value is:".$hashmd5;    echo ‘\<br>‘;    echo "sha1 hash value is:$hashsha1";    //最后的结果是:    //md5 hash value is:腢??臂 ? OgJ鎈    //sha1 hash value is:?U銿$縿囱驿~ {zN 

很 显然,这有点像火星文,在MYSQL存储时这个消息摘要,有时候因为字符集会出现难以解释的人品问题。一般我们只用默认的,以16进制的形式输出的,存储 方便。一般,MD5和SHA1用得最多的场合就是验证用户密码的场合。通常将用户的口令用MD5或者SHA1处理后放入数据库中,以减少万一被SQL注入 攻击或者获得数据表后的暴露真实口令信息的风险。但是,由于使用标准的MD5算法,常用的口令的散列值可以很容易穷举,所以实际上寻找一个碰撞可能就比较 容易了。网上有很多这样的MD5散列碰撞的查询站点。为了对付这种穷举,我们在使用时可以采用些变形或者带有salt的MAC,而且最好使用SHA1。以 下给个例子:

<?php    $passwd="13455564432";    $salt="dfdasafgr4vtrgrrgf";    $str=$passwd.$salt;    $hashmd5=md5(md5($str).$salt);      echo $hashmd5; //结果:bc052554e38b588df52594176a148b8b

现在我们来谈谈md5_file(), sha1_file().很显然,这个是用来计算给定文件名的散列值的。函数原型如下:

string md5_file ( string filename [, bool raw_output])string sha1_file ( string filename [, bool raw_output])


其中参数的意义和用法跟前面md5(), sha1()是一样的。我们可以来看个例子:

<?php    $filename = ‘1.txt‘;    $hash = md5_file($filename);    echo $hash; //结果e0a7a59e58c7b138e850b8055fba27da


这两个函数可以用来判断自己的程序文件是否被篡改过,比如是否嵌入了木马。DISCUZ论坛源码有个代码检查功能,就用这个实现。除此之外,还可以用来构造复杂的数学验证算法,防止别人破解需要授权的PHP程序。因为只要修改了程序,其文件的散列值基本上都要改变的。对脚本代码加壳当然这对于一般的程序员有一定的难度。一般应用中有这几个基本上也够用了,这是PHP默认就支持的。接下来我们来看看MAC和CRC。
其中主要的函数是mhash(),通俗的说,就是带有一个密钥的哈希函数。为什么要带个密钥呢,其实这种改进主要是为了防止散列函数的在线暴力破解和提高抗碰撞能力。函数原型如下:

string mhash ( int hash, string data [, string key])


因为这个属于扩展模块,所以要使用mhash(),需要在编译PHP的时候增加一项配置,在PHP的配置时,添加--with-mhash[=DIR]。如果没有,就会出现“Fatal error: Call to undefined function…”,这个函数的第一项参数是我们要使用的哈希算法模式,由一些预定义算法模式构成,每种算法模式得到的散列值是不一样的,其中最常用的如下:

MHASH_MD5MHASH_SHA1MHASH_HAVAL256  //HAVAL系列算法支持多种输出长度,从128-256MHASH_HAVAL192MHASH_HAVAL160MHASH_HAVAL128MHASH_RIPEMD160 //RIPEMD系列算法也支持多种输出长度,从128-256MHASH_GOSTMHASH_TIGERMHASH_CRC32MHASH_CRC32B 


第二项参数是我们要计算哈希值的字符串,第三项参数是密钥。除此之外,HMAC体系还有其他的一些函数,如下所示:

mhash_count --获取最多能够支持的HASH函数算法模式的个数mhash_get_block_size --获取特定预定义算法模式的散列值长度mhash_get_hash_name --获取预定义算法名称mhash_keygen_s2k --产生一个安全的密码散列值,完全可以取代MD5对用户输入口令的处理mhash --计算散列值


为了说明他们的用法,我们来看看例子:

<?php    $input = "security matrix";    $hash = mhash(MHASH_MD5, $input);    echo "The hash is " . bin2hex($hash) . "<br />\n";//结果是:The hash ise6cfee2c4530b72d7f8f4b010fa80a00    $hash = mhash(MHASH_MD5, $input, "smatrix");    echo "The hmac is " . bin2hex($hash) . "<br />\n";//结果是:The hmac is d3e7983ba7bdb7c7d0150e056d2c5017     echo mhash_count()."<br />\n";     $hash = MHASH_RIPEMD160;     echo mhash_get_hash_name($hash)."<br />\n"; //获取算法名称,为RIPEMD160     echo mhash_get_hash_name(24)."<br />\n"; //让我们看看它提供的24号算法模式是什么,获取算法名称,为RIPEMD256     echo mhash_get_block_size(24)."<br />\n"; //获取24号算法模式产生的散列值长度,结果为32,32*8=256,一个字符占8位



其中mhash_keygen_s2k是个特别好的东西,建议PHP开发者能够用它取代传统的MD5来处理用户口令,至少让网上的这些MD5碰撞检索站点失效。mhash_keygen_s2k()函数以指定参数1-hash算法模式,指定的参数3-随机的盐,对原始口令参数2产生bytes长度(参数4)产生一个伪随机腌制的口令。所谓盐,就是在信息中添加噪声干扰,破坏其部分语义特征。 这个函数的原型是:

string mhash_keygen_s2k ( int hash, string password, string salt, int bytes )


下面,我们看下具体的用法:

<?php    $password = "security matrix";    $salt = "f34iffffffffj4";    $hash = MHASH_RIPEMD160;    $bytes=160;    echo mhash_keygen_s2k ( $hash, $password, $salt, $bytes ); //产生长度160的腌制后口令    //结果:    //毉v9銑B??韔?幢a 镝X K藎ù壥Vd磌?目 ?┕ ?潿(j  Iゎ忲" ? ¬潥护窯1〧//蠊得牶熔.跆]?欭:躎?  3浹か戠3-がJ篮9&?v1懏^?縋3 鄡 鳖-?鎿轐p`xxJ;?    //恏 


值得注意的是,实际使用中,最好不同的用户口令使用不同的盐,相同的盐总是让人不是很放心,一般情况下,我们没必要自带一个盐库,随机产生一个又需要保留,否则到时候用户口令比较时候会出错。这里我可以给一个可行的方案,使得盐和用户口令内相关,免去记忆盐的烦恼,如下:

<?php    $password = "security matrix";    $salt = md5($password);    $hash = MHASH_RIPEMD160;    $bytes=160;    echo mhash_keygen_s2k ( $hash, $password, $salt, $bytes ); //产生长度160的腌制后口令


现在我们介绍最后一个单向散列函数crypt,这个很多人会误认是对称加密函数,其实不是,下一章我会专门论述PHP的对称加密算法。现在我们看看这个函数的原型:

string crypt ( string str [, string salt])


这个函数使用了参数,其中第一个是我们需要求散列值的字符串,后面一个可选参数是盐。如果你自己不填,它会使用默认的盐。不过我不建议使用默认的,默认的可能会出现一些应用中的特殊问题。比如由于盐的改变导致用户口令腌制时最后的散列值不同,导致验证用户失败。这个函数如果你看PHP手册,可能会有点晕,发现该函数不知道在何处定义其散列算法。实际上认真分析会发现,它是根据盐的长度和格式来决定使用何种算法的,这是个非常古怪的方法。我们还是看看例子。

<?phpif (CRYPT_STD_DES == 1) {    echo ‘Standard DES: ‘ . crypt(‘www.smatrix.org‘, ‘rl‘) . "<br>\n";}//当盐是两个字符的时候,内置的散列算法使用了标准的DES构造,也就是CRYPT_STD_DES模式if (CRYPT_EXT_DES == 1) {    echo ‘Extended DES: ‘ . crypt(‘www.smatrix.org‘, ‘_J9..rasm‘) . "<br>\n";}//当盐是九个字符的时候,内置的散列算法使用了扩展的DES构造,也就是CRYPT_EXT_DES模式if (CRYPT_MD5 == 1) {    echo ‘MD5:          ‘ . crypt(‘www.smatrix.org‘, ‘$1$rasmusle$‘) . "<br>\n";}//当盐采用$1$开头,且12个字符时,内置的散列算法使用了MD5,也就是CRYPT_MD5模式if (CRYPT_BLOWFISH == 1) {    echo ‘Blowfish:     ‘ . crypt(‘www.smatrix.org‘, ‘$2a$07$rasmuslerd...........$‘) . "<br>\n";}//当盐采用$2a$或者$2$开头,且16个字符时,内置的散列算法使用了BLOWFISH构造,也就是CRYPT_BLOWFISH 模式//结果://Standard DES: rls0yK8/sarN6//Extended DES: _J9..rasm6cqPLViS6Po//MD5: $1$rasmusle$Z4lIslfbi/Oo4nyaxR4eB///Blowfish: $2a$07$rasmuslerd............wnbDKPDtWJSnGforY7iyGVP8XWLrCni


PHP中关于散列算法基本已经讲完了,从上面的论述看,MHASH函数体系使用灵活而且安全性更好一些。CRYPT比较古怪,一般很少有人会使用。基本散列函数使用最方便,但是问题也较多,容易穷举。当然,并不是每个IDC都会在其PHP模块中加载MHASH,我们开发应用时一定要搞清楚服务器环境。一般可以通过phpinfo函数中信息看出来:
mhash
MHASH support     Enabled
MHASH API Version     Emulated Support
就写到这里,下一节是PHP的对称密码算法及其应用。希望大家能在开发时多使用成熟的密码学解决方案,而不是自己创新山寨一个。