安全网络通信(JAVA-JSSE)


JAVA安全套接字扩展(JSSE,Java Secure Socket Extension)基于SSL和TLS协议的Java网络应用程序提供了Java API及参考实现。JSSE支持数据加密、服务器端身份验证、数据完整性,以及可选的客户端身份认证,是信用JSSE,可以保证采用各种应用层协议(Http,telnet和Ftp等)的客户端程序与服务器程序安全的交换数据。
JSSE封装了底层复杂的安全通信细节,使得安全开发人员能方便的用它来开发安全的网络应用程序。


1、关于SSL

SSL(Server Socket Layer)是一种保证网络上连个节点进行安全通信的协议。IETF(Internet Engineering Task Force)对SSL做了标准化,制定了RFC2246规范,并将其成为TLS(Transport Layer Security)。从目前使用的技术上来讲,TLS1.0和SSL3.0之间的差别非常微小。
SSL采用加密技术来实现安全通信,保证通信数据的保密性和完整性,并且保证通信双方可以验证对方身份。

加密通信

加密通信––
处理对数据进行加密通信外,SSL还采用了身份认证机制,确保通信双方都可以验证对方的真实身份。个人可以通过身份证来来证明自己的身份,SSL通过安全安全证书来证明客户或者服务器的身份。当客户通过安全的连接和扶额u器通信时,服务器会首先向它出示它的安全证书,这个证书声明该服务器是安全的,而且的确是这个服务器。每个证书在全世界的范围内都是唯一的,其他服务器无法假冒原始服务器的身份。
安全证书采用公钥加密技术,安全证书机包含了用于加密数据的密钥,又包括了证明身份的数字签名。
获得安全证书的方式有两种,一种是从权威机构购买安全证书(安全证书由国际权威的证书机构 CA颁发),另一种是创建自签名的证书(SUN 公司提供的Key tool工具可以创建这样的证书)。

SSL握手

客户在与服务器通信时,需要完成SSL握手,SSL握手主要完成以下任务:

  • 协商使用的加密套件(加密套件包括一组加密参数,这些参数指定了加密的算法和密钥的长度信息等)
  • 验证对方的身份(此操作可选)
  • 确定使用的加密算法
    SSL握手的流程如下:
    (1)客户将自己的SSL版本号、加密参数、与会话有关的数据以及其他必要的信息发送给服务器。
    (2)服务器将自己的SSL版本号、加密参数、与会话有关的数据以及其他必要的信息发送给客户端,同时还将自己的证书发送给客户,如果需要验证客户的身份,还会发出要求客户端提供证书的请求。
    (3)客户端验证服务器的证书,如果验证失败,就提示不能建立SS连接,如果成功,则继续下面的步骤。
    (4)客户端为本次会话生成预备主密钥(pre-master secret),并用服务器的公钥加密之后发送给服务器。
    (5)如果服务器要求验证客户端的身份,客户端还要对另外一些数签名后,连同自己的证书一起发送给服务器。
    (6)如果服务器要求验证客户端的身份,则检查签署客户端证书的CA是否可信,如果在信任列表中,服务器用自己的私钥解密收到的预备主密钥,并用它通过某些算法生成本次会话的主密码(master-secret)。
    (7)客户端和服务器均使用此主密码生成此次会话的会话密钥(对称密钥)。
    (8)客户端通知服务器此后的通信都使用这个会话密钥进行加密,并通知服务器客户端已经完成此次SSL握手。
    (9)服务器通知客户端此后的通信都使用这个会话密钥进行加密,并通知客户端服务器已经完成此次SSL握手。
    (10)握手结束,会话建立。

2、JSSE简介

JSSE封装了底层复杂的安全通信细节,使得安全开发人员能方便的用它来开发安全的网络应用程序,JDK1.4及以后版本中都自带了JSSE类库。

