HTTPS 理论与实践

密码学基础

尽管密码学的漫长历史可以追溯到 Julius Caesar 时代,但现代密码技术基于的是过去 30 年所取得的进展。密码学非常复杂,所以我们只能初步了解密码学的基本方面,特别是因为这些东西正在今天的互联网上发挥着重要的作用。

我们假设 Alice 要向 Bob 发送一个消息:”Bob,I love you. Alice”,这被称为明文(plaintext,cleartext)。Alice 使用加密算法(encryption algorithm)加密她的消息,生成的加密消息被称为密文(cipherttext),该密文对第三者是无法理解的。

Alice 提供了一个密钥(key)Ka,它是一段数字或字符。加密算法以密钥和明文消息作为输入,生成的密文作为输出。类似地,Bob 将为解密算法(decryption algorithm)提供密钥 Kb,将密钥 Kb 和密文作为输入,输出初始的明文。

对称加密

对称密钥系统中,Alice 和 Bob 使用的密钥是相同的。比如非常古老和简单的凯撒密码(Caesar cipher),凯撒密码用于英语文本时,将明文报文中的每个字母用字母表后的第 k 个字母进行替换。例如,当偏移量是 3 的时候,所有的字母 A 将被替换成 D,B 变成 E,以此类推。然而,因为密钥值只有25个,因此破解也是非常简单的。

凯撒密码的一种改进方法是单码代替密码(monoalphabetic cipher),这种规则每种字母也有其对应的替换字符,不过不按照偏移量进行计算。但是,如果对明文语言进行统计分析,比如在英语中字母e和字母t出现的频率最高,还可知常见的字母组合(in,it,the)。比如,中间人 Trudy 是 Bob 的妻子,怀疑 Bob 和 Alice 有暧昧关系,则他可能猜想 bob 和 alice 可能出现在消息中。这会使破解变得相对容易。

非对称加密

如果使用对称加密, Alice 如果想向 Bob 发送一条消息,还必须让 Bob 知道她的密钥才行,Alice 需要将密钥发送给 Bob,可是万一密钥也被中间人 Trudy 截取了呢?那 Alice 也可以和 Bob 会面,当面把密钥交给 Bob 啊。听起来确实是个好办法。可是如果 Alice 还想和 David, Steven, John 发消息呢?总不能和每个人都见一次面吧,那也太麻烦了。

从凯撒密码时代直到20世纪70年代的两千多年以来,加密通讯都需要通信双方共享一个密钥,但这样做的前提是双方需要事先通信,就像 Alice 和 Bob 一样。在网络世界中,通信各方可能从未见过面,通信双方该如何在没有预先商定共享密钥的情况下进行加密通信呢?

Diffie-Hellman 密钥交换

我们知道,混合两种颜色很容易,然而如果我们只有一个混合色,却很难还原两种原始颜色。这叫做单向函数

假设 Alice 和 Bob 在起始颜色上达成一致,比如黄色,然后双方各自混合他们的私有颜色,并将混合色发送给对方, Alice 和 Bob 再在得到的混合色基础上添加私有颜色,最后将得到一个只有双方知道的秘密颜色。注意,这里中间人 Trudy 在没有两个私有颜色之一的情况下,是无法确定这个秘密颜色的。

我们需要将这一理论运用到数字上,在1976年,Diffie 和 Hellman 论证了一个解决该问题的算法,这就是 Diffie-Hellman 密钥交换,这是个完全不同,极为优雅的安全通信算法。

  • Alice 和 Bob 协定使用 p=23 以及 g=5.。这通常在协议开始之前就规定好, gp 是公开的,并可以被所有的攻击者看到。
  • Alice 选择一个秘密整数 a 并且将g^{a} \bmod{p}发送给 Bob
  • Bob 选择一个秘密整数 b 并且将 g^{b} \bmod{p}发送给 Alice
  • Alice 计算\left ( g^{b} \right )^{a} \bmod{p}
  • Bob 计算\left ( g^{a} \right )^{b} \bmod{p}

    如果 p 是一个至少 300 位的质数,并且 ab 至少有100位长, 那么即使使用全人类所有的计算资源和当今最好的算法也不可能从 g , pga mod p 中计算出 a。这个问题就是著名的 离散对数 问题。

总结来说,非对称加密就是

1.乙方生成两把密钥(公钥和私钥)。公钥是公开的,任何人都可以获得,私钥则是保密的。

2.甲方获取乙方的公钥,然后用它对信息加密。

3.乙方得到加密后的信息,用私钥解密。

