2

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 握手阶段完成,接下来双方通信的消息都会使用协商出来的密钥进行加密 (对称加密)

简易版概括:Client 明文发送 client random,Server 明文发送 server random,Client 密文 (非对称加密) 发送 premaster secret。双方根据约定的算法,使用三个随机数计算出用于对称加密的密钥。

sy10660a.gif

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 散列算法/哈希算法)

风歌
37 声望8 粉丝