非对称加密算法 - ECDH
背景
之前的章节讲到了对称加密算法AES,发送方和接收方需要使用相同的密钥进行通讯,但是发送方怎么将密钥安全的发送给接收方?这是一个问题。
密钥分配问题
对称加密算法中,为了将密钥安全的发送给对端,通常有以下四种方法:
-
事先共享密钥
事先双方约定好密钥的数值,或者使用其他安全的手段例如派特工押送等,将密钥安全的送到对端,这种方式虽然简单,但是局限性很大,另外通讯量增大以后,密钥个数也会相应增加,这在高数量级的设备通讯上并不现实。 -
密钥分配中心
为了解决事先共享密钥的密钥增多的问题,也可以使用密钥分配中心来解决。每次加密的密钥由密钥中心进行分配,每个人只要和密钥中心事先共享密钥就可以了。虽然这个方法解决了密钥增多的问题,但是密钥中心存储和记录了所有的密钥,一旦它出现故障或者被攻击破坏,那么所有的加密都会瘫痪,这也是集中式管理的缺点。 -
DH 密钥交换
为了解决集中式管理的缺点,有人想出了 Diffie-Hellman 密钥交换的方法。在 Diffie-Hellman 密钥交换中,加密通信双方需要交换一些信息,而这个过程是可以防止窃听者攻击的。根据交换的信息,双方各自生成相同的密钥。而窃听者无法生成相同的密钥。 将DH算法与ECC公钥算法结合形成ECDH算法,这是一种解决密钥交换问题的一种很常见的解决方案,这个下面也会详细讲解。 -
非对称加密
非对称加密双方分别有一个公钥和一个私钥。公钥可以互相发给对方,用来加密数据,私钥则由自己保存。公钥甚至在网上传播,被窃听者拿到也没有关系,由于没有私钥,他也无法解开密文。私钥只要掌握在接收者手上就不会导致密文被窃听,这样就解决了密钥传输的问题。
虽然非对称加密解决了密钥配送的问题,但是非对称密钥仍然存在一些问题,比如:
- 非对称加密处理速度较慢,通常不到对称加密速度的十分之一
- 无法防止中间人攻击 ,这个通常需要结合签名和证书来解决。
非对称加密算法
非对称加密算法可以说是密码学历史上最伟大的发明,每个公钥算法的底层原理都是非常巧妙的。常用的非对称加密算法有RSA、ECC算法等, 在物联网领域常用的是ECC加密算法。
非对称加密算法的本质是什么?
我们知道非对称加密可以防止窃听者攻击,也就是说双方进行公钥分享、协商、计算的时候就算是旁边有窃听者,将双方的数据都获取到,也无法计算出双方协商的密钥。 对于初学者可能很难理解这件事情,双方的数据都可以窃听到,为什么通讯双方可以计算出密钥,而窃听者却无法计算出呢?
-
对于RSA算法是基于大素数相乘后结果很容易计算,但是通过结果反推出两个素数却很困难,目前只能使用穷举法。
-
ECC算法是基于椭圆曲线的离散对数问题上的密码体制,给定椭圆曲线上的一个点G,并选取一个整数k,求解K=kG很容易,但反过来计算确很困难。
所以非对称加密的本质就是利用正向运算比较简单,但可逆运算比较困难的单向函数。 而防止窃听者攻击也并不是窃听者完全无法破解出密钥,而是因为窃听者只能使用穷举法破解,可能破解的时间是正向运算的几万倍,就近似的认为该算法是可以防止窃听者攻击的。
ECDH 算法简介
由于 ECC 密钥具有很短的长度,所以运算速度比较快。到目前为止,对于 ECC 进行逆操作还是很难的,数学上证明不可破解,ECC 算法的优势就是性能和安全性高。实际应用可以结合其他的公开密钥算法形成更快、更安全的公开密钥算法,比如结合 DH 密钥形成 ECDH 密钥协商算法,结合数字签名 DSA 算法组成 ECDSA 数字签名算法。
ECDH算法常常用来进行密钥的协商,协商好密钥后,用来解决上面的密钥分配问题,将对称加密的密钥安全的传到对端设备。
算法 | 加密/解密 | 数字签名 | 密钥交换 |
---|---|---|---|
RSA | ✅ | ✅ | ✅ |
Diffie-Hellman | ❌ | ❌ | ✅ |
DSS | ❌ | ✅ | ❌ |
椭圆曲线 ECC | ✅ | ✅ | ✅ |
如上表,椭圆曲线可以用于 3 个方面:
- 基于椭圆曲线的公钥密码
- 基于椭圆曲线的数字签名
- 基于椭圆曲线的密钥交换
ECDH 算法原理
椭圆曲线源自于求椭圆弧长的椭圆积分的反函数。一般情况下,椭圆曲线可以用下面的方程式来表示,其中 a,b,c,d 为系数:
E: y^2 = ax^3 + bx^2 + cx+ d
举个例子,
上面是一条椭圆曲线,但是它的样子也并不是一个椭圆。
1. 椭圆曲线上的运算
加法运算:椭圆曲线上的两点 A 和 B,构成的直线与椭圆曲线的交点,与 X 轴的对称点,定义为 A+B。如下图:
当然也存在两个点重合的情况,这种情况下,就相当于寻找 2 倍点的问题。在椭圆曲线上的一点 A,做一条切线,与椭圆曲线的另外一交点,相对于 X 轴的对称点成为 2 倍点。这种运算成为 2 倍计算,如下图:
点 A 相对于 X 轴的对称位置的点成为 -A。这个运算成为椭圆曲线的正负取反运算。如下图:
2. 椭圆曲线加密实质
椭圆曲线加密的实质是利用椭圆曲线上离散对数问题(Elliptic Curve Discrete Logarithm Problem, ECDLP),实质是已知点 xG 求数 x 的问题。
已知:
椭圆曲线 E
椭圆曲线 E 上的一点 G (基点)
椭圆曲线 E 上的一点 xG (G 的 x 倍)求:
数 x
椭圆曲线在实数域上是连续的,曲线也是一条光滑的曲线。假设椭圆曲线为 E2:y^2 = x^3 + x + 1,如下图:
如果位于有限域 F23 上,那么 E2:y^2 ≡ x^3 + x + 1 (mod 23)。此时椭圆曲线就不是一个连续的曲线了,而是一堆离散的点。如下图:
上图中每个点的 y 坐标对 23 求余,都等于 x^3 + x + 1 对 23 求余。如果我们把 E2 上的点 G=(0,1) 作为基点,那么按照椭圆曲线的计算规则计算 2G、3G、4G、5G、……。
这就是“已知 G 和 xG 求 x 的问题”。这个问题在 p 相当大的时候,很难求解。椭圆曲线无法被破解的原因是:求解椭圆曲线上的离散对数问题是非常困难的。
但是椭圆曲线加密算法并非在实数域 R 上,而是在有限域 F§ 上。有限域 F§ 指的是对于某个给定的质数 p,由 0,1,2,……,p-1 共 p 个元素所组成的整数集合中定义的加减乘除运算。
椭圆曲线加密算法除了可以使用特征数为质数 p 的素域 GF§,椭圆曲线还可以使用特征数为 2^m 的扩张域 GF(2^m)。
3. 椭圆曲线 Diffie-Hellman 密钥交换 (ECDH)
非椭圆曲线的 Diffie-Hellman 密钥交换利用的是:
以 p 为模,已知 G 和 G^x mod p 求 x 的复杂度(有限域上的离散对数问题)
椭圆曲线的 Diffie-Hellman 密钥交换利用的是:
在椭圆曲线上,已知 G 和 xG 求 x 的复杂度(椭圆曲线上的离散对数问题)
DH 算法和 RSA 算法最大的不同在于:
- DH 算法在进行密钥协商的过程中,通信双方的任何一方无法独自计算出一个会话密钥,通信双方各自保留一部分关键信息,再将另外一部分信息告诉对方,双方有了全部信息才能共同计算出全部的会话密钥。
- RSA 算法在传输会话密钥的时候,会话密钥完全由客户端生成和控制,并没有服务端的参与,准确的来说应该叫 RSA 密钥传输。
举个例子来说明 ECDH 算法是如何生成共享密钥的。
- Alice 向 Bob 发送点 G,点 G 被窃听了也没有关系。
- Alice 生成随机数 a。这个数只有 Alice 自己一个人知道。也没有必要告诉 Bob。更加不能让第三者知道,这个 a 称为 Alice 的私钥。
- Bob 生成随机数 b。这个数只有 Bob 自己一个人知道。也没有必要告诉 Alice。更加不能让第三者知道,这个 b 称为 Bob 的私钥。
- Alice 向 Bob 发送点 aG。点 aG 被窃听了也没有关系。它是 Alice 的公钥。
- Bob 向 Alice 发送点 bG。点 bG 被窃听了也没有关系。它是 Bob 的公钥。
- Alice 拿到 Bob 发过来的 bG,开始计算其在椭圆曲线上 a 倍的点,即 a(bG) = abG,它就是 Alice 和 Bob 的共享密钥。
- Bob 拿到 Alice 发过来的点 aG 开始计算其在椭圆曲线上 b 倍的点,即 b(aG) = baG = abG,它就是 Alice 和 Bob 的共享密钥。
窃听者一共可以拿到 3 个有效信息:G、aG、bG。但是由于“已知 G 和 xG 求 x 非常难”,导致已知 G 和 aG 无法求解出 a,已知 G 和 bG 无法求解出 b。所以最终也就无法求解出私钥 abG。
严格的来说,椭圆曲线上的离散对数问题的复杂度只能证明 “已知 G,aG,bG 难以求出 a,b”,但无法证明“已知 G,aG,bG 难以求出 abG”,后者需要另外证明。具体证明这里省略。
如果采用静态的 DH 算法和 ECC 结合就是 ECDH 算法。这种方式每次都使用的相同的 G 基点,它的优点在于可以避免每次在初始化连接时服务器频繁生成 G。这个过程比较消耗 CPU。但是它带来的缺点是,一旦随机数 a、b 被泄露了,那么在这之前的所有会话都将会被解密。
为了解决这个问题,于是出现了 DHE 算法(Diffie-Hellman Ephemeral ,短暂临时的 DH 算法),结合 ECC 后形成了 ECDHE 算法。它可以保证每次通信使用的共享密钥都是不同的,DH 密钥对仅仅保存在内存中,不像 RSA 的私钥保存在磁盘上,攻击者即使从内存中破解了私钥,也仅仅影响本次通信,所以无需担心在此之前的通信内容会被解密,这样的特征成为前向安全性(Forward Secrecy,FS)或者完全前向安全性(Perfect Forward Secrecy,PFS)。更安全的是,协商出会话密钥后,a 和 b 两个私钥可以丢弃,进一步提升了安全性,在有限的时间、有效的空间生成了密钥对。在 TLS 握手中使用的 ECDHE_ECDSA 和 ECDHE_RSA 密钥交换算法。
混合加密系统
通过使用对称加密可以解决信息安全的问题,但是使用对称加密,必须要解决密钥配送的问题。
使用公钥密码可以解决密钥配送的问题,但是公钥密码又暴露了 2 个新的问题。
- 公钥密码的处理速度远远低于对称密码
- 公钥密码难以抵御中间人攻击
第一点缺点可以用这一章节讲的混合加密系统来解决。第二点缺点需要用到下一篇文章中的认证相关的算法来解决。
混合密码系统 (hybrid crytosystem) 是将对称密码和公钥密码的优势相结合的方法。用快速的对称密码对明文进行加密,用公钥密码对对称密码的密钥进行加密。由于对称密钥一般都比消息本身要短,这样公钥密码速度慢的问题也可以忽略。
- 用对称密码对消息明文进行加密
- 用伪随机生成器生成对称密码加密中使用的会话密钥
- 用公钥密码加密会话密钥
- 从混合密码系统外部赋予公钥密码加密时使用的密钥
会话密钥 (session key) 是指为本次通信而生成的临时密钥。它一般都是通过伪随机数生成器产生的。伪随机生成器所产生的会话密钥同时也会作为对称密码的密钥使用。会话密钥是对称密码的密钥,同时也是公钥密码的明文。
下图是混合密码系统中的加密流程:
值得说明的是,公钥密码的强度应该高于对称密码。因为对称密码会话密钥被破译只影响本地通信内容,但是公钥密钥被破译就会影响到过去所有通信内容,以及未来所有通信内容。
下图是混合密码系统中的解密流程:
OpenSSL 中的 ECC
1. DH 密钥协商
与 RSA 密钥对生成方式不一样,DH 密钥对生成方式分为 2 步,第一步先生成参数文件,第二步再根据参数文件生成密钥对,同一个参数文件可以生成无数多且不重复的密钥对。
第一步,生成 DH 密钥对
// 生成 2048 比特的参数文件
$ openssl dhparam -out dhparam.pem -2 2048
$ openssl dhparam -in dhparam.pem -noout -C
输出
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
......................+..........................................................................+..............................................................................................................................................................................................+.......................................................+
#ifndef HEADER_DH_H
# include <openssl/dh.h>
#endif
DH *get_dh2048()
{
static unsigned char dhp_2048[] = {
0xBD, 0x70, 0xDB, 0x2E, 0xF9, 0x0B, 0x89, 0x37, 0xC4, 0x31,
0x93, 0x48, 0x47, 0xEF, 0xD5, 0xEA, 0x7E, 0xBC, 0xDA, 0xC8,
0x14, 0x0E, 0x82, 0xDD, 0xF6, 0xC6, 0x07, 0x2A, 0xD4, 0x97,
0xC3, 0x02, 0xA1, 0x9B, 0x02, 0xCB, 0xE4, 0xC0, 0xB9, 0x33,
0xD1, 0xBB, 0x69, 0xF0, 0xBA, 0x8C, 0x7A, 0x57, 0x1F, 0xDF,
0xD3, 0xB5, 0x2F, 0x87, 0x1E, 0xA8, 0x35, 0xE4, 0xC0, 0x94,
0xED, 0x20, 0x04, 0x26, 0x58, 0x50, 0x27, 0xFC, 0xF6, 0xE1,
0xBE, 0xC7, 0xB8, 0x7A, 0x14, 0xC1, 0x08, 0x16, 0x06, 0xC6,
0xB8, 0x09, 0xDC, 0x34, 0xEA, 0xA0, 0xD1, 0x3E, 0x88, 0xBD,
0xB3, 0xBB, 0x05, 0xFE, 0x4D, 0xCB, 0x62, 0x05, 0x9A, 0xC7,
0x00, 0xA2, 0x0B, 0x73, 0xAD, 0xDD, 0x39, 0x18, 0x9A, 0xD8,
0x2A, 0x95, 0xCE, 0xF4, 0x10, 0x6A, 0xB2, 0x5C, 0x0F, 0x9E,
0x99, 0xE5, 0xE6, 0x0D, 0x6C, 0x19, 0xF5, 0xF5, 0xDC, 0x07,
0x2D, 0xF0, 0xDE, 0xB5, 0x58, 0xEC, 0x35, 0x33, 0xEF, 0x65,
0x70, 0xC3, 0x8C, 0xBF, 0x14, 0x40, 0x4C, 0xC3, 0x47, 0x77,
0xE0, 0x5F, 0xF6, 0x61, 0x5F, 0x49, 0x35, 0xCC, 0x39, 0x75,
0x8E, 0x31, 0xA3, 0x99, 0x43, 0x61, 0xD7, 0xE3, 0xB7, 0xB8,
0x0F, 0x79, 0xF3, 0x66, 0x50, 0x95, 0x0D, 0x95, 0xB2, 0x5F,
0x1B, 0x2C, 0x9D, 0x64, 0xE0, 0x54, 0xCB, 0x27, 0xE9, 0x4A,
0x23, 0x96, 0xA0, 0x7E, 0x55, 0xD4, 0xA9, 0x93, 0x43,