如何解决? 现在一个稍强一点的家用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
这种方法的前提是用户的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 < 1000; $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"); }
结果的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 // ... }