SSL工作原理介绍以及java实现

SSL工作原理介绍以及java实现

目录

  • SSL工作原理介绍以及java实现
    • SSL简介
    • SSL工作原理
      • 握手协议Handshake protocol
        • 1握手阶段使用RSA加密算法
        • 2握手阶段使用Diffie-Hellman加密算法
      • 记录协议Record protocol
      • 警报协议Alert protocol
    • Wireshark抓包图解
    • java实现

Secure Sockets Layer(安全套接层),在OSI七层模型里,该协议工作在会话层和表示层。本文主要从SSL原理入手,讲解SSL认证过程,并使用java实现单向认证。

1. SSL简介

安全套接字(Secure Socket Layer,SSL)协议是一个TCP连接之间安全交换信息的协议,提供两个基本的安全服务:鉴别与保密。

SSL是Netscape于1994年开发的,后来成为了世界上最通用的安全连接机制,主流的浏览器(IE/Chrome/Firefox等)以及操作系统远程连接服务(Windows的mstsc/Linux的SSH等)都支持SSL协议。

目前有如下版本:SSL2.0、SSL3.0、TLS1.0、TLS1.1、TLS1.2。因之前旧版本暴露出多个严重bug,目前推荐使用TLS1.2版本。

2. SSL工作原理

握手协议(Handshake protocol)

SSL中密钥交换算法有6种:无效(没有密钥交换)、RSA、匿名Diffie-Hellman、暂时Diffie-Hellman、固定Diffie-Hellman、Fortezza。本文以RSA与匿名Diffie-Hellman模式介绍握手协议。

2.1握手阶段使用RSA加密算法

RSA算法概述:
(1)选择两个大素数P、Q
(2)计算N=P*Q
(3)选择一个公钥(加密密钥)E,使其不是(P-1)与(Q-1)的因子
(4)选择私钥(解密密钥)D,满足如下条件:
          (D*E) mod (P-1)(Q-1)=1
(5)加密时,明文PT计算密文CT如下:
          CT= PT·E mod N
(6)解密时,从密文CT计算明文PT如下:
          PT= CT·D mod N 这也是SSL中会用一种密钥交换算法。 

开始加密通信之前,客户端和服务器首先必须建立连接和交换参数,这个过程叫做握手(handshake)。握手阶段分成五步。

第一步,Client给出协议版本号、一个客户端生成的随机数(Client random),以及客户端支持的加密方法。

第二步,Server确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random)。

第三步,Client确认数字证书有效,然后生成一个新的随机数(Premaster secret),并使用数字证书中的公钥,加密这个随机数,发给Server。

第四步,Server使用自己的私钥,获取Client发来的随机数(即Premaster secret)。

第五步,Client和Server根据约定的加密方法,使用前面的三个随机数,生成”对话密钥”(session key),用来加密接下来的整个对话过程。

上面的五步,如下图所示:

SSL工作原理介绍以及java实现_第1张图片

RSA加密算法的握手阶段有四点需要注意:

(1)整个握手阶段都是明文传输。如果有人窃听通信,他可以知道双方选择的加密方法,以及三个随机数中的两个。

(2)生成对话密钥一共需要三个随机数。

(3)握手之后的对话使用”对话密钥”加密(对称加密),服务器的公钥和私钥只用于加密和解密”对话密钥”(非对称加密),无其他作用。

(4)服务器公钥放在服务器的数字证书之中。

也就是说,整个对话过程中(握手阶段和其后的对话),服务器的公钥和私钥只需要用到一次。整个通话的安全,只取决于第三个随机数(Premaster secret)能不能被破解。

理论上,只要服务器的公钥足够长(比如2048位),那么Premaster secret可以保证不被破解。但是为了足够安全,我们可以考虑把握手阶段的算法从默认的RSA算法,改为Diffie-Hellman算法(简称DH算法)。

2.2握手阶段使用Diffie-Hellman加密算法

Diffie-Hellman算法概述:
(1)Alice与Bob确定两个大素数n和g,这两个数不用保密
(2)Alice选择另一个大随机数x,并计算A如下:A=gx mod n
(3)Alice将A发给Bob
(4)Bob选择另一个大随机数y,并计算B如下:B=gy mod n
(5)Bob将B发给Alice
(6)计算Alice的秘密密钥K1如下:K1=Bx mod n
(7)计算Bob的秘密密钥K2如下:K2=Ay mod n
K1=K2,因此Alice和Bob可以用其进行加解密 

