[toc]

HASH算法

摘要算法又称哈希算法、散列算法,它的作用是:它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。

要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。

哈希函数的主要作用不是完成数据加密与解密工作,它是用来检验数据完整性的重要技术,运算结果具有不可逆性。

通过哈希函数,可以为数据创建"数组指纹"(散列值/哈希值),哈希值通常是一个短的随机字母和数字组成的字符串。消息认证流程如下:

img

在上述认证流程中,信息收发双方在通信前已经商定好了具体的哈希算法,并且该算法是公开的。

如果消息在传递过程中被篡改,则该消息不能与已经获得的数字指纹(哈希值)相匹配。


哈希函数的一些特性

  • 消息的长度不受限制;
  • 对于给定的消息,其哈希值的计算是很容易的;
  • 如果两个哈希值不想同,则这两个哈希值的原文数据也不想同,这个特性使得哈希函数具有确定性的结果;
  • 哈希函数的运算过程是不可逆的,即函数的单向性。这也是单向函数命名的由来。
  • 对于一个已知的消息和其哈希值,要找到另一个消息使其获得相同的哈希值是不可能的,即抗弱碰撞性,用来防止伪造。

哈希函数官方用于消息完整性验证,是数据签名的核心技术。

哈希函数的常用算法有:MD(消息摘要算法)、SHA(安全散列算法)及Mac(消息认证码算法)

算法 输出长度(位) 输出长度(字节)
SM3 256 bits 32 bytes
MD5 128 bits 16 bytes
SHA-1 160 bits 20 bytes
RipeMD-160 160 bits 20 bytes
SHA-256 256 bits 32 bytes
SHA-512 512 bits 64 bytes

举个栗子

Java标准库提供了常用的哈希算法,并且有一套统一的接口。我们以MD5算法为例,看看如何对输入计算哈希:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Test
    public void run2(){
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            // 反复调用update输入数据:
            messageDigest.update("Hello".getBytes(StandardCharsets.UTF_8));
            messageDigest.update("World".getBytes(StandardCharsets.UTF_8));
            byte[] result = messageDigest.digest();
            System.out.println(new BigInteger(1, result).toString(16));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
  }

运算结果:

68e109f0f40ca72a15e05cc22786f8e6


哈希算法的用途

因为相同的输入永远会得到相同的输出,因此,如果输入被修改了,得到的输出就会不同。

文件防篡改

我们在网站上下载软件的时候,经常看到下载页显示的哈希:

file-md5

如何判断下载到本地的软件是原始的、未经篡改的文件?我们只需要自己计算一下本地文件的哈希值,再与官网公开的哈希值对比,如果相同,说明文件下载正确,否则,说明文件已被篡改。

存储用户口令

哈希算法的另一个重要用途是存储用户口令。如果直接将用户的原始口令存放到数据库中,会产生极大的安全风险:

  • 数据库管理员能够看到用户明文口令;
  • 数据库数据一旦泄漏,黑客即可获取用户明文口令。

不存储用户的原始口令,那么如何对用户进行认证?

方法是存储用户口令的哈希,例如:MD5

在用户输入原始口令后,系统计算用户输入的原始口令的MD5并与数据库存储的MD5对比,如果一致,说明口令正确,否则,口令错误。

因此,数据库存储用户名和口令的表内容应该像下面这样:

username password
bob f30aa7a662c728b7407c54ae6bfd27d1
alice 25d55ad283aa400af464c76d713c07ad
tim bed128365216c019988915ed3add75fb

这样一来,数据库管理员看不到用户的原始口令。即使数据库泄漏,黑客也无法拿到用户的原始口令。想要拿到用户的原始口令,必须用暴力穷举的方法,一个口令一个口令地试,直到某个口令计算的MD5恰好等于指定值。

使用哈希口令时,还要注意防止彩虹表攻击

什么是彩虹表呢?上面讲到了,如果只拿到MD5,从MD5反推明文口令,只能使用暴力穷举的方法。

然而黑客并不笨,暴力穷举会消耗大量的算力和时间。但是,如果有一个预先计算好的常用口令和它们的MD5的对照表:

常用口令 MD5
hello123 f30aa7a662c728b7407c54ae6bfd27d1
12345678 25d55ad283aa400af464c76d713c07ad
passw0rd bed128365216c019988915ed3add75fb
19700101 570da6d5277a646f6552b8832012f5dc
20201231 6879c0ae9117b50074ce0a0d4c843060

这个表就是彩虹表

如果用户使用了常用口令,黑客从MD5一下就能反查到原始口令:

bob的MD5:f30aa7a662c728b7407c54ae6bfd27d1,原始口令:hello123

alice的MD5:25d55ad283aa400af464c76d713c07ad,原始口令:12345678

tim的MD5:bed128365216c019988915ed3add75fb,原始口令:passw0rd

这就是为什么不要使用常用密码,以及不要使用生日作为密码的原因。

如何抵御彩虹表攻击

即使用户使用了常用口令,我们也可以采取措施来抵御彩虹表攻击:方法是对每个口令额外添加随机数,这个方法称之为加盐(salt)(如果盐值是随机还需要在数据库里存盐值)

1
digest = 摘要算法(salt+inputPassword)

经过加盐处理的数据库表,内容如下:

username salt password
bob H1r0a a5022319ff4c56955e22a74abcc2c210
alice 7$p2w e5de688c99e961ed6e560b972dab8b6a
tim z5Sk9 1eee304b92dc0d105904e7ab58fd2f64

加盐的目的在于使黑客的彩虹表失效,即使用户使用常用口令,也无法从MD5反推原始口令。

SHA-1

SHA-1也是一种哈希算法,它的输出是160 bits,即20字节。SHA-1是由美国国家安全局开发的,SHA算法实际上是一个系列,包括SHA-0(已废弃)、SHA-1、SHA-256、SHA-512等。