JSSE主要包含一下四个包:

  • javax.net.ssl包:包括进行安全通信的类,比如SSLServerSocket和SSLSocket类。
  • javax.net包:包括安全套接字的工厂类,比如SSLServerSocketFactory和SSLSocketFactory类。
  • java.security.cert包:包括处理安全证书的类,如X509Certificate类。
  • com.sun.net.ssl包:包括SUN公司提供的JSSE的实现类。

    JSSE具有以下重要特征:

  • 纯粹用Java语言编写。
  • 可以出口到大多数国家。
  • 提供了支持SSL2.0和SSL3.0的JSSE API,,并且提供了SSL 3.0 的JSSE实现。
  • 提供了支持TLS1.0的JSSE API和JSSE实现。
  • 提供了用于创建安全连接的类,如SSLSocket、SSLServerSocket和SSLEngine。
  • 支持加密通信。
  • 支持客户端和服务器端的身份验证。
  • 支持SSL会话。
  • 支持一些常用的加密算法,比如RSA(加密长度2048位)、RC4(密钥长度128位)和DH(密钥长度1024位)。

JSSE API的主要类框图

主要类框图––

JSSE中负责安全通信的最核心的类是SSLServerSocket类与SSLSocket类,它们分别是ServerSocket与Socket类的子类。
SSLSocket对象由SSLSocketFactory创建,此外,SSLServerSocket的accept()方法也会创建SSLSocket。
SSLServerSocket对象由SSLServerSocketFactory创建。
SSLSocketFactory、SSLServerSocketFactory以及SSLEngine对象都由SSLContext对象创建。
SSLEngine类用于支持非阻塞的安全通信。

创建采用SSL协议的SSLSocket对象

1
2
3
4
5
6
public void createSocket()throws Exception{
factory=(SSLSocketFactory)SSLSocketFactory.getDefault();
socket=(SSLSocket)factory.createSocket(host,port);
String[] supported=socket.getSupportedCipherSuites();
socket.setEnabledCipherSuites(supported);
}

KeyStore、KeyManager与TrustManager类

在进行安全通信时,要求客户端与服务器端都支持SSL或TSL协议。
客户端与服务器端可能都需要设置用于证实自身身份的安全证书,还要设置信任对方的安全证书。
更常见的情况是,服务器只需要设置用于证实自身身份的安全证书,客户端只需要设置信任服务器的安全证书。
KeyStore类用于存放安全证书。以下程序代码创建了一个KeyStore对象,它从test.keys文件中加载安全证书。

1
2
3
4
5
6
String passphrase = "654321";
//JKS是SUN支持的KeyStore的类型
KeyStore keyStore = KeyStore.getInstance("JKS");
char[] password = passphrase.toCharArray();
//password参数用于打开安全证书
keyStore.load(new FileInputStream("test.keys"), password);

KeyManager接口的任务是选择用于证实自身身份的安全证书,把它发送给对方。KeyManagerFactory负责创建KeyManager对象,例如:

1
2
3
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, password);
KeyManager[] keyManagers= keyManagerFactory.getKeyManagers();

TrustManager接口的任务是决定是否信任对方的安全证书。
TruesManagerFactory负责创建TrustManager对象,例如:

1
2
3
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers= trustManagerFactory.getTrustManagers();

SSLContext类

SSLContext类负责设置与安全通信有关的各种信息,比如使用的协议(SSL或者TLS),自身的安全证书以及对方的安全证书。SSLContext还负责构造SSLServerSocketFactory、SSLSocketFactory和SSLEngine对象。
以下程序代码创建并初始化了一个SSLContext对象,然后由它创建了一个SSLServerSocketFactory对象:

1
2
3
SSLContext sslCtx = SSLContext.getInstance("TLS"); //采用TLS协议
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLServerSocketFactory ssf=sslCtx.getServerSocketFactory();