使用Diffie-Hellman加密算法时,握手阶段分成五步。

第一步,Client给出协议版本号、一个客户端生成的随机数(Client random),以及客户端支持的加密方法。

第二步,Server确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random)。通过Client random、Server random以及服务器提供的DH参数(Server DH parameter)算出私钥。

第三步,Server端使用私钥加密Server DH parameter,发送给Client。Client使用证书中公钥解密得到Server DH parameter。

第四步,Client将客户端提供的DH参数(Client DH parameter)传给Server。

第五步,Client和Server根据约定的加密方法,使用之前双方交换的DH参数生成”秘密密钥”(Premaster secret),用来加密接下来的整个对话过程(Session key)。

上面的五步,如下图所示:

SSL工作原理介绍以及java实现_第2张图片

Diffie-Hellman加密算法规避了RSA中秘密密钥的传递,整个过程中只传递了参数,更加安全,但与之对应的连接资源开销也变得更大。

记录协议(Record protocol)

记录协议在客户机和服务器握手成功后使用,即客户机和服务器鉴别对方和确定安全信息交换使用的算法后,进入SSL记录协议,记录协议向SSL连接提供两个服务:

(1)保密性:使用握手协议定义的秘密密钥实现

(2)完整性:握手协议定义了MAC,用于保证消息完整性

记录协议的过程:

SSL工作原理介绍以及java实现_第3张图片

警报协议(Alert protocol)

客户机和服务器发现错误时,向对方发送一个警报消息。如果是致命错误,则算法立即关闭SSL连接,双方还会先删除相关的会话号,秘密和密钥。每个警报消息共2个字节,第1个字节表示错误类型,如果是警报,则值为1,如果是致命错误,则值为2;第2个字节制定实际错误类型。

3. Wireshark抓包图解

下面以访问百度首页为例,使用wireshark抓包查看SSL连接建立产生的数据包。

SSL工作原理介绍以及java实现_第4张图片

上图为Client Hello的包详情,可以看到所有信息均未加密。该包在握手协议第一个阶段,定义了Content Type=Handshake和Version=TLS 1.2。

其中Client随机数在random_bytes处,支持的加密套件在Cipher Suites处,共有26种。接下来定义压缩方法为null,扩展信息中可以看到Server_name为www.baidu.com。

下面看Server Hello的包。

SSL工作原理介绍以及java实现_第5张图片

可以看到服务器已经确认了加密套件为TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256。注意这里是一个套件,不是列出的所有算法都要参与Premaster secret的加密。这些套件会在Session key的加密中被用到。

接下来,Client使用证书中的公钥加密消息后继续向Server发送握手包,直到握手流程结束。如下所示:

SSL工作原理介绍以及java实现_第6张图片

之后,https连接建立,开始传输数据信息,所有数据均被加密。如下所示:

SSL工作原理介绍以及java实现_第7张图片

4. java实现

最后我们使用jdk1.6自带组件进行SSL连接的实现。

首先要为服务端生成一个数字证书。Java环境下,数字证书是用keytool生成的,这些证书被存储在keystore中,即证书仓库。使用cmd命令行调用keytool命令为服务端生成数字证书和保存它使用的证书仓库,如下:

keytool -genkey -v -alias bluedash-ssl-demo-server -keyalg RSA -keystore d:/zk/server_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass server -keypass 110119120

这样,我们就将服务端证书bluedash-ssl-demo-server保存在了server_ksy这个store文件当中。执行结果如下:

SSL工作原理介绍以及java实现_第8张图片

建立服务器端时需要设置上述证书仓库路径,用于面向client出示证书。代码如下:


package com.paic.test.ssl;

import java.io.BufferedReader;  
import java.io.FileInputStream;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.io.PrintWriter;  
import java.net.ServerSocket;  
import java.net.Socket;  
import java.security.KeyStore;  

import javax.net.ServerSocketFactory;  
import javax.net.ssl.KeyManagerFactory;  
import javax.net.ssl.SSLContext;  
import javax.net.ssl.SSLServerSocket; 


public class Server extends Thread {

        private Socket socket;  

        public Server(Socket socket) {  
            this.socket = socket;  
        }  

        public void run() {  
            try {  
                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));  
                PrintWriter writer = new PrintWriter(socket.getOutputStream());  

