前言:在那个https还没有普及的黑暗年代,混沌的大地充斥着各种危险的明文传输(抱歉,中二了)。相信很多软件开发人员都遇到过通信加密的问题,早期的一些做法,只是简单的将对称加密的密钥写死在客户端中,通过对称加密的方式进行数据传输加密。然而这样的方案问题也十分明显,一旦客户端被反编译,密钥泄露,那么几乎所有客户端都暴露在了风险中。
既然不适合将对称加密的密钥写死在客户端,那是否可以动态生成密钥并交换呢?
答案当然是肯定的,今天我们来介绍一种密钥交换协议:
-
DH密钥交换协议
DH密钥交换是1976年由Diffie(迪菲)和Hellman(赫尔曼)共同发明的一种算法。使用这种算法,通信双方仅通过交换一些可以公开的信息就能够生成出共享的密码数字,而这一密码数字就可以被用作对称密码的密钥。
这套算法的核心,就是一个数学公式:
(G^A mod P)^B mod P = G^(AB) mod P;
先来梳理几个数学概念:
- 原根:如果a是素数p的一个原根,那么数值: a mod p,a^2 mod p,…,a^(p-1) mod p 是各不相同的整数,且以某种排列方式组成了从1到p-1的所有整数。
- 离散对数:如果对于一个整数 b 和素数 p 的一个原根 a,可以找到一个唯一的指数 i,使得: b = (a^i) mod p,其中0≦i ≦p-1,那么指数 i 称为 b 的以 a 为基数的模p的离散对数。
假设Tom和Jerry想要交换一个密钥,取素数 P 和整数 G,G 是 P 的一个原根,公开 G 和 P。
Tom选择随机数A (A<P),A即为Tom的私钥(打死别泄露),并计算X = G^A mod P。
Jerry选择随机数B (B<P),B即为Jerry的私钥(同样严格保密),并计算Y = G^B mod P。
Tom将计算结果 X 传给Jerry,Jerry对 X 再进行计算:
Pass = X^B mod P == (G^A mod P)^B mod P == G^(AB) mod P;
同样,Tom在拿到Jerry的计算结果Y,也做同样的计算:
Pass = Y^A mod P == (G^B mod P)^A mod P == G^(BA) mod P;
到这里,各位看官应该就能明白了,双方在交换各自的计算结果后,再次计算的结果是一致的,而这个结果Pass,就是最后协商出的密钥。
网上转一张图来梳理下整个流程:
-
安全性
对于符合特定条件的 P 和 G ,计算 G^A mod P很容易,只是一个简单的数学运算。但是,想要通过 P 、G、G^A mod P,反向推算 A 却很困难,这就好比小时候我们去拆家里的闹钟,拆成零件很容易,但是想再把它装起来....
只要Tom和Jerry使用足够大的A, B 以及P,那么暴力破解会变得非常困难,假设 P 是一个至少 300 位的质数,并且 A 和 B 至少有100位长, 那么即使使用全人类所有的计算资源和当今最好的算法也不可能从A、P 和 G^(A*B) mod P 中计算出 A*B。
实际上,不管是RSA、离散对数加密还是椭圆曲线加密,公钥加密算法都是依赖于某个正向计算很简单(比如多项式时间复杂度),而逆向计算很难(比如指数时间复杂度)的数学难题。对于RSA,这个问题是大整数因子分解问题;对于离散对数加密,是离散对数问题;对于椭圆曲线加密,则为椭圆曲线上的离散对数问题。
但是这并不意味着这个算法的绝对安全,实际上,DH算法只能保证密钥本身的安全,却无法验证双方的身份,对于中间人攻击这样的手段,是完全无能为力的(这部分内容,大家有兴趣的话之后会再开一篇来详细讲一下)。
下一篇,给大家讲讲另一种更加安全的密钥交换方式,DH加密算法的进化版:椭圆曲线加密ECDH。