前一段时间 SHA-1 算法被破解了,大家都在说这个算法太危险了,其实并不是想象的那样,简单的结论就是应该正确的认识这个算法,发生碰撞并不完全代表不安全。
什么是 Hash 函数
Hash 函数和数据结构中的哈希表其实并不完全一样,数据结构中的哈希表是为了快速查找,而 Hash 函数由于有很多特性,在很多场合都可以使用。那么它有什么特性呢?
- 单向性,消息经过运算得到的散列值不能反解(这也说明它不是加密算法,因为加密算法需要能够反解)。
- 不管消息有多大,通过 Hash 函数运算后,散列值的长度都是固定的。
- 在这个世界上很难发现两个消息具有同样的散列值。
- 消息只要稍微改变下,散列值就会发生变化。
- Hash 函数的运算特别快速(针对 MD5、SHA-1 这样的算法来说)。
其实最重要的特性就是很难发现两个消息具有同样的散列值,Hash 函数理论上要求强抗碰撞性,也正因为如此 Hash 函数被认为是“安全”的。
Hash 函数在那些场合下使用呢
因为有了上述的特性,对于我们程序员来说,很多地方都能看到 Hash 函数的影子(确切的说是看到 MD5 或 SHA-1)。
(1)比如我们在下载软件的时候,总是发现一个值“该软件的 MD5 值是多少多少”,这表示什么意思呢?假如从安全的角度来看,你下载了一个软件,然后对这软件做 Hash 运算,假如发现散列值和下载网站上明文标出的散列值不一样,就说明你可能下载了一个假软件(可能不安全)。
(2)在 Git 中,任何对象(不管是 blob 对象、树对象等等)都是通过 SHA-1 这个 Hash 函数运算得来的,它认为在一个 Git 仓库中,每个内容都应该是唯一的。
(3)HTTPS 中也有 Hash 函数的影子,比如浏览器首先会对证书进行 SHA-1 运算得到摘要,然后再和原始证书上的散列值进行比较;还有为了保证发送内容的完整性,HTTPS 中服务器也会使用 SHA-1 对内容进行摘要计算,然后通过对称加密算法运算后发送给用户。
(4)其实对于技术开发来说,Hash 函数用的比较多的地方就是对口令进行加密,Hash 函数本身和口令加密没有任何关系,但由于它具备的一些特性,确实可以用来加密,用 cryptographic hash functions 来进行标识比较好。这一块后面也会重点介绍。
发生碰撞说明什么
假如能找到两个不同的消息其散列值一样,那么说明发生了碰撞,这样就代表不安全了,虽然现在 Google 发现 SHA-1 存在碰撞(其实以前就说 SHA-1 不安全,等真正产生的时候大家才如令大敌)。其实碰撞不完全代表不安全,首先要产生碰撞需要话费大量的运算时间和空间,Google spent 6500 CPU years and 110 GPU years to convince everyone we need to stop using SHA-1 for security critical applications.
;其次一个系统真正是否安全取决你如何使用 Hash 函数以及原始消息是如何组成的。
比如在 Git 中,并不是对文件内容直接做 SHA-1 运算,运算的内容不仅包括文件内容,还包括了很多元信息,而想要伪造这些本身很困难,所以现实情况下,SHA-1 情况并没有那么严重。
cryptographic hash functions
这是我想重点描述的一块,如何安全的存储用户口令,观点就是 Hash 函数并没有错,错在于你可能并不理解它。
对于开发者来说要明白 Hash 函数的原理,寻找存储口令的最佳实践。
即使我们并不明白密码学,也要学会如何正确的使用 Hash 函数存储口令。下面的 5 个观点希望你能做到,做的越多口令可能就更安全。
(1)假设你存储用户密码的数据库被脱库了,这样才能通过字典攻击和暴力攻击获取用户口令。假如完全通过 HTTP 的暴力攻击去获取用户密码,很大程度上会被当作 DDOS 攻击而屏蔽掉,你任何校验口令的程序需要有保护机制。
(2)你的口令应该足够“安全”,最好是 ASCII 的无序组合,因为口令长度增大 1 位,攻击者花费的时间就更长。
(3)在使用 SHA-1 等 Hash 函数的时候,一定要在口令后面附上盐值,盐值一定要足够唯一,而且盐值一定不要和用户数据库(包含密码)保存在一起,盐值主要是为了防止彩虹表攻击,加大口令破解难度。
(4)MD5、SHA-1 这样的 Hash 函数运算特别快,所以不适合用作加密口令,因为速度越快,攻击者破解花费的时间就越少。所以应该选择运算相对慢的函数,比如 Bcrypt 算法。
(5)Bcrypt 算法能够让运算减慢速度,原因在于有个迭代因子的概念,另外个好处就是算法本身会存储盐值,不用额外在别的地方存储(这本身就很危险),所以提倡使用。假如你是 PHP 开发者,请直接使用 Password Hashing
扩展,其对 Bcrypt 做了透明处理。
假如你想自己实现 Bcrypt(对 Crypt 的一个封装),可以使用下列的 PHP 代码:
<?php
class PassHash {
// blowfish ,高于 PHP 5.3.7
private static $algo = '$2a$';
public static function unique_salt() {
return substr(sha1(mt_rand()),0,22);
}
public static function hash($password,$cost=10) {
return crypt($password,self::$algo .$cost . self::unique_salt());
}
public static function check_password($hash, $password) {
$full_salt = substr($hash, 0, 29);
$new_hash = crypt($password, $full_salt);
return ($hash == $new_hash);
}
}
$user_pwd = "mypassword";
$pass_hash = PassHash::hash($user_pwd,5);
var_dump(PassHash::check_password($pass_hash, $user_pwd));