                String data = reader.readLine();  
                writer.println(data);  
                writer.close();  
                socket.close();  
            } catch (IOException e) {  

            }  
        }  

        private static String SERVER_KEY_STORE = "D:/Security/src/com/paic/test/ssl/server_ks";  
        private static String SERVER_KEY_STORE_PASSWORD = "110119120";  

        public static void main(String[] args) throws Exception {  
            System.setProperty("javax.net.ssl.trustStore", SERVER_KEY_STORE);  
            SSLContext context = SSLContext.getInstance("TLS");  

            KeyStore ks = KeyStore.getInstance("jceks");  
            ks.load(new FileInputStream(SERVER_KEY_STORE), null);  
            KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509");  
            kf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray());  

            context.init(kf.getKeyManagers(), null, null);  

            ServerSocketFactory factory = context.getServerSocketFactory();  
            ServerSocket _socket = factory.createServerSocket(8443);  
            ((SSLServerSocket) _socket).setNeedClientAuth(false);  

            while (true) {  
                new Server(_socket.accept()).start();  
            }  
        }  

}


接下来说客户端。连接时最重要的一点,服务端证书里面的CN一定和服务端的域名统一,我们的证书服务的域名是localhost,那么我们的客户端在连接服务端时一定也要用localhost来连接,否则根据SSL协议标准,域名与证书的CN不匹配,说明这个证书是不安全的,通信将无法正常运行。

现在客户端也必须要使用SSL协议连接服务端了,先为服务端也建立一个证书仓库,cmd中输入以下命令:

keytool -genkey -v -alias bluedash-ssl-demo-client -keyalg RSA -keystore d:/zk/client_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass client -keypass 110119120

回显如下:

SSL工作原理介绍以及java实现_第9张图片

现在,我们要把服务端的证书导出来,并导入到客户端的仓库。第一步是导出服务端的证书:

keytool -export -alias bluedash-ssl-demo-server -keystore d:/zk/server_ks -file d:/zk/server_key.cer

这里keystore的密码是server

这里写图片描述

第二步,把服务器端提供的证书导入客户端的证书仓库中,如下:

keytool -import -trustcacerts -alias bluedash-ssl-demo-server -file d:/zk/server_key.cer -keystore d:/zk/client_ks

这里keystore的密码是client。结果如下:

SSL工作原理介绍以及java实现_第10张图片

最后编写Client代码:

package com.paic.test.ssl;

import java.io.BufferedReader;  
import java.io.InputStreamReader;  
import java.io.PrintWriter;  
import java.net.Socket;  

import javax.net.SocketFactory;  
import javax.net.ssl.SSLSocketFactory;


public class Client {

        private static String CLIENT_KEY_STORE = "D:/Security/src/com/paic/test/ssl/client_ks";  

        public static void main(String[] args) throws Exception {  
            // Set the key store to use for validating the server cert.  
            System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);  

            System.setProperty("javax.net.debug", "ssl,handshake");  

            Client client = new Client();  
            Socket s = client.clientWithoutCert();  

            PrintWriter writer = new PrintWriter(s.getOutputStream());  
            BufferedReader reader = new BufferedReader(new InputStreamReader(s  
                    .getInputStream()));  
            writer.println("Hello World!");  
            writer.flush();  
            System.out.println(reader.readLine());  
            s.close();  
        }  

        private Socket clientWithoutCert() throws Exception {  
            SocketFactory sf = SSLSocketFactory.getDefault();  
            Socket s = sf.createSocket("localhost", 8443);  
            return s;  
        }  


}

完整日志如下:

keyStore is : 
keyStore type is : jks
keyStore provider is : 
init keystore
init keymanager of type SunX509
trustStore is: D:\Security\src\com\paic\test\ssl\client_ks
trustStore type is : jks
trustStore provider is : 
init truststore
adding as trusted cert:
  Subject: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
  Issuer:  CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
  Algorithm: RSA; Serial number: 0x58806140
  Valid from Thu Jan 19 14:48:32 CST 2017 until Wed Apr 19 14:48:32 CST 2017

adding as trusted cert:
  Subject: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
  Issuer:  CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
  Algorithm: RSA; Serial number: 0x58805a3a
  Valid from Thu Jan 19 14:18:34 CST 2017 until Wed Apr 19 14:18:34 CST 2017