SSLContext的init()方法定义如下:
init(KeyManager[] km, TrustManager[] tm, SecureRandom random)

  • 参数random用于设置安全随机数,如果为null,则采用默认的SecureRandom实现
  • 如果参数km为空,会创建一个默认的KeyMagager对象,以及与之相关联的KeyStore对象,KeyStore对象从系统属性javax.net.ssl.keyStore中获取安全证书,若不存在这个属性,那么KeyStore对象的内容为空.
  • 如果参数tm为空,会创建一个默认的TrustManager对象,以及与之相关联的KeyStore对象,KeyStore对象按照如下步骤获取安全证书:
    先尝试从系统属性javax.net.ssl.trustStore中获取安全证书
    若上一步失败,就尝试把<JDK目录>/jre/security/jsscacerts文件作为安全证书文件
    若上一步失败,就尝试把<JDK目录>/jre/security/cacerts文件作为安全证书文件
    若上一步失败,则KeyStore对象内容为空

SSLServerSocketFactory类负责创建SSLServerSocket对象:

1
2
SSLServerSocket serverSocket =
(SSLServerSocket) sslServerSocketFactory.createServerSocket(8000);

SSLServerSocketFactory对象有两种创建方法:
调用SSLContext类的getServerSocketFactory()方法。
调用SSLServerSocketFactory类的静态getDefault()方法。
SSLServerSocketFactory类的静态方法getDefault()返回一个默认的SSLServerSocketFactory对象,他与一个默认的SSLcontext对象关联,getDefault()方法的实现按照如下方法初始化这个默认的SSLContext对象:

1
sslContext.init(null,null,null)

SSLSocketFactory类负责创建SSLSocket对象

1
sslContext.init(null,null,null)

SSLSocketFactory对象有两种创建方法:
调用SSLContext类的getSocketFactory()方法。
调用SSLSocketFactory类的静态getDefault()方法
SSLSocketFactory类的静态方法getDefault()返回一个默认的SSLSocketFactory对象,他与一个n默认的SSLcontext对象关联,getDefault()方法的实现按照如下方法初始化这个默认的SSLContext对象:

1
sslContext.init(null,null,null)

SSLSocket类

SSLSocket类是Socket类的子类,因此两者的用法有许多相似之处。
SSLSocket类还具有与安全通信有关的方法:
(1)设置加密套件
SSLSocket类的getSupportedCipherSuites()方法返回一个字符串数组,它包含当前SSLSocket对象所支持的加密套件组。 SSLSocket类的setEnabledCipherSuites(String[] suites)方法设置当前SSLSocket对象的可使用的加密套件组。
可使用的加密套件组应该是所支持的加密套件组的子集。
*SSLSocket类的getEnabledCipherSuites()方法返回一个字符串数据,包含当前SSLSocket对象的可使用的加密套件组。
例如以下代码仅仅启用了具有高加密强度的加密套件,这可以提高该通信端的安全性,禁止那些不支持强加密的通信端连接当前通信端:

1
2
3
4
5
String[] strongSuites={"SSL_DES_DSS_WITH_3DES_EDE_CBC_SHA",
"SSL_RSA_WITH_RC4_128_MD5",
"SSL_RSA_WITH_RC4_128_SHA",
"SSL_RSA_WITH_3DES_EDE_CBC_SHA"};
sslSocket. setEnabledCipherSuites(strongSuites) ;

(2)处理握手结束事件
SSL握手需要花很长的时间,当SSL握手完成,会发出一个HandshakeCompletedEvent事件,该事件由HandshakeCompletedListener负责监听。
SSLSocket类的addHandshakeCompletedListener()方法负责注册HandshakeCompletedListener监听器。
HandshakeCompletedEvent类提供了获取与握手事件相关的信息的方法:

1
2
3
public SSLSession getSession()  //获得会话
public String getCipherSuite() //获得实际使用的加密套件
public SSLSocket getSocket() //获得发出该事件的套接字

HandshakeCompletedListener接口的以下方法负责处理握手结束事件
public void handshakeCompleted(HandshakeCompletedEvent event)
(3)管理SSL会话
SSLSession接口表示SSL会话,它具有以下方法:

1
2
3
4
5
6
7
8
9
byte[] getId()  //获得会话ID。每个会话都有惟一的ID
String getCipherSuite() //获得实际使用的加密套件
long getCreationTime() //获得创建会话的时间
long getLastAccessedTime() //获得最近一次访问会话的时间。访问会话是指程序创建一个使用
该会话的SSLSocket。
String getPeerHost() //获得通信对方的主机
int getPeerPort() //获得通信对方的端口
void invalidate() //使会话失效
boolean isValid() //判断会话是否有效

SSLSocket的getSession()方法返回SSLSocket所属的会话。
SSLSocket的setEnableSessionCreation(boolean flag)方法决定SSLSocket是否允许创建新的会话。flag参数的默认值为true。
SSLSocket的startHandshake()方法显式的执行一次SSL握手。
由于多数情况下客户端无需向服务器证实自己的身份,因此当一个通信端无需向对方证实自己身份,就称它处于客户模式,否则称它处于服务器模式。
SSLSocket的setUseClientMode(boolean mode)方法用来设置客户模式或者服务器模式。
如果mode参数为true,就表示客户模式,即无需向对方证实自己的身份;
如果mode参数为false,就表示服务器模式,即需要向对方证实自己的身份。
当SSLSocket处于服务器模式,还可以通过以下方法来决定是否要求对方提供身份认证:
setWantClientAuth(boolean want):当want参数为true,表示希望对方提供身份认证。如果对方未出示安全证书,连接不会中断,通信继续进行。
setNeedClientAuth(boolean need):当need参数为true,表示要求对方必须提供身份认证。如果对方未出示安全证书,连接中断,通信无法继续。

SSLServerSocket

SSLServerSocket类是ServerSocket类的子类,因此两者的用法有许多相似之处。
此外,SSLServerSocket类还具有与安全通信有关的方法。这些方法与SSLSocket类中的同名方法具有相同的作用。
(1)设置加密套件的方法如下:

1
2
3
String[] getSupportedCipherSuites():返回一个字符串数组,它包含当前SSLServerSocket对象所支持的加密套件组。
void setEnabledCipherSuites(String[] suites):设置当前SSLServerSocket对象可使用的加密套件组。
String[] getEnabledCipherSuites():返回一个字符串数组,它包含当前SSLServerSocket对象可使用的加密套件组。

(2)管理SSL会话的方法如下:

1
2
void setEnableSessionCreation(boolean flag):决定由当前SSLServerSocket对象创建的SSLSocket对象是否允许创建新的会话。
boolean getEnableSessionCreation():判断由当前SSLServerSocket对象创建的SSLSocket对象是否允许创建新的会话。

(3)设置客户端模式的方法如下:

1
2
3
void setUseClientMode(boolean mode):当mode参数为true,表示客户端模式。
void setWantClientAuth(boolean want):当want参数为true,表示希望对方提供身份认证。
void setNeedClientAuth(boolean need):当need参数为true,表示要求对方必须提供身份认证。

SSLEngine类

SSLEngine类与SocketChannel类联合使用,就能实现非阻塞的安全通信。
SSLEngine类封装了与安全通信有关的细节,把应用程序发送的应用数据打包为网络数据,打包就是指对应用数据进行加密,加入SSL握手数据,把它变为网络数据。
SSLEngine类还能把接收到的网络数据展开为应用数据,展开就是指对网络数据解密。
SSLEngine类的wrap()方法负责打包应用数据,unwrap()方法负责展开网络数据。

SSLEnging––

创建基于SSL的安全服务器和安全客户

比如:
创建一个基于SSL的安全服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.security.KeyStore;

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

