信息加密技术经过多年的发展,由一些基本算法组合形成了许多成熟的应用,如数字签名,安全证书,HTTPS,以及最近大热的数字加密货币,区块链等。这些看似种类繁多的应用,其实都由三类基本的算法通过不同的组合来实现,这三类算法分别是: 数据摘要(在很多场合也被称作哈希运算),对称加密,非对称加密。本文抛开这三类算法在不同实现方案中的差异,抽象出各类算法的共性,提纲挈领地描绘出三类算法在加密体系中的应用场景,让开发者能在短时间内对信息加密体系有个全局的认识,并能将这三类算法应用在实际的需求场景中,希望本文能成为设计信息加密应用的简要手册。
  阅读建议:每类算法都以伪代码给出函数声明,不代表特定算法, 比如函数digest代表了在实际使用中的MD5, SHA1, RIPEMD160等实现了数据摘要功能的算法,读者在阅读过程中不要急于去考虑这些函数的实现,而应该去领会算法的特点和完成的功能。这些算法几乎在所有主流的编程语言库中都有具体实现,实在有很少的场合需要自己再去实现。对于有研究技术细节需求的读者,可以在阅读完本文后,对信息加密体系这棵树有了全局的了解,再按图索骥地去研究树叶.
  下面将分别介绍这三类算法各自的特性和应用, 然后再对将这些算法组合起来的应用进行介绍.
  

1. 数据摘要

1.1. 函数声明:

byte[] digest(byte[] data)

1.2. 函数特性:

  1.2.1. 无论data的长度为多少,digest返回某一定长数据,通常为几十个字节
  1.2.2. 若digest(data1) != digest(data2),则data1 != data2;若digest(data1) = digest(data2)则可以认为有很高的概率data1 = data2,在特定的情况下,对某些算法来讲,该判断出错的概率不到1/128^2。
  1.2.3. 运算不可逆,即知道digest的返回值,不能反推出data的值。顺便提一句,人们常提起的比特币挖矿其实就是指通过不断的尝试找出一个data,使得digest(data)的结果小于某一个表示难度的数,其基本代码如下:

while(digest(blockData + random()) > difficultyFactor); //random()在这里产生随机数

  可见即使为了得到满足某一条件的data值,也只能通过暴力尝试,而不能有其它快速的方法。
  1.2.4. digest函数相对于下面要谈的其它两类算法来说,运算速度非常快。注意是相对的快,因为即便如此挖矿也是个很费时的运算,呵呵。

1.3. 应用举例:

1.3.1. 大数据检索比较

  假设要做这么一个文件上传系统,要求上传文件前,先检测服务器上是否已经有同样的文件存在,如果已经存在,则不再上传。如果没有digest算法,只有把整个文件上传到服务器再进行比较,这无疑非常费时。如果我们每次上传文件,都对文件做一次摘要运算(特性1.2.4:摘要运算速度非常快,相对于文件上传的耗时来说忽略不计),并将摘要值保存起来;那么下次上传文件前我们对将要上传的文件进行一次摘要运算,只是把摘要值提交到服务器(特性1.2.1:摘要值一般只有几十个字节), 服务器在之前保存的摘要值列表中查找客户端新提交上来的摘要值,如果找到了(特性1.2.2),则告诉客户端不用再上传了。这就是很多云盘使用的秒传技术,很多上G的文件,只要几秒钟就上传成功了,原因就在于其他人在你之前已经上传过同样的文件。

1.3.2. 防数据损坏

  还是上传文件的例子,客户端上传前先把本地文件的摘要值传给服务器,服务器在接收到完整的文件后,用同样的摘要算法,计算接收到的文件的摘要值,和客户端上传的摘要值进行比较,如果相同则可以认为文件接收完整;反之则认为文件在传输过程中损坏(特性1.2.2)。

1.3.3. 防数据纂改

  设计一个简单的充值磁条卡,所有磁卡机都能读磁卡中的数据,但只有授权的磁卡机才能合法地写入数据。其数据写入的过程如下图所示:
图片描述
  
  写入磁卡的数据分为两部分,一部分是明文数据;一部分是摘要数据。这里的摘要数据有个术语叫签名。签名的生成过程很简单,就是把密码和明文数据拼接在一起作为digest函数的输入,digest的输出即为签名。授权的磁卡机读取磁条数据时先要验证签名,即把磁条的明文数据读入内存,然后和密码按照写入时同样的过程生成一个签名,把该签名和磁条上记录的签名做对比,如果签名一样则认为数据没有被篡改,反之则认为已经被篡改。磁条上任何一个字节的修改,都不能通过签名验证过程。因为非授权磁卡机在不知道密码的情况下,很难生成一个合法的签名,虽然能读取签名,但也不能根据签名推导出密码(特性1.2.3).
  注意:在实际生产环境中,大部分情况下,签名是在服务器端进行。为了加强安全性,产生签名时还会加入一个随机数,随机数和明文一样写入磁条,同时参加签名验证过程。同时也还可以进行多遍摘要运算。这些措施都是为了进一步增加通过试探伪造签名的难度。
  

2. 对称加密

2.1. 函数声明:

byte [] symEncrypt(byte[] plainData,byte[] password);
byte [] symDecrypt(byte[] cipherData,byte[] password);

2.2. 函数特性:

    2.2.1.  加密和解密必须使用同样的密匙。
    2.2.2.  相对于非对称加密速度快。

2.3. 应用举例:

2.3.1. 文件加密解密

  不再赘述。
  

3.非对称加密

3.1函数声明:

class KeyPair  //密钥对
{
    byte [] privateKey  //私钥
    byte [] publicKey;  //公钥
};
KeyPair generateKeyPair();//用于产生一个密钥对
byte[] asyEncrypt(byte[] plainData,byte[] publicKey);   //用公钥对数据加密
byte[] asyDecrypt(byte[] cipherData,byte[] privateKey); //用私钥对公钥加密的数据解密

3.2. 函数特性:

  3.2.1. 用公钥加密的数据,只能由对应的私钥解密。即他们都在由generateKeyPair产生的一个密钥对(KeyPair)里面。有时也用私钥加密,公钥解密。
  3.2.2. 知道一个KeyPair的publicKey,以现在计算机的算力在短期内无法计算出它对应的privateKey。这个短期指的是至少几十年。
  3.2.3. 非对称加密通常比对称加密慢很多。所以它通常只用来对密钥或摘要加解密,而不会用在长数据上。

3.3. 应用举例:

  设计一个文件上传系统,要求所有终端上传的数据都经过加密,即使数据经过被窃听的网络,窃听者也无法获知数据内容。如果使用对称加密会发生什么:
  1. 所有终端和服务器共享一个秘钥,任何一个终端泄露了秘钥,数据传输将不再安全。
  2. 为每个终端分配一个秘钥,服务器保存每个终端的秘钥,从不同终端来的数据使用对应终端的秘钥来解密。这可以解决一个终端泄密,导致整个系统泄密的问题。但同样存在秘钥泄露的风险。
  这时候使用非对称加密就解决了上面的所有问题。首先用generateKeyPair生成一个密钥对(KeyPair),将KeyPair的publicKey分配给所有终端,将KeyPair的privateKey安全保存在服务器。终端上传数据时使用asyEncrypt(byte[] plainData,byte[] publicKey)加密数据,服务器端用asyDecrypt(byte[] cipherData,byte[] privateKey)解密数据。因为公钥加密的数据只能通过对应的私钥来解密(特性3.2.1),所以数据即使在传播途中被窃听也是安全的,又因为特性3.2.2,即使所有人知道公钥,私钥也是安全的。
  上述设计方案只是为了演示非对称加密方案的使用,在现实中一般不会这么做,因为特性3.2.3,这样会存在性能问题。所以一般会与对称加密组合使用,我们接着就要介绍这种用法。
  

4. 三类算法的组合应用:

4.1. 加密通信

  继续上文的例子,由于非对称加密的速度非常慢,所以我们考虑只用它来加密数据量小的一个临时密钥,用这个临时密钥来使用对称加密的方法加密实际的通信数据。终端向服务器发起通信的流程大致如下:
  1.终端生成一个随机密码:symPassword.
  2.终端将这个随机密码使用非对称加密加密,加密使用服务器对应的公钥(publicKey)。asyEncrypt(symPassword,publicKey),并将这个加密后的结果发到服务器端。
  3.服务器端使用私钥进行非对称解密asyDecrypt(encryptedSymPassword,privateKey),解密得到symPassword.
  4.终端使用symEncrypt(data,symPassword)对data进行加密,并将加密结果发送到服务器。
  5.服务器使用symDecrypt(encryptedData,symPassword)对数据进行解密。
  这个方案使用临时的对称加密密码来进行数据的加密传输,由于临时密码可以在每次通信建立时重新生成,所以不会有密码泄露的风险。
  

4.2. 数字证书

  上文的加密通信还有个问题没有解决,就是服务器的公钥如何发放到众多的终端。如果只是小范围内使用,比如一个办公室内部,可以用U盘将服务器公钥拷贝到每个终端计算机上。但如果是一个公共网站,希望和全世界的所有用户的通信都是安全的,该怎么发放服务器的公钥呢?这将就要用到数字证书了。假定我们要为a.com的服务器发放一个证书,如下图所示:
图片描述
  
  终端访问a.com时,先下载上图右边所示的数字证书。一般证书是从a.com下载,但从其它地方下载也是安全的,因为数字证书要验证后才会使用。终端得到证书后,就开始验证它,验证步骤如下:
  1. 把上图中证书下部的明文内容部分进行一次digest运算。
  2. 根据明文内容中证书签发者的信息,在本机查找并验证证书签发者的公钥。(稍后详述这一过程)
  3. 用证书签发者的公钥,解密证书上部的数字签名部分,使用asyDecrypt.
  4. 将第3步的asyDecrypt结果和第1步的digest结果进行比较,如果一样,则认为证书合法;反之则说明证书非法或已损坏。
  5. 如果证书合法,就可以把明文内容中a.com的公钥拿来使用了。
  前面步骤的第2步具体怎么操作的?注意到我们其实是要检验证书签发者公钥的合法性,我们就需要一个关于证书签发者公钥的证书:
图片描述
  
  就这样一环扣一环,形成了一个称为证书链的东西。这样的循环什么时候结束呢?一直找到一个值得信任的证书或根证书。根证书是证书拥有者自己给自己签发的证书。根证书怎么验证合法性?答案是不能,我们选择信任它。我们为什么信任它,因为它是和操作系统或浏览器一起发布的。
  在实际应用中,证书还有个过期的概念。这也是由非对称加密的特性3.2.2.决定的,为了防止经过较长时间的计算由某个签发者的公钥算出他对应的私钥。
  

4.3.HTTPS

  这实际上是在HTTP协议的下层增加了由4.1和4.2组合成的加密通信协议。先有4.2得到网站的公钥,再用4.1的方法传输HTTP数据。具体的实现不在本文讨论的范围。
  

4.4. 加密货币与区块链

  不外乎也是本文讨论的三类算法的组合应用,将在接下来的文章讨论这个话题。