RSA 算法

1977年 ,三位数学家 Ron Rivest,Adi Shamir 和 Leonard Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字命名,叫做 RSA算法。从那时直到现在,RSA 算法一直是最广为使用的非对称加密算法。

互质

数论中,如果两个或两个以上的整数的最大公约数是 1,则称它们为互质

例如 8 与 10 的最大公约数是 2,不是 1,因此它们并不互质。
又例如 7, 10, 13 的最大公约数是 1,因此它们互质。

最大公因数可以通过辗转相除法得到。

1
2
3
4
5
function gcd(a, b)
if b = 0
return a
else
return gcd(b, a mod b)

例如 GCD(1071, 462) 的计算过程是:函数的第一次调用计算 GCD(462, 1071 mod 462) = GCD(462, 147);下一次调用计算 GCD(147, 462 mod 147) = GCD(147, 21),在接下来是 GCD(21, 147 mod 21) = GCD(21, 0) = 21。

欧拉函数

对正整数 n欧拉函数\varphi (n)是小于或等于 n 的正整数中与 n 互质的数的数目。

例如\varphi (8)=4 因为 1,3,5,7 均和 8 互质。

密钥生成的步骤

假设 Alice 和 Bob 要进行加密通信,她该怎么生成公钥和私钥呢?

1.随机选择两个不相等的质数 p和 q。
Alice 选择了 61 和 53。(实际应用中,这两个质数越大,就越难破解。)

2.计算 p 和 q 的乘积n。
Alice 就把 61 和 53 相乘。

1
n = 61 × 53 = 3233

n 的长度就是密钥长度。3233 写成二进制是 110010100001,一共有 12 位,所以这个密钥就是 12 位。实际应用中,RSA 密钥一般是 1024 位,重要场合则为 2048 位。

3.计算 n 的欧拉函数 φ(n)。
根据公式:

1
φ(n) = (p - 1)(q - 1)

爱丽丝算出 φ(3233) 等于 60 × 52,即 3120。

4.随机选择一个整数 e,条件是 1< e < φ(n),且 e 与 φ(n) 互质。
爱丽丝就在1到3120之间,随机选择了17。(实际应用中,常常选择 65537。)

5.计算 e 对于 φ(n) 的模反元素 d。
所谓 模反元素 就是指有一个整数 d,可以使得 ed 被 φ(n) 除的余数为1。

1
ed ≡ 1 (mod φ(n))

ed ≡ 1 (mod φ(n)) 意味着,ed 除以 φ(n) 余1,于是,ed - 1 就能被 φ(n) 整除。能被整除就意味着 ed - 1= kφ(n),k 是任意一个整数。参见 同余

1
ed - 1 = kφ(n)

于是,找到模反元素d,实质上就是对下面这个二元一次方程求解。

1
ex + φ(n)y = 1

已知 e=17, φ(n)=3120,

1
17x + 3120y = 1

这个方程可以用 扩展欧几里得算法 求解,Alice 算出一组整数解为 (x , y) = (2753 , -15),即 d = 2753。

6.将 n 和 e 封装成公钥,n 和 d 封装成私钥。
在爱丽丝的例子中,n = 3233,e = 17,d = 2753,所以公钥就是 (3233,17),私钥就是(3233, 2753)。

可靠性

有无可能在已知 n 和 e 的情况下,推导出 d ?

1.ed ≡ 1 (mod φ(n))。只有知道 e 和 φ(n),才能算出 d。

2.φ(n) = (p - 1)(q - 1)。只有知道 p 和 q,才能算出 φ(n)。

3.n = pq。只有将 n 因数分解,才能算出 p 和 q。

对极大整数做因数分解的难度决定了 RSA 算法的可靠性。今天只有短的 RSA 密钥才可能被强力方式解破。到目前为止,世界上还没有任何可靠的攻击 RSA 算法的方式。只要其密钥的长度足够长,用 RSA 加密的信息实际上是不能被解破的。

2009年12月12日,编号为 RSA-768(768 bits, 232 digits)数被成功分解。这一事件威胁了现通行的 1024-bit 密钥的安全性,普遍认为用户应尽快升级到 2048-bit 或以上。

RSA-768 表示如下:

1
2
3
4
5
6
7
8
9
123018668453011775513049495838496272077285356959533479219732245215172640050726
365751874520219978646938995647494277406384592519255732630345373154826850791702
6122142913461670429214311602221240479274737794080665351419597459856902143413
= 3347807169895689878604416984821269081770479498371376856891
2431388982883793878002287614711652531743087737814467999489
×
3674604366679959042824463379962795263227915816434308764267
6032283815739666511279233373417143396810270092798736308917
加密和解密

