SSL/TLS 握手协议使客户端和服务端能够安全协商出同一份通信密钥,本文隐藏了一些细节上的内容,对这一握手过程进行了简要说明,如有错误还请指出
SSL/TLS 握手协议
(0) Client 与 Server 之前建立 (TCP) 连接
(1) Client 向 Server 发送 “client hello” 消息,里面包含了安全相关的信息,例如 SSL/TLS 版本号,Client 支持的加密套件 (CipherSuite)。”client hello” 消息还包含了一个随机数 client random,用于通信密钥的计算。SSL/TLS 协议还允许 “client hello” 消息包含 Client 所支持的压缩算法 (可选项)
(2) Server 回复一条 “server hello” 消息,里面包含了加密套件 (Server 从 “client hello” 消息的 CipherSuites 列表中选择其中一个),session id 和 另一个随机数 server random。Server 还会在消息中附带自己的数字证书。(可选) 如果 Server 需要 Client 的数字证书进行客户端认证,会向 Client 发送 “client certificate request” 请求消息,里面包含了 Server 所支持的证书类型和认可的证书颁发机构 CA
(3) Client 收到 “server hello”,验证 Server 端的数字证书,并得到证书中 Server 端的公钥,读者可自行查阅更多数字证书的知识
(4) Client 向 Server 发送第三个随机数 pre-master secret。与之前不同,这次的随机数使用了 Server 的公钥加密 (非对称加密)。现在双方同时拥有这三个随机数 client random,server random,premaster secret,可以用来计算生成共同的通信密钥 master secret 用于加密后面传输的业务数据。
(5 – 可选) 如果收到 Server 端发来的 “client certificate request” 请求消息,Client 会向 Server 发送一个使用 Client 自己的私钥加密过的随机数 (暂时记作 secret-A),附带 Client 的数字证书。或者发送一个 “no digital certificate alert” 无证书警告,这种情况下基本可以认为 SSL/TLS 握手失败。
(6 – 可选) Server 验证 Client 发送过来的数字证书,并得到证书中公钥对 Client 进行身份认证 (通过公钥解密上面那个 secret-A)。
(7) Client 向 Server 发送 “finished” 消息,使用第 4 步中计算出来的密钥进行加密传输 (对称加密),这表示 Client 端握手阶段已经完成。
(8) Server 也向 Client 发送 “finished” 消息,使用第 4 步中计算出来的密钥进行加密传输 (对称加密),这表示 Server 端握手阶段完成。
(9) SSL/TLS 握手阶段完成,接下来双方通信的消息都会使用协商出来的密钥进行加密 (对称加密)
Java 代码演示
服务端 (全局 SSL 配置)
public static void main(String[] args) throws IOException {
System.setProperty(“javax.net.debug”, “SSL,handshake”);
System.setProperty(“javax.net.ssl.keyStore”, “./keystore/TEST.p12”);
System.setProperty(“javax.net.ssl.keyStorePassword”, “TEST”);
SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(8001);
// serverSocket.setNeedClientAuth(true); 需求客户端认证,可选
while (true) {
try {
SSLSocket socket = (SSLSocket) serverSocket.accept();
InputStream in = socket.getInputStream();
String message = IOUtils.toString(in);
System.out.println(message);
in.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端
public static void main(String[] args) throws UnknownHostException, IOException {
System.setProperty(“javax.net.debug”, “SSL,handshake”);
System.setProperty(“javax.net.ssl.trustStore”, “./keystore/TEST.p12”);
System.setProperty(“javax.net.ssl.trustStorePassword”, “TEST”);
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket) factory.createSocket(“localhost”, 8001);
socket.startHandshake();
OutputStream out = socket.getOutputStream();
out.write(“hello”.getBytes());
out.close();
socket.close();
}
因测试需要,Server 的数字证书是自签名的,而非权威的 CA 所颁发,于是客户端使用了全局的 TrustStore 配置,引入 Server 的数字证书,否则会有以下错误
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
… 14 more
现在我们分别启动 Server 和 Client,并分析 SSL debug 日志
Client Hello
*** ClientHello, TLSv1.2
RandomCookie: GMT: 1545722559 bytes = {221, 47, 184, 101, 75, 18, 171, 225, 219, 236, 80, 229, 222, 114, 155, 14, 110, 144, 168, 163, 85, 252, 110, 180, 127, 37, 247, 50}
Session ID: {}
Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods: {0}
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA
***
首先是 Client 发起 SSL 握手,发送 “client hello” 消息
ClientHello, TLSv1.2 得知 Client 支持的版本号
RandomCookie,客户端生成的随机数 (client random),使用 4 个字节的当前时间加上 28 个随机字节
Cipher Suites 列表,表示 Client 所支持的加密套件
Server Hello
Server 收到 “client hello”,即来自 Client 的握手请求,回复 “server hello”
*** ServerHello, TLSv1.2
RandomCookie: GMT: 1545722559 bytes = {230, 234, 216, 95, 222, 185, 10, 245, 211, 122, 11, 47, 116, 109, 51, 164, 52, 92, 165, 72, 58, 222, 7, 19, 230, 32, 247, 99}
Session ID: {92, 34, 219, 191, 186, 218, 195, 78, 237, 222, 208, 62, 165, 14, 115, 106, 29, 243, 81, 152, 79, 45, 199, 0, 141, 231, 199, 100, 242, 152, 101, 13}
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
Compression Method: 0
Extension renegotiation_info, renegotiated_connection: <empty>
***
Cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
*** Certificate chain
chain [0] = [
[
Version: V3
Subject: CN=fwks, OU=ACL, O=ACL, L=ZHA, ST=ASIA, C=CN, EMAILADDRESS=ACL@GMAIL.COM
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
…………
ServerHello, TLSv1.2,Server 使用的版本号
RandomCookie,Server 生成的随机数 (server random),4 个字节的当前时间加上 28 个随机字节
Session ID,凭借 session id,会话双方可以缓存并使用 SSL/TLS 握手阶段生成的密钥,而不需要再频繁地进行 SSL/TLS 握手
Cipher suite,Server 从 “client hello” 的加密套件列表中选择的其中一个
Certificate chain,是从 CA 到 Server 的数字证书链列表。因为这里是测试用的自签名证书,所以证书链中只有 Server 自己的数字证书
Client 收到 “server hello” 后对 Server 的证书进行验证,成功后打出如下日志 (测试需要,Server 的自签名证书已经配置在 Client 的 TurstStore/TrustManager 中)
Found trusted certificate:
[
[
Version: V3
Subject: CN=fwks, OU=ACL, O=ACL, L=ZHA, ST=ASIA, C=CN, EMAILADDRESS=ACL@GMAIL.COM
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
…………
“server hello” 中还有一段消息 ServerKeyExchange,告诉 Client 使用的密钥交换算法是什么 (例中使用 ECDH 算法),即如何使用 client random, server random, premaster-secret 生成通信密钥 (不了解 ECDH,这里可能会有误)。
*** ECDH ServerKeyExchange
Signature Algorithm SHA512withRSA
Server key: Sun EC public key, 256 bits
public x coord: 80178198866764561576110018839724135146035097258288090685496480316896017800231
public y coord: 21879990761153492368331320937448674839810402545614808541518903129245252068750
parameters: secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)
ClientKeyExchange
Client 使用 Server 的公钥加密第三个随机数 pre-master secret,并发送给 Server。只有 Server 能使用自己的私钥解出这个 pre-master secret
*** ECDHClientKeyExchange
ECDH Public value: {4, 159, 152, 225, 34, 111, 12, 18, 196, 101, 247, 201, 137, 231, 252, 89, 48, 157, 66, 201, 181, 25, 159, 10, 12, 202, 18, 190, 64, 58, 12, 220, 204, 49, 251, 95, 11, 40, 251, 46, 204, 69, 48, 238, 166, 116, 134, 140, 172, 186, 106, 85, 34, 105, 169, 185, 87, 101, 80, 133, 214, 130, 56, 132, 64}
main, WRITE: TLSv1.2 Handshake, length = 70
现在通信双方都掌握了足够的信息去生成通信密钥 (master secret)
SESSION KEYGEN:
PreMaster Secret:
0000: 03 01 84 54 F5 D6 EB F5 A8 08 BA FA 7A 22 61 2D …T……..z”a-
0010: 75 DC 40 E8 98 F9 0E B2 87 80 B8 1A 8F 68 25 B8 u.@……….h%.
0020: 51 D0 54 45 61 8A 50 C9 BB 0E 39 53 45 78 BE 79 Q.TEa.P…9SEx.y
CONNECTION KEYGEN:
Client Nonce:
0000: 40 FC 30 AE 2D 63 84 BB C5 4B 27 FD 58 21 CA 90 @.0.-c…K’.X!..
0010: 05 F6 A7 7B 37 BB 72 E1 FC 1D 1B 6A F5 1C C8 9F ….7.r….j….
Server Nonce:
0000: 40 FC 31 10 79 AB 17 66 FA 8B 3F AA FD 5E 48 23 @.1.y..f..?..^H#
0010: FA 90 31 D8 3C B9 A3 2C 8C F5 E9 81 9B A2 63 6C ..1.<..,……cl
Client Nonce,就是第一个随机数 client random
Server Nonce,就是第二个随机数 server random
PreMaster Secret,第三个随机数
生成的通信密钥如下。除了 Master Secret 的其他几个,笔者也不是特别了解
Master Secret:
0000: 2C 31 A6 EC A7 75 D0 DC E9 3E 23 1D B4 B7 50 87 ,1…u…>#…P.
0010: 48 41 18 7D 29 D4 DB 8A 7D A5 F3 D5 15 08 A4 50 HA..)……….P
0020: 5A 4A 50 7D 08 C3 E5 A5 CB ED 4C 40 80 C3 B8 B2 ZJP…….L@….
Client MAC write Secret:
0000: 1C C1 5F 82 CB CD AB 6B 77 C7 7B D8 66 48 6F A4 .._….kw…fHo.
0010: C2 30 59 4D 91 1A 36 82 A4 C2 EF 9B 42 B5 98 7F .0YM..6…..B…
Server MAC write Secret:
0000: 7D D6 D2 3C 6F 61 AE 15 1F 62 46 4E A5 68 59 66 …<oa…bFN.hYf
0010: 72 50 81 0D 12 07 41 B4 8E 83 1F 5D EF 85 D0 12 rP….A….]….
Client write key:
0000: B0 50 53 C9 FF 10 4E 71 0B 5F 29 63 9C 47 82 77 .PS…Nq._)c.G.w
Server write key:
0000: 65 67 22 93 A2 45 74 18 D0 F7 B9 F2 78 19 61 07 eg”..Et…..x.a.
Finish 消息
现在通信双方都计算同一份密钥 Master Secret,可以用于加密并发送 finish 消息了。但在此之前 Client 还会发送了一条 “Change Cipher Spec”,用于告诉对方接下来的通信使用新的密钥加密消息。SSL 日志也会打出下面这一条:
main, WRITE: TLSv1.2 Change Cipher Spec, length = 1
接下来才是使用新密钥加密发送 finish 消息
*** Finished
verify_data: {5, 73, 52, 104, 95, 23, 44, 252, 228, 173, 15, 129}
***
main, WRITE: TLSv1.2 Handshake, length = 80
Server 收到来自 Client 的 “Change Cipher Spec” 和 “finish” 消息后,也会向 Client 发送 “Change Cipher Spec” 和 “finish” 消息
main, READ: TLSv1.2 Change Cipher Spec, length = 1
main, READ: TLSv1.2 Handshake, length = 80
*** Finished
verify_data: {5, 73, 52, 104, 95, 23, 44, 252, 228, 173, 15, 129}
***
main, WRITE: TLSv1.2 Change Cipher Spec, length = 1
*** Finished
verify_data: {169, 120, 73, 97, 72, 13, 37, 157, 77, 249, 0, 7}
***
main, WRITE: TLSv1.2 Handshake, length = 80
至此,SSL/TLS 握手阶段完成,通信双方使用新协商的密钥加 / 解密业务数据
main, READ: TLSv1.2 Application Data, length = 64
hello
关于 HTTPS
到这是否豁然开朗了?HTTPS 就是 HTTP over SSL/TLS,同样先进行 SSL/TLS 握手协商通信密钥,再使用通信密钥加密 HTTP 请求 / 响应报文。如果你在浏览器输入 https://localhost:8001/ 访问上面的 SSL Server,浏览器会给出如下警告浏览器在验证 Server 证书的时候已经失败了,原因是:
证书不可信,因为它是自签名的 (The certificate is not trusted because it is self-signed)
证书的内容和域名 “localhost” 不匹配 (The certificate is not valid for the name localhost)
附录 A:加密套件 CipherSuite
加密算法套件是一组密码算法的集合,SSL/TLS 通信过程会使用到这一组算法,他们包括
密钥交换算法 (key exchange algorithm),主要有 RSA, DH, ECDH, ECDHE
认证算法,规定服务端认证和客户端认证 (可选) 使用的算法,主要有 RSA, DSA, ECDSA 这一类非对称加密算法
数据加密算法,规定在实际的数据传输中使用的对称加密算法,有 AES, DES, 3DES 这一类对称加密算法
消息验证算法 (MAC algorithm),规定数据完整性验证算法 (验证数据在传输中是否受到噪声干扰和其它非人为的破坏),SHA, MD5 这一类散列算法可作为 MAC
分割线上方的算法在 SSL/TLS 握手阶段使用,下方两类算法在实际数据传输时使用到
回顾之前测试中使用到的加密套件 Cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS: 协议名字
ECDHE: 密钥交换算法
RSA: 认证算法 (RSA 非对称加密算法)
WITH: 分割线
AES_128_CBC: 数据加密算法 (AES 对称加密算法)
SHA256: MAC 算法 (SHA 散列算法 / 哈希算法)