trigger seeding of SecureRandom
done seeding SecureRandom
Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
%% No cached client session
*** ClientHello, TLSv1
RandomCookie:  GMT: 1468033624 bytes = { 214, 154, 197, 69, 110, 42, 160, 233, 199, 229, 122, 38, 230, 183, 103, 52, 25, 29, 96, 144, 86, 13, 236, 159, 228, 168, 228, 235 }
Session ID:  {}
Cipher Suites: [SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods:  { 0 }
***
main, WRITE: TLSv1 Handshake, length = 75
main, WRITE: SSLv2 client hello message, length = 101
main, READ: TLSv1 Handshake, length = 644
*** ServerHello, TLSv1
RandomCookie:  GMT: 1468033624 bytes = { 9, 150, 107, 67, 1, 147, 77, 73, 25, 134, 74, 228, 1, 172, 47, 149, 159, 32, 205, 72, 163, 21, 240, 247, 41, 111, 26, 108 }
Session ID:  {88, 128, 106, 88, 120, 114, 169, 106, 158, 74, 153, 73, 226, 136, 102, 99, 73, 5, 232, 16, 26, 208, 199, 85, 118, 7, 178, 38, 46, 14, 48, 207}
Cipher Suite: SSL_RSA_WITH_RC4_128_MD5
Compression Method: 0
Extension renegotiation_info, renegotiated_connection: 
***
%% Created:  [Session-1, SSL_RSA_WITH_RC4_128_MD5]
** SSL_RSA_WITH_RC4_128_MD5
*** Certificate chain
chain [0] = [
[
  Version: V3
  Subject: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 1024 bits
  modulus: 132010717494208823330135610754239731637209803896658954827545525773931053366786164660256992731220177418937202145579952356341491963197334472861732045115022380989341508527250473019782724735723642876007066492240045958870757117972447694316024493377798207800715178238449213037958195281193945491472218196895403430833
  public exponent: 65537
  Validity: [From: Thu Jan 19 14:18:34 CST 2017,
               To: Wed Apr 19 14:18:34 CST 2017]
  Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
  SerialNumber: [    58805a3a]

]
  Algorithm: [SHA1withRSA]
  Signature:
0000: 6D CD A8 06 62 DA F6 7D   EB 63 13 AC F5 BC A0 8F  m...b....c......
0010: 28 0E 3F DD EB 7C 70 6D   17 D8 FB 56 33 E1 23 79  (.?...pm...V3.#y
0020: 9A D8 E2 7C 08 CD 02 A0   7B BF E7 86 53 65 49 CF  ............SeI.
0030: 51 FC AF 3A 7B 78 36 15   83 4F 72 C3 B5 76 9E FC  Q..:.x6..Or..v..
0040: F0 69 82 82 41 BE 64 41   86 5F 97 04 DE D3 C0 82  .i..A.dA._......
0050: 5C 67 54 C2 69 E9 95 B6   D5 D5 B2 80 9B EC E0 F6  \gT.i...........
0060: 64 83 D0 7D 13 6D AA BC   25 56 EF BE 9D 5B CB 54  d....m..%V...[.T
0070: 81 C7 57 78 65 4D 4B 9C   55 7A 5F 35 CD 31 D5 1F  ..WxeMK.Uz_5.1..

]
***
Found trusted certificate:
[
[
  Version: V3
  Subject: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 1024 bits
  modulus: 132010717494208823330135610754239731637209803896658954827545525773931053366786164660256992731220177418937202145579952356341491963197334472861732045115022380989341508527250473019782724735723642876007066492240045958870757117972447694316024493377798207800715178238449213037958195281193945491472218196895403430833
  public exponent: 65537
  Validity: [From: Thu Jan 19 14:18:34 CST 2017,
               To: Wed Apr 19 14:18:34 CST 2017]
  Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
  SerialNumber: [    58805a3a]

]
  Algorithm: [SHA1withRSA]
  Signature:
0000: 6D CD A8 06 62 DA F6 7D   EB 63 13 AC F5 BC A0 8F  m...b....c......
0010: 28 0E 3F DD EB 7C 70 6D   17 D8 FB 56 33 E1 23 79  (.?...pm...V3.#y
0020: 9A D8 E2 7C 08 CD 02 A0   7B BF E7 86 53 65 49 CF  ............SeI.
0030: 51 FC AF 3A 7B 78 36 15   83 4F 72 C3 B5 76 9E FC  Q..:.x6..Or..v..
0040: F0 69 82 82 41 BE 64 41   86 5F 97 04 DE D3 C0 82  .i..A.dA._......
0050: 5C 67 54 C2 69 E9 95 B6   D5 D5 B2 80 9B EC E0 F6  \gT.i...........
0060: 64 83 D0 7D 13 6D AA BC   25 56 EF BE 9D 5B CB 54  d....m..%V...[.T
0070: 81 C7 57 78 65 4D 4B 9C   55 7A 5F 35 CD 31 D5 1F  ..WxeMK.Uz_5.1..

]
*** ServerHelloDone
*** ClientKeyExchange, RSA PreMasterSecret, TLSv1
main, WRITE: TLSv1 Handshake, length = 134
SESSION KEYGEN:
PreMaster Secret:
0000: 03 01 93 47 7C E6 00 37   1B 28 41 F6 28 C5 6F 85  ...G...7.(A.(.o.
0010: 94 B6 60 4C 8F C4 C5 EE   03 9A 70 A9 48 1B 3C 97  ..`L......p.H.<.
0020: 1B 6A 4B AB 2B ED FE F9   AD F0 C0 7E 62 DD AD 9D  .jK.+.......b...
CONNECTION KEYGEN:
Client Nonce:
0000: 58 80 6A 58 D6 9A C5 45   6E 2A A0 E9 C7 E5 7A 26  X.jX...En*....z&
0010: E6 B7 67 34 19 1D 60 90   56 0D EC 9F E4 A8 E4 EB  ..g4..`.V.......
Server Nonce:
0000: 58 80 6A 58 09 96 6B 43   01 93 4D 49 19 86 4A E4  X.jX..kC..MI..J.
0010: 01 AC 2F 95 9F 20 CD 48   A3 15 F0 F7 29 6F 1A 6C  ../.. .H....)o.l
Master Secret:
0000: 56 86 3F 43 70 7C 22 93   89 77 55 02 1D 49 2A 93  V.?Cp."..wU..I*.
0010: B4 A3 68 F6 96 D3 B5 E9   8A C5 CE 47 E1 33 08 22  ..h........G.3."
0020: 5C 53 BD 6A B2 71 56 FB   41 2B 90 39 9E 39 B2 8F  \S.j.qV.A+.9.9..
Client MAC write Secret:
0000: 89 57 FD 3D 9A 4E A1 F0   5E B4 FB C6 9A 6E DA 65  .W.=.N..^....n.e
Server MAC write Secret:
0000: 61 10 61 13 FA 1E FB 86   57 7E 7A 4C DD B9 81 82  a.a.....W.zL....
Client write key:
0000: A4 B8 09 BB 7C C3 68 03   C2 13 8D E5 A7 D4 BD EB  ......h.........
Server write key:
0000: 98 67 0B 85 89 F7 07 42   B8 68 3D 37 16 7E AF EF  .g.....B.h=7....
... no IV used for this cipher
main, WRITE: TLSv1 Change Cipher Spec, length = 1
*** Finished
verify_data:  { 96, 176, 192, 183, 120, 220, 1, 193, 132, 36, 241, 246 }
***
main, WRITE: TLSv1 Handshake, length = 32
main, READ: TLSv1 Change Cipher Spec, length = 1
main, READ: TLSv1 Handshake, length = 32
*** Finished
verify_data:  { 15, 9, 125, 232, 190, 217, 60, 132, 134, 34, 19, 105 }
***
%% Cached client session: [Session-1, SSL_RSA_WITH_RC4_128_MD5]
main, WRITE: TLSv1 Application Data, length = 30
main, READ: TLSv1 Application Data, length = 30
Hello World!
main, called close()
main, called closeInternal(true)
main, SEND TLSv1 ALERT:  warning, description = close_notify
main, WRITE: TLSv1 Alert, length = 18
main, called closeSocket(selfInitiated)

相比普通socket连接,SSL连接中的Client增加了使用信任证书仓库的代码。

如果连接成功将会返回客户端提交的Hello World!字符串。客户端定义的日志输出为debug,可以看到SSL单向握手的全过程。

如果需要Server与Client双向认证,将Server中((SSLServerSocket) _socket).setNeedClientAuth(false); 的false改为true,再将客户端证书导入服务端证书仓库即可。

你可能感兴趣的:(安全)