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