SSL/TLS 握手協議使客戶端和服務端可以安全協商出同一份通訊密鑰,本文隱藏了一些細節上的內容,對這一握手過程進行了簡要說明,若有錯誤還請指出java
client random
,用於通訊密鑰的計算。SSL/TLS 協議還容許 "client hello" 消息包含 Client 所支持的壓縮算法 (可選項)server random
。Server 還會在消息中附帶本身的數字證書。(可選) 若是 Server 須要 Client 的數字證書進行客戶端認證,會向 Client 發送 "client certificate request" 請求消息,裏面包含了 Server 所支持的證書類型和承認的證書頒發機構 CApre-master secret
。與以前不一樣,此次的隨機數使用了 Server 的公鑰加密 (非對稱加密)。如今雙方同時擁有這三個隨機數client random
,server random
,premaster secret
,能夠用來計算生成共同的通訊密鑰 master secret
用於加密後面傳輸的業務數據。簡易版歸納:Client 明文發送 client random
,Server 明文發送 server random
,Client 密文 (非對稱加密) 發送 premaster secret
。雙方根據約定的算法,使用三個隨機數計算出用於對稱加密的密鑰。git
服務端 (全局 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 日誌安全
*** 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" 消息session
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 ............
Client 收到 "server hello" 後對 Server 的證書進行驗證,成功後打出以下日誌 (測試須要,Server 的自簽名證書已經配置在 Client 的 TurstStore/TrustManager 中)dom
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,這裏可能會有誤)。socket
*** 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)
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
生成的通訊密鑰以下。除了 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.
如今通訊雙方都計算同一份密鑰 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 就是 HTTP over SSL/TLS,一樣先進行 SSL/TLS 握手協商通訊密鑰,再使用通訊密鑰加密 HTTP 請求/響應報文。
若是你在瀏覽器輸入 https://localhost:8001/
訪問上面的 SSL Server,瀏覽器會給出以下警告
瀏覽器在驗證 Server 證書的時候已經失敗了,緣由是:
加密算法套件是一組密碼算法的集合,SSL/TLS 通訊過程會使用到這一組算法,他們包括
分割線上方的算法在 SSL/TLS 握手階段使用,下方兩類算法在實際數據傳輸時使用到
回顧以前測試中使用到的加密套件Cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256