SSL/TLS 握手協議概述

SSL/TLS 握手協議使客戶端和服務端能夠安全協商出同一份通信密鑰,本文隱藏了一些細節上的內容,對這一握手過程進行了簡要說明,如有錯誤還請指出

SSL/TLS 握手協議

  • (0) Client 與 Server 之前建立 (TCP) 連接
  • (1) Client 向 Server 發送 "client hello" 消息,裏面包含了安全相關的信息,例如 SSL/TLS 版本號,Client 支持的加密套件 (CipherSuite)。"client hello" 消息還包含了一個隨機數 (client random),用於通信密鑰 (secret key) 的計算。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 密文),可以用來計算生成共同的密鑰 (secret key) 用於加密後面傳輸的業務數據。
  • (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 握手階段完成,接下來雙方通信的消息都會使用協商出來的密鑰進行加密 (對稱加密)

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, [email protected]
  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, [email protected]
  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  [email protected]%.
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 散列算法/哈希算法)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章