public class EchoServer {
private int port =8000;
private SSLServerSocket serverSocket;

public EchoServer() throws Exception{
SSLContext context=creatSSLContext();
SSLServerSocketFactory factory=context.getServerSocketFactory();
serverSocket=(SSLServerSocket) factory.createServerSocket(port);
System.out.println("服务器启动!");

String []supported=serverSocket.getSupportedCipherSuites();
serverSocket.setEnabledCipherSuites(supported);
}

public SSLContext creatSSLContext()throws Exception{
String keyStoreFile="test.keystore";
String password="123456";
KeyStore ks=KeyStore.getInstance("JKS");
char[]psw=password.toCharArray();
ks.load(new FileInputStream(keyStoreFile),psw);
KeyManagerFactory kmf=KeyManagerFactory.getInstance("SunX509");
kmf.init(ks,psw);

SSLContext sslContext=SSLContext.getInstance("SSL");
sslContext.init(kmf.getKeyManagers(), null, null);

return sslContext;

}

public String echo(String msg){
return "echo:"+msg;
}

private PrintWriter getWriter(Socket socket)throws IOException {

OutputStream socketOutputStream=socket.getOutputStream();
return new PrintWriter(socketOutputStream,true);
}

private BufferedReader getReader(Socket socket) throws IOException{
InputStream socketIn=socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}

public void service() {
while(true){
Socket socket=null;
try{
socket=serverSocket.accept();
System.out.println("新连接:"+socket.getInetAddress()+":"+socket.getPort());

BufferedReader br=getReader(socket);
PrintWriter pw=getWriter(socket);

String msg=null;
while ((msg=br.readLine())!=null) {
System.out.println(msg);
pw.println(echo(msg));
if(msg.equals("bye")){
break;
}

}
}catch (Exception e) {
e.printStackTrace();
}finally {
if(socket!=null)
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args)throws Exception {
new EchoServer().service();
}
}

对应的,创建一个基于SSL的安全客户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;


public class EchoClient {
private String host="localhost";
private int port=8000;
private SSLSocket socket;

public EchoClient()throws IOException {
SSLSocketFactory factory=(SSLSocketFactory) SSLSocketFactory.getDefault();
socket=(SSLSocket) factory.createSocket(host,port);

String[] supported=socket.getSupportedCipherSuites();
socket.setEnabledCipherSuites(supported);
}

public static void main(String[] args) throws IOException {
new EchoClient().talk();
}

private PrintWriter getWriter(Socket socket)throws IOException {

OutputStream socketOutputStream=socket.getOutputStream();
return new PrintWriter(socketOutputStream,true);
}

private BufferedReader getReader(Socket socket) throws IOException{
InputStream socketIn=socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}

public void talk() {
try{
BufferedReader bReader=getReader(socket);
PrintWriter pWriter=getWriter(socket);
BufferedReader localReader=new BufferedReader(new InputStreamReader(System.in));
String msg=null;
while((msg=localReader.readLine())!=null){

pWriter.println(msg);
System.out.println(bReader.readLine());

if(msg.equals("bye")){
break;
}
}

}catch (Exception e) {
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

总结

  • JAVA安全套接字扩展(JSSE,Java Secure Socket Extension)基于SSL和TLS协议的Java网络应用程序提供了Java API及参考实现。JSSE支持数据加密、服务器端身份验证、数据完整性,以及可选的客户端身份认证,是信用JSSE,可以保证采用各种应用层协议(Http,telnet和Ftp等)的客户端程序与服务器程序安全的交换数据。
  • JSSE封装了底层复杂的安全通信细节,使得安全开发人员能方便的用它来开发安全的网络应用程序。
  • JSSE中负责安全通信的最核心的类是SSLServerSocket类与SSLSocket类,它们分别是ServerSocket与Socket类的子类。
  • SSLSocket对象由SSLSocketFactory创建,此外,SSLServerSocket的accept()方法也会创建SSLSocket。
  • SSLServerSocket对象由SSLServerSocketFactory创建。
  • SSLSocketFactory、SSLServerSocketFactory以及SSLEngine对象都由SSLContext对象创建。
  • SSLEngine类用于支持非阻塞的安全通信。
  • SSLContext负责为SSLSocket和SSLSocketFactory设置安全参数,如使用的安全协议、安全证书、KeyManager、TrustManager和安全随机数等。

Mallory
2018年9月3日


资料参考:
[1]孙卫琴 JAVA网络编程精解