用公钥加密 (n,e)

假设 Bob 要向 Alice 发送加密信息 m,他就要用 Alice 的公钥 (n,e) 对 m 进行加密。这里需要注意,m 必须是整数(字符串可以取 ascii 值或 unicode 值),且 m 必须小于 n。

所谓加密,就是算出下式的 c:

1
m^e ≡ c (mod n)

爱丽丝的公钥是 (3233, 17),鲍勃的 m 假设是 65,那么可以算出下面的等式:

1
65^17 ≡ 2790 (mod 3233)

于是,c 等于 2790,鲍勃就把 2790 发给了爱丽丝。

用私钥解密(n,d)

爱丽丝拿到鲍勃发来的 2790 以后,就用自己的私钥 (3233, 2753) 进行解密。可以证明,下面的等式一定成立:

1
c^d ≡ m (mod n)

也就是说,c的d次方除以n的余数为m。现在,c等于2790,私钥是(3233, 2753),那么,爱丽丝算出

1
2790^2753 ≡ 65 (mod 3233)

因此,爱丽丝知道了鲍勃加密前的原文就是 65。

HTTPS

超文本传输安全协议(HTTPS)是一种通过计算机网络进行安全通信的传输协议。HTTPS经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。

SSL/TLS 握手过程

当访问一个 HTTPS 站点时,我们可以通过浏览器查看证书的内容。

接下来,我们用 WireShark 工具来分析下 HTTPS 的请求过程,以 https://haoduoyu.cc 为例,我们过滤 WireShark 的结果,可以看到一个连接的建立过程:

Client Hello

可以看到,我们使用 TLS1.2 协议,并提供了一个随机数,一个 Session ID(第一次连接时为 0),和一串浏览器支持的加密组合。

Server Hello

服务器收到 Client Hello 信息后,就给浏览器发送一个 Server Hello 的包。包括随机数,Session ID 和服务器选中的加密方式。

Certificate

接着,服务器会发送证书,这与浏览器中查看的证书相同,一个叫 haoduoyu.cc ,另一个叫 Let’s Encrypt Authority X3

Server Key Exchange 和 Client Key Exchange

上文提到,服务器选择的加密方式为:

Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)

这一串加密方式可以分为三部分

密钥交换 数据传输 检验算法
RSA RC4 MD5
DSA 3DES SHA256
Diffie-Hellman AES SHA384

服务器选中的密钥交换加密方式为 RSA,数据传输加密方式为 AES,检验数据是否合法的算法为 SHA256.

RSA 是用来验证身份然后交换密钥的,并不是用来加密数据的,因为它太长了,计算量太大。服务器发送给客户端的公钥为 65 个字节。

同样地,浏览器结合服务器发给它的随机密码 (Server Hello),生成它自己的主密钥,然后发送公钥发给服务器。

Change Cipher Spec

双方交换密钥后,浏览器给服务器发了一个明文的 Change Cipher Spec 的包和一个 Encrypted Handshake Message (Client Finish) ,告诉服务器我已经准备好了,可以开始传输数据了,同样地,服务器也会给浏览器发一个 Change Cipher Spec 的包和一个 Encrypted Handshake Message (Server Finish)

Application Data

到这里,双方已安全地协商出了同一份秘钥,所有的应用层数据都会用这个秘钥加密后再通过 TCP 进行可靠传输。

配置 HTTPS

下载 cerbot

1
2
3
$ git clone https://github.com/certbot/certbot
$ cd certbot
$ ./certbot-auto --help

生成免费证书

1
$ ./certbot-auto certonly --webroot --agree-tos -v -t --email 邮箱地址 -w 网站根目录 -d 网站域名

生成 dhparams

1
$ openssl dhparam -out /etc/ssl/certs/dhparams.pem 2048

nginx 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server {
listen 443 ssl;
server_name localhost;
ssl on;
ssl_certificate /etc/letsencrypt/live/www.haoduoyu.cc/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.haoduoyu.cc/privkey.pem;
ssl_dhparam /etc/ssl/certs/dhparams.pem;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
root /root/www/homepage/;
index index.html;
}
}

重启 nginx 后就可以通过 HTTPS 访问网站了。

参考资料:

计算机网络:自顶向下方法 CH8
维基百科
图解SSL/TLS协议
RSA算法原理(一)
RSA算法原理(二)
https连接的前几毫秒发生了什么