美文网首页
Java网络编程进阶:通过JSSE创建安全的数据通信

Java网络编程进阶:通过JSSE创建安全的数据通信

作者: 博文视点 | 来源:发表于2020-11-12 10:20 被阅读0次

    小编说:本文作者孙卫琴,知名IT作家和Java专家。本文将通过一个范例向大家介绍JSSE是如何实现安全的网络通信的。


    在网络上,信息在由源主机到目标主机的传输过程中会经过其他计算机。一般情况下,中间的计算机不会监听路过的信息。但在使用网上银行或者进行信用卡交易时,网络上的信息有可能被非法分子监听,从而导致个人隐私的泄露。

    由于Internet和Intranet体系结构存在一些安全漏洞,总有某些人能够截获并替换用户发出的原始信息。

    随着电子商务的不断发展,人们对信息安全的要求越来越高,于是Netscape公司提出了SSL协议,目的为了能在开放网络上安全保密地传输信息。

    Java安全套接字扩展(JSSE,Java Secure Socket Extension)为基于SSL和TLS协议的Java网络应用程序提供了Java API以及参考实现。

    JSSE支持数据加密、服务器端身份验证、数据完整性以及可选的客户端身份验证。使用JSSE,能保证采用各种应用层协议(比如HTTP、Telnet和FTP等)的客户程序与服务器程序安全地交换数据。

    JSSE封装了底层复杂的安全通信细节,使得开发人员能方便地利用它来开发安全的网络应用程序。

    下文参考了《Java网络编程核心技术详解》一书的第15章,将结合具体范例来向大家介绍JSSE的用法。


    JSSE API 简介

    JSSE封装了底层复杂的安全通信细节,使得开发人员能方便地用它来开发安全的网络应用程序。JSSE主要包括四个包:

    • javax.net.ssl包:包括进行安全通信的类,比如SSLServerSocket和SSLSocket类。
    • javax.net包:包括安全套接字的工厂类,比如SSLServerSocketFactory和SSLSocketFactory类。
    • java.security.cert包:包括处理安全证书的类,如X509Certificate类。X.509是由国际电信联盟(ITU-T)制定的安全证书的标准。
    • com.sun.net.ssl包:包括Oracle公司提供的JSSE的实现类。

    JSSE具有以下重要特征:

    • 纯粹用Java语言编写。
    • 可以出口到大多数国家。
    • 提供了支持SSL的JSSE API和JSSE实现。
    • 提供了支持TLS的JSSE API和JSSE实现。
    • 提供了用于创建安全连接的类,如SSLSocket、 SSLServerSocket 和 SSLEngine。
    • 支持加密通信。
    • 支持客户端和服务器端的身份验证。
    • 支持SSL会话。
    • JSSE的具体实现会支持一些常用的加密算法,比如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 类用于支持非阻塞的安全通信。

    创建安全服务器

    以下EchoServer类创建了一个基于SSL的安全服务器,它处于服务器模式。

      1/* EchoServer.java*/
      2import java.net.*;
      3import java.io.*;
      4import javax.net.ssl.*;
      5import java.security.*;
      6
      7public class EchoServer {
      8  private int port=8000;
      9  private SSLServerSocket serverSocket;
     10
     11  public EchoServer() throws Exception {
     12    //输出跟踪日志
     13    //System.setProperty("javax.net.debug", "all");
     14    SSLContext context=createSSLContext();
     15    SSLServerSocketFactory factory=context.getServerSocketFactory();
     16    serverSocket =(SSLServerSocket)factory.createServerSocket(port);
     17    System.out.println("服务器启动");
     18    System.out.println(
     19               serverSocket.getUseClientMode()? "客户模式":"服务器模式");
     20    System.out.println(serverSocket.getNeedClientAuth()?
     21             "需要验证对方身份":"不需要验证对方身份");
     22
     23    String[] supported=serverSocket.getSupportedCipherSuites();
     24    serverSocket.setEnabledCipherSuites(supported);
     25  }
     26
     27  public SSLContext createSSLContext() throws Exception {
     28    //服务器用于证实自己身份的安全证书所在的密钥库
     29    String keyStoreFile = "test.keystore";
     30    String passphrase = "123456";
     31    KeyStore ks = KeyStore.getInstance("JKS");
     32    char[] password = passphrase.toCharArray();
     33    ks.load(new FileInputStream(keyStoreFile), password);
     34    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
     35    kmf.init(ks, password);
     36
     37    SSLContext sslContext = SSLContext.getInstance("SSL");
     38    sslContext.init(kmf.getKeyManagers(), null, null);
     39
     40    //当要求客户端提供安全证书时,服务器端可创建TrustManagerFactory,
     41    //并由它创建TrustManager,TrustManger根据与之关联的KeyStore中的信息,
     42    //来决定是否相信客户提供的安全证书。
     43
     44    //客户端用于证实自己身份的安全证书所在的密钥库
     45    //String trustStoreFile = "test.keystore";  
     46    //KeyStore ts = KeyStore.getInstance("JKS");
     47    //ts.load(new FileInputStream(trustStoreFile), password);
     48    //TrustManagerFactory tmf =
     49    //    TrustManagerFactory.getInstance("SunX509");
     50    //tmf.init(ts);
     51    //sslContext.init(kmf.getKeyManagers(),
     52    //                 tmf.getTrustManagers(), null);
     53
     54    return sslContext;
     55  }
     56
     57  public String echo(String msg) {
     58    return "echo:" + msg;
     59  }
     60
     61  private PrintWriter getWriter(Socket socket)throws IOException{
     62    OutputStream socketOut = socket.getOutputStream();
     63    return new PrintWriter(socketOut,true);
     64  }
     65  private BufferedReader getReader(Socket socket)throws IOException{
     66    InputStream socketIn = socket.getInputStream();
     67    return new BufferedReader(new InputStreamReader(socketIn));
     68  }
     69
     70  public void service() {
     71    while (true) {
     72      Socket socket=null;
     73      try {
     74        socket = serverSocket.accept();  //等待客户连接
     75        System.out.println("New connection accepted "
     76                        +socket.getInetAddress()
     77                       + ":" +socket.getPort());
     78        BufferedReader br =getReader(socket);
     79        PrintWriter pw = getWriter(socket);
     80
     81        String msg = null;
     82        while ((msg = br.readLine()) != null) {
     83          System.out.println(msg);
     84          pw.println(echo(msg));
     85          if (msg.equals("bye")) //如果客户发送的消息为“bye”,就结束通信
     86            break;
     87        }
     88      }catch (IOException e) {
     89         e.printStackTrace();
     90      }finally {
     91         try{
     92           if(socket!=null)socket.close();  //断开连接
     93         }catch (IOException e) {e.printStackTrace();}
     94      }
     95    }
     96  }
     97
     98  public static void main(String args[])throws Exception {
     99    new EchoServer().service();
    100  }
    101}
    

    以上EchoServer类先创建了SSLContext对象,然后由它创建SSLServerSocketFactory对象,再由该工厂对象创建SSLServerSocket对象。对于以下程序代码:

    1System.out.println(serverSocket.getUseClientMode()?
    2               "客户模式":"服务器模式");
    3System.out.println(serverSocket.getNeedClientAuth()?
    4               "需要验证对方身份":"不需要需要验证对方身份");
    
    

    打印结果为:

    1服务器模式
    2不需要验证对方身份
    
    

    由此可见,默认情况下,SSLServerSocket处于服务器模式,必须向对方证实自身的身份,但不需要验证对方的身份,即不要求对方出示安全证书。

    如果希望程序运行时输出底层JSSE实现的日志信息,可以把“javax.net.debug”系统属性设为“all”:

    1System.setProperty("javax.net.debug", "all");
    
    

    创建安全客户程序

    以下EchoClient类创建了一个基于SSL的安全客户,它处于客户模式

     1/* EchoClient.java */
     2import java.net.*;
     3import java.io.*;
     4import javax.net.ssl.*;
     5import java.security.*;
     6
     7public class EchoClient {
     8  private String host="localhost";
     9  private int port=8000;
    10  private SSLSocket socket;
    11
    12  public EchoClient()throws IOException{
    13    SSLContext context=createSSLContext();
    14    SSLSocketFactory factory=context.getSocketFactory();
    15    socket=(SSLSocket)factory.createSocket(host,port);
    16    String[] supported=socket.getSupportedCipherSuites();
    17    socket.setEnabledCipherSuites(supported);
    18    System.out.println(socket.getUseClientMode()?
    19                           "客户模式":"服务器模式");
    20  }
    21
    22  public SSLContext createSSLContext() throws Exception {
    23    String passphrase = "123456";
    24    char[] password = passphrase.toCharArray();
    25
    26    //设置客户端所信任的安全证书所在的密钥库
    27    String trustStoreFile = "test.keystore";    
    28    KeyStore ts = KeyStore.getInstance("JKS");
    29    ts.load(new FileInputStream(trustStoreFile), password);
    30    TrustManagerFactory tmf =
    31                 TrustManagerFactory.getInstance("SunX509");
    32    tmf.init(ts);
    33
    34    SSLContext sslContext = SSLContext.getInstance("SSL");
    35    sslContext.init(null,tmf.getTrustManagers(), null);
    36    return sslContext;
    37  }
    38  public static void main(String args[])throws IOException{
    39    new EchoClient().talk();
    40  }
    41  private PrintWriter getWriter(Socket socket)throws IOException{
    42    OutputStream socketOut = socket.getOutputStream();
    43    return new PrintWriter(socketOut,true);
    44  }
    45  private BufferedReader getReader(Socket socket)throws IOException{
    46    InputStream socketIn = socket.getInputStream();
    47    return new BufferedReader(new InputStreamReader(socketIn));
    48  }
    49  public void talk()throws IOException {
    50    try{
    51      BufferedReader br=getReader(socket);
    52      PrintWriter pw=getWriter(socket);
    53      BufferedReader localReader=
    54          new BufferedReader(new InputStreamReader(System.in));
    55      String msg=null;
    56      while((msg=localReader.readLine())!=null){
    57        pw.println(msg);
    58        System.out.println(br.readLine());
    59
    60        if(msg.equals("bye"))
    61          break;
    62      }
    63    }catch(IOException e){
    64       e.printStackTrace();
    65    }finally{
    66       try{socket.close();}catch(IOException e){e.printStackTrace();}
    67    }
    68  }
    69}
    
    

    以上EchoClient类先创建了一个SSLSocketFactory对象,然后由它创建了SSLSocket对象。对于以下程序代码:

    1System.out.println(socket.getUseClientMode()?"客户模式":"服务器模式");
    
    

    打印结果为:

    1客户模式
    
    

    由此可见,默认情况下,由SSLSocketFactory创建的SSLSocket对象处于客户模式,不必向对方证实自身的身份。

    EchoClient类依靠TrustManager来决定是否信任EchoServer出示的安全证书。EchoClient类的SSLSocketFactory对象是由SSLContext对象来创建的。这个SSLContext对象通过TrustManager来管理所信任的安全证书。在本例中,TrustManager所信任的安全证书位于test.keystore密钥库文件中。

    在本例中,服务器端向客户端出示的安全证书位于test.keystore密钥库文件中。在实际应用中,服务器端的密钥库文件中包含密钥对,从安全角度出发,客户端所信任的密钥库文件中应该仅仅包含公钥,所以服务器和客户端应该使用不同的密钥库文件。

    在 IT 行业,大多数 Java 程序员都看过孙卫琴老师的书。

    孙老师的书,清晰严谨,把复杂的技术架构层层剖析,结合典型的实例细致讲解,读者只要静下心来好好品读,就能深入 Java 技术的殿堂!

    如今,Java 在网络应用开发领域得到了非常广泛的运用,孙卫琴老师新书上市之际,博文视点学院联合孙老师共同打造技术视频课《Java网络编程核心技术详解》(含同名新书一本)!

    通过课程+图书的学习,不仅可以帮助你掌握网络编程的实用技术,还可以进一步提高按照面向对象的思想来设计和开发Java软件的能力!

    热文推荐

    • 声纹技术:让智能语音助手真正“认得”自己
    • 为什么人人都需要懂一点高阶(中台)产品思维
    • 苏杰:如果可以重来,你还会做工作狂么?
    • 超详细丨完整的推荐系统架构设计

    相关文章

      网友评论

          本文标题:Java网络编程进阶:通过JSSE创建安全的数据通信

          本文链接:https://www.haomeiwen.com/subject/nnbubktx.html