Skip to content

门泊吴船亦已谋

加密算法详解

网络通信中我们很多时候会需要保证信息不被窃听或者不被篡改,于是就会需要对信息进行加密。

对称加密

通信双方都持有一个共同的密钥,双方收发消息都是使用这个密钥来解密加密。

有一个问题就是这个密钥一般需要某一方生成之后传给另一方,这个过程中一旦密钥泄露就危险了。 因此通常来说这个密钥的传输也需要使用加密技术进行保护,也就是后面要说的非对称加密。 当然不嫌麻烦你也可以用 U 盘拷。

另外还有一个问题。 在服务端跟客户端通信的场景下,客户端是有很多个的。 如果这些客户端都共享同一个密钥,会增加泄露的风险,一旦泄露就全部爆炸。 但是如果对于每个客户端都使用不同的密钥,服务端就要存储所有的密钥,会比较恶心。

不过对称加密的优势在于速度快,往往是使用时效较短的密钥进行短时间内的通讯。 比如说一方先随机生成一个临时密钥,然后用非对称加密把密钥传给另一方,这样既能提高加密传输的性能,又能保证安全性。

非对称加密

这个就稍微复杂一点。 简单说就是它有两个密钥:公钥和私钥。 公钥是公开的;私钥不公开,且只有一方持有。 使用公钥加密的数据只有私钥能解开;使用私钥加密的数据用公钥能解开。

于是在服务端跟多个客户端通信的场景下,就可以让服务端持有私钥,各个客户端使用公钥把数据进行加密后发给服务端,这个数据就只有服务端能够解出来。 如果服务端无法解析,说明服务端是伪造的。 于是我们就发现,客户端只要自己随机生成一个对称加密的密钥,用公钥加密后发给服务端,服务端用私钥解出来,然后双方只要使用这个生成的密钥就可以使用对称加密进行通信了。

另外说一下数字签名,这个数字签名实际上就是一段数据使用私钥加密得到的密文,然后用公钥能够解开。 为什么叫做签名,因为这个密文只有私钥的持有者才能够生成。 如果一段密文能够使用公钥进行解密,就能证明生成密文的一方是私钥的持有者。 由此一来,也能够保证这段数据不会被篡改,因为攻击者不知道私钥,因此无法正确的加密得到密文。

但是如果公钥本身也是伪造的怎么办,所以接下来就说一下数字证书。 数字证书包含了公钥、所选择的加密算法、有效期以及证书所有者相关的一些其他信息。 简单说就是证书可以当成公钥来看。 证书是由一些权威机构发布的,为了解决公钥伪造的问题,当我们需要获得一个服务器(比如说一个域名)的公钥时,就需要向这些机构查询这台服务器的公钥,也就是证书。 当然,为了避免机构也是伪造的,查询这个证书的过程也必须是要加密的。 因为它们是权威机构,所以主流浏览器基本都会默认安装这些机构的证书,也就是机构的公钥。 于是机构就用自己的私钥对需要查询的证书进行加密,这个操作实际上就是一种签名。 最后我们使用浏览器默认安装的证书就能解密出来,得到服务器的证书。 这一整个过程都是安全的。 除非机构本身就是内鬼。

那么为什么公钥加密的东西只有私钥能解呢? 实际上有很多巧妙的算法可以实现,这里介绍一下比较容易理解的 RSA。 RSA 算法利用的是质因数分解的特点:计算两个大质数的乘积以及其他相关的其他运算都很快,但是对它们的乘积做质因数分解很慢。 高精度乘法无非是多算几个位;但是质因数分解只能枚举,所以解密的时间远远大于加密的时间。 考虑到处理器运算速度会不断发展,这时我们只要使用更大的质数,就能让解密的时间永远保持在令人无法接受的范围内。 那么具体是怎么实现的呢? 假设有两个非常大的质数 p 和 q,它们的乘积 p * q = n。 接着求 n 的欧拉函数 φ(n) = φ(p) * φ(q) = (p - 1) * (q - 1)。 然后找一个跟 φ(n) 互质的数 e,比如说随便一个大质数。 使用扩展欧几里得求模 φ(n) 意义下 e 的逆元。 这个时候我们就可以把 n 和 e 作为公钥公布出去了。 仅仅根据 n 和 e 的话,攻击者是无法算出 d 的,因为要计算出 d 必须先计算 φ(n),要么枚举要么质因数分解,都是令人无法接受的。 假设需要加密的明文为 m。 使用公钥加密后得到的密文 c = m ^ e mod n。 使用私钥把 c 解密就能得到原文,m = c ^ d mod n。 然后也可以使用私钥按照这种方式进行加密,同样可以用公钥解密出来,也就是数字签名。 至于证明,有点长,这里就不写了。

信息摘要

这个就简单多了。 信息摘要就是把一坨数据经过一些运算得到一个结果,一般情况下无法根据这个结果反推出原本的数据。 它可用于验证数据的完整性,也可以用来实现身份认证。

验证完整性一个常见的例子就是下载文件之后的验证。 我们在一些网站下载文件,会发现它有提供这个文件的 SHA1 码或者 MD5 码,其实就是一些哈希值。 然后把文件下好之后,在本地计算一下它的哈希值,发现跟网站提供的完全一致,那就证明这个文件是完整的,没有被篡改过。 攻击者如果想在修改过文件之后,还要保持哈希值完全相同,基本上是不可能的。 其中文件为了提高下载速度使用明文传输,而哈希值则需要使用非对称加密中的私钥加密以确保不被篡改。 这其实也是数字签名的一种。

身份认证的话稍微复杂一点。 这里假设我们不使用上面提到的加密技术。 一个用户在登陆的时候需要输入他的用户名和密码。 如果把密码在网络中明文传输是非常危险的。 于是考虑使用哈希把密码加密后再发送给服务端,服务端查出该用户的密码,并算出哈希值,跟用户发过来的进行对比,如果相同的话即是认证成功。 但是我们注意到一个问题,哈希值是不变的,攻击者只要截获了这个哈希值,直接就能冒充这个用户了。 所以我们要特殊处理一下。 用户在登陆的时候,服务端先随机生成一段数据的发给用户。 然后用户先对自己的密码做哈希,再把这段数据混进去,再算一遍哈希值,最后发给服务端。 服务端也使用同样的方法做哈希,对比哈希值就可以认证用户的身份了。 这段随机的数据每次登录都要是不同的,并且短时间内就要让它失效,这样就能有效避免安全问题。 那么,为什么要先对密码做哈希再混入这段随机数据? 因为服务端不应当明文保存用户的密码,服务端上存储的只能是经过哈希之后的密码。 这样就算数据库被攻击,用户的密码也不会泄露,当然这种情况也是挺严重的。

哈希

常见的哈希有 MD、SHA 等,把原始数据经过一系列运算之后得到一个哈希值。 对于同一段数据,哈希值都是相同的。 而不同的数据,哈希值几乎是没有相同的。 如果数据仅仅产生了少量变化,优秀的哈希算法会计算出与变化前截然不同的哈希值。 一般来说,仅根据哈希值是没有办法还原原始的数据的。

但是有一种特殊的哈希,它需要在哈希值中保留原始数据的信息,就是 CRC。 CRC 是用于数据链路层中校验数据完整性的。 因为一个包在网络上传输由于物理硬件等问题可能会导致包中的数据产生错误。 于是就需要在原始数据后面再多发一个哈希值,路由器收到包之后对原始数据做个哈希,再跟后面的哈希值做个比较,就能验证这个包在传输之后有没有损坏。 那么有没有一种可能,这个包损坏的不是很严重,比如只有一两个位错了,我们能不能从它的哈希值中获取一些信息来修复这个数据呢? 这就是 CRC 的一个作用,另外直觉告诉我们,CRC 的位数越多,它就能包含更多的信息,就能修复越离谱的错误。

还有一个要说的就是加盐哈希。 其实就是上述的身份认证里每次登录时生成的那段随机数据。 这个东西就是“盐”。 加盐的目的跟上面说的一样,为了避免攻击者拿到固定的哈希就为所欲为。 每次验证的时候都加一个随机的盐,就算攻击者侥幸截获一次也没有意义。

MAC

最后是 MAC 算法,跟哈希很类似。 通信双方都需要持有一个共同的密钥。 然后依据这个密钥,对原文进行一些计算后就得到一个 MAC 值。 这种算法也可以依赖于哈希。 于是我们很容易就把它跟加盐哈希联想起来。 相同之处在于它们都可以验证数据的完整性。 但是因为密钥是不公开的,所以 MAC 算法还可以实现身份认证的功能。 另外,MAC 跟哈希在安全特性和模型上都是不同的,如果只是把原文跟密钥简单缝合起来再做哈希,并不能得到安全的 MAC 算法。 可以参考 Length extension attack