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 握手階段完成,接下來雙方通信的消息都會使用協商出來的密鑰進行加密 (對稱加密)
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 散列算法/哈希算法)