当前位置:天才代写 > tutorial > JAVA 教程 > Java 安详套接字编程以及 keytool 利用最佳实践

Java 安详套接字编程以及 keytool 利用最佳实践

2017-11-02 08:00 星期四 所属: JAVA 教程 浏览:1433

副标题#e#

概述

操作 Java 的 JSSE(Java Secure Socket Extension)技能,我们可以利便的编写安详套接字措施,关于 JSSE 的先容,可以参阅 Oracle网站提供的 JSSE指导。措施编写进程中,我们需要将数字证书应用到代码中。凡是在正式的产物开拓中,我们可以付出必然的用度,向正规认证机构,譬喻:Verisign、Geotrust、Thawte等申请。

假如只是为了尝试,我们还可以利用 Java 自带的 keytool 东西来建造证书。keytool 是密钥和证书打点东西,生成的密钥或证书,存放在 jks(Java Key Store) 名目标文件里。从用途上来说,jks 名目标文件常用于:

1) 存储非对称密钥对以及数字证书的证书库;

2) 存储信任证书列表的信任库。

留意:差异版本的 Java 自带的 keytool 呼吁行参数大概会略有差异。对比于 Java6,在 Java7 中 keytool        东西有如下窜改:

-export 选项更名为 -exportcert

-genkey 选项更名为 -genkeypair

-import 选项更名为 – importcert

-keyclone 选项被废弃

-identitydb 选项被废弃

-selfcert 选项被废弃

下面将以 Java7 中的 keytool 为例,对常见的用法举办说明。

利用 keytool 建造证书库以及信任库

生成非对称密钥以及自签发证书

呼吁:keytool -genkeypair -alias TEST_ROOT -keystore test_root.jks

表明:生成一对密钥以及一个自签发证书,个中私钥和证书以别名 TEST_ROOT 存储在 test_root.jks 文件中。

留意:利用上述呼吁时,呼吁行会交互的需要手动填写暗码、CN、OU 等信息。

生成证书请求文件

呼吁:keytool -certreq -file test_server.csr -alias TEST_SERVER -keystore        test_server.jks

表明:将别名为 TEST_SERVER 的公钥和一些小我私家书息从 test_server.jks 文件中导出,作为证书请求文件。

签发证书

呼吁:keytool -gencert -infile test_server.csr -outfile test_server.cer        -alias TEST_ROOT -keystore TEST_ROOT.jks

表明:利用别名为 TEST_ROOT 的私钥为 test_server.csr 签发证书,并生存到 test_server.cer 文件中。

从 jks 文件中导出证书

呼吁:keytool -exportcert -alias TEST_ROOT -file test_root.cer -keystore        test_root.jks

表明:从 test_root.jks 文件中导出别名为 TEST_ROOT 的证书并存放在 test_root.cer 文件中。

导入信任证书到 jks 文件

呼吁:keytool -importcert -alias TEST_ROOT -file test_root.cer -keystore        TEST_SERVER.jks

表明:将证书 test_root.cer 以别名 TEST_ROOT 导入 TEST_SERVER.jks 中。

留意:这里的方针 jks 文件里不含有指定的别名,此时的导入条目才会以 trustedCertEntry        信任证书的形式生存。

导入签发证书到 jks 文件 ( 更新证书 )

呼吁:keytool -importcert -alias TEST_SERVER -file        test_server.cer -keystore TEST_SERVER.jks

表明:将证书 test_server.cer 更新到已存在别名 TEST_SERVER 的        TEST_SERVER.jks 文件中

留意:这里的呼吁和上述导入信任证书的呼吁在形式上完全一样,但浸染差异。

1. 这里的方针 jks 文件里要含有指定的别名,这样 keytool 东西才会领略呼吁为更新证书,并以 PrivateKeyEntry 的形式生存。

2. 在更新被签发证书之前,必然要先将相应的 CA 证书,导入进 jks 文件,不然会报错“keytool 错误 : java.lang.Exception:        无法从回覆中成立链”。


#p#副标题#e#

打印证书内容

呼吁:keytool – printcert – v – file test_server.cer

表明:将证书 test_server.cer 的内容打印出来

留意:也可以利用 -sslserver ip:port 的参数,直接从网络上打印出某个 ssl server 提供的证书的内容,详情见 参考资料中列出的 keytool 利用辅佐。

显示 jks 文件里的内容

呼吁:keytool – list – v – keystore test_server.jks

表明:显示 test_server.jks 里存储的所有条目

留意:这里会要求提供 jks 文件的暗码,假如不输入,也可以显示出所有条目信息,但会提示“存储在密钥库中的信息的完整性尚未获得验证!”

从 jks 文件删除条目

呼吁:keytool -delete -alias TEST_ROOT -keystore test_server.jks

表明:从 test_server.jks 中删除别名为 TEST_ROOT 的条目

安详套接字措施编写的要领

利用 Java 编写安详套接字措施,可以遵循必然的要领,如图 1 所示,展示了相关的各个类之间的干系。个中 Keystore、KeyManagerFactory、TrustManagerFactory、SSLContext 可以称之为“引擎类”(engine        class),对它们指定特定的参数 ( 譬喻:协议、算法等 ),就可以发生切合我们要求的,用于编程的工具实例。

图 1. 相关类之间的干系

Java 和平套接字编程以及 keytool 操作最佳实践

(注:图片引自 《 Java Secure Socket Extension (JSSE) Reference Guide 》)

#p#副标题#e#

编程的步调可以简朴的小结为以下几步:

1. 利用 Keystore 类将证书库或信任库文件加载进来;

2. 利用 KeyManagerFactory 和加载了证书库的 Keystore 实例,发生 KeyManager 实例数组;

3. 利用 TrustManagerFactory 和加载了信任库的 Keystore 实例,发生 TrustManager 实例数组;

4. 利用 SSLContext 初始化 KeyManager 实例数组和 TrustManager 实例数组,从而设定好通信的情况。

5. 操作 SSLContext 发生的 SSLSocket 或 SSLServerSocket 举办通信。

在编写详细措施之前,我们需要操作前文对 keytool 东西的常识先容,筹备如下 jks 文件:

1. test_root.jks:该文件中存有自签发的证书,用作 CA 来签发证书;

2. test_server_cert.jks:该文件中存有 CA 签名的证书,用于 SSL/TSL 通信的处事端;

3. test_server_trust.jks:该文件中存有信任客户端的证书,用于 SSL/TSL 通信的处事端;

4. test_client_cert.jks:该文件中存有 CA 签名的证书,用于 SSL/TSL 通信的客户端;

5. test_client_trust.jks:该文件中存有信任处事端的证书,用于 SSL/TSL 通信的客户端。

假定每个 jks 文件的暗码都设定为“Testpassw0rd”,都存放在“D:”盘下。

通过系统属性指定证书库和信任库

#p#分页标题#e#

这种编写方法较量简朴直观,可以通过给 JVM 通报参数,可能在代码中利用 System.setProperty() 要领,来指定通信需要的 jks 文件。

处事端措施

要运行如清单 1 所示的措施,可以在呼吁行添加如下虚拟机参数,指定处事端措施要利用的证书库和暗码:

-Djavax.net.ssl.keyStore="D:/test_server_cert.jks"

-Djavax.net.ssl.keyStorePassword="Testpassw0rd"

留意到措施中 setNeedClientAuth(false),暗示不需要验证客户端身份。假如这里配置为 true,则我们这里还需要指定信任库和暗码:

-Djavax.net.ssl.trustStore="D:/test_server_trust.jks"

-Djavax.net.ssl.trustStorePassword="Testpassw0rd"

清单 1. 简朴的 SSL 通信处事端措施
import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStreamReader; 
import java.net.Socket; 
    
import javax.net.ssl.SSLServerSocket; 
import javax.net.ssl.SSLServerSocketFactory; 
import javax.net.ssl.SSLSocket; 
    
 public class Simple_SSLServerSocket{ 
 // 界说了监听端标语
 private final static int LISTEN_PORT=54321;
    
  public static void main(String args[]) throws IOException{ 
   SSLServerSocket serverSocket=null; 
   SSLSocket clientSocket=null; 
   // 利用默认方法获取套接字工场实例
   SSLServerSocketFactory ssf=(SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
    
  try{ 
     serverSocket=(SSLServerSocket)ssf.createServerSocket(LISTEN_PORT); 
     // 配置不需要验证客户端身份
     serverSocket.setNeedClientAuth(false);
     System.out.println("SSLServer is listening on "+LISTEN_PORT+" port"); 
     // 轮回监听端口,假如有客户端连入就新开一个线程与之通信
     while(true){ 
       // 接管新的客户端毗连
       clientSocket=(SSLSocket)serverSocket.accept(); 
       ClientConnection clientConnection=new ClientConnection(clientSocket); 
       // 启动一个新的线程
       Thread clientThread=new Thread(clientConnection); 
       System.out.println("Client "+clientThread.getId()+" is connected"); 
       clientThread.run(); 
     } 
   }catch(IOException ioExp){ 
     ioExp.printStackTrace(); 
   }catch(Exception e){ 
     e.printStackTrace(); 
   }finally{ 
     serverSocket.close(); 
   } 
 } 
} 
    
class ClientConnection implements Runnable{ 
 private Socket clientSocket=null;
    
public ClientConnection(SSLSocket sslsocket){ 
   clientSocket=sslsocket; 
 }
    
 public void run(){ 
   BufferedReader reader=null; 
   // 将吸收到的来自客户端的文字打印出来
   try{ 
     reader=new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); 
     while(true){ 
       String line=reader.readLine(); 
       if(line==null){ 
         System.out.println("Communication end."); 
         break; 
       } 
       System.out.println("Receive message: "+line); 
     } 
     reader.close(); 
     clientSocket.close(); 
   }catch(IOException ioExp){ 
     ioExp.printStackTrace(); 
   }catch(Exception e){ 
     e.printStackTrace(); 
   } 
 } 
}

#p#副标题#e#

客户端措施

对应于清单 1 所示的处事端措施,清单 2 是客户端措施,需要在呼吁行添加如下虚拟机参数,指定信任库和暗码:

-Djavax.net.ssl.trustStore="D:/test_client_trust.jks"

-Djavax.net.ssl.trustStorePassword="Testpassw0rd"

假如处事端措施 setNeedClientAuth(true) 要求验证客户端身份,则我们还需要指定证书库和暗码:

-Djavax.net.ssl.keyStore="D:/test_client_cert.jks"

#p#分页标题#e#

-Djavax.net.ssl.keyStorePassword="Testpassw0rd"
清单 2. 简朴的 SSL 通信客户端措施

import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStreamReader; 
import java.io.OutputStreamWriter; 
import java.io.Writer; 
import javax.net.ssl.SSLSocket; 
import javax.net.ssl.SSLSocketFactory; 
    
    
public class Simple_SSLSocket{ 
 // 界说要毗连的处事器名和端标语
 private static final int DEFAULT_PORT=54321; 
 private static final String DEFAULT_HOST="localhost"; 
 public static void main(String args[]){ 
   SSLSocket socket=null; 
   // 利用默认的方法获取工场实例
   SSLSocketFactory sf=(SSLSocketFactory)SSLSocketFactory.getDefault();
   try{ 
     // 毗连处事端的端口,完成握手进程
     socket=(SSLSocket)sf.createSocket(DEFAULT_HOST, DEFAULT_PORT); 
     socket.startHandshake(); 
     System.out.println("Connected to "+DEFAULT_HOST+":"+DEFAULT_PORT+" !"); 
     // 从节制台输入要发送给处事端的文字
     BufferedReader reader=new BufferedReader(new InputStreamReader(System.in)); 
     Writer writer=new OutputStreamWriter(socket.getOutputStream());
  // 可以重复向处事端发送动静
     boolean done=false; 
     while (!done) { 
       System.out.print("Send Message: "); 
       String line=reader.readLine(); 
       if (line!=null) { 
         writer.write(line+"\n"); 
         writer.flush(); 
       }else{ 
         done=true; 
       } 
     } 
     socket.close(); 
   }catch (Exception e) { 
     System.out.println("Connection failed: "+e); 
     try{ 
       socket.close(); 
     }catch(IOException ioe){} 
     socket=null; 
   } 
 } 
}

通过 SSLContext 指定证书库和信任库

前文描写的,通过系统参数指定证书库和信任库的要领,固然简朴易用,可是缺点也是显而易见的,整个措施的情况都得利用同样的 jks 文件。假如措施里有差异的 SSL/TSL 通信,则需要利用差异的 jks 文件,该怎么做呢?

可以利用 SSLContext 来指定 jks 文件,只需要把清单 3 的代码片断替换到清单 1 的“SSLServerSocketFactory ssf”生成处;把清单 4 的代码片断替换到清单 2 的“SSLSocketFactory sf”生成处,再稍作代码调解即可。

#p#副标题#e#

(注:实际上,在利用 SSLSocketFactory.getDefault() 可能 SSLServerSocketFactory.getDefault() 建设套接字的时候,措施内部已经利用了默认的 context,其参数就是通过系统属性指定的 )
清单 3. SSLContext 指定证书库

// 相关的 jks 文件及其暗码界说
private final static String CERT_STORE="D:/test_server_cert.jks"; 
private final static String CERT_STORE_PASSWORD="Testpassw0rd";
// 载入 jks 文件
FileInputStream f_certStore=new FileInputStream(CERT_STORE); 
KeyStore ks=KeyStore.getInstance("jks"); 
ks.load(f_certStore, CERT_STORE_PASSWORD.toCharArray()); 
f_certStore.close(); 
    
// 建设并初始化证书库工场
String alg=KeyManagerFactory.getDefaultAlgorithm(); 
KeyManagerFactory kmFact=KeyManagerFactory.getInstance(alg); 
kmFact.init(ks, CERT_STORE_PASSWORD.toCharArray()); 
    
KeyManager[] kms=kmFact.getKeyManagers(); 
    
// 建设并初始化 SSLContext 实例
SSLContext context=SSLContext.getInstance("SSL"); 
context.init(kms, null, null); 
SSLServerSocketFactory ssf=(SSLServerSocketFactory)context.getServerSocketFactory();

清单 4. SSLContext 指定信任库

// 相关的 jks 文件及其暗码界说
 private final static String TRUST_STORE="D:/test_client_trust.jks"; 
 private final static String TRUST_STORE_PASSWORD="Testpassw0rd";
    
// 载入 jks 文件
 FileInputStream f_trustStore=new FileInputStream(TRUST_STORE); 
 KeyStore ks=KeyStore.getInstance("jks"); 
 ks.load(f_trustStore, TRUST_STORE_PASSWORD.toCharArray()); 
 f_trustStore.close(); 
    
 // 建设并初始化信任库工场
 String alg=TrustManagerFactory.getDefaultAlgorithm(); 
 TrustManagerFactory tmFact=TrustManagerFactory.getInstance(alg); 
 tmFact.init(ks); 
    
 TrustManager[] tms=tmFact.getTrustManagers(); 
    
 // 建设并初始化 SSLContext 实例
 SSLContext context=SSLContext.getInstance("SSL"); 
 context.init(null, tms, null); 
 SSLSocketFactory sf=context.getSocketFactory();

虽然,假如要在处事端可能客户端同时利用证书库和信任库,可将清单 3 和清单 4 的代码用在同一处 context.init() 的处所。

#p#分页标题#e#

这里需要说明的是,假如 context.init() 的第一个 KeyManager[] 参数为 null,则暗示不提供证书;假如第二个 TrustManager[] 参数为        null,则会寻找系统默认提供的信任库 ( 譬喻:JRE 安装目次下的 lib/security/cacerts)。

利用 X509 证书信任打点器

查察本栏目

X509TrustManager 接口扩展了 TrustManager 接口,利用 TrustManager        接口,我们已经可以在措施中自界说信任库了,但假如对方的证书不在信任库中,则通信会直接宣告失败。

假如但愿能自界说信任库的一些行为 ( 譬喻:检讨对方证书,针对异常做一些处理惩罚 ),我们可以利用 X509TrustManager 接口,实现本身的要领。

如清单 5 所示,假定我们要在客户端措施利用 X509TrustManager,那么就可以在 checkServerTrusted()        函数里做一些工作,检测随处事端证书异常的话,就可以做一些本身的处理惩罚。CheckClientTrusted() 则是用于处事端检测客户端的证书环境。

将清单 5 的代码替换到清单 4 的 TrustManager[] tms 的生成处,并对代码稍作调解即可。

清单 5. X509TrustManager 的利用

// 利用自界说的 MyTrustManager 发生信任库
 TrustManager[] tms=new TrustManager[]{new MyTrustManager()}; 
……
……
 class MyTrustManager implements X509TrustManager{ 
  // 相关的 jks 文件及其暗码界说
  private final static String TRUST_STORE="D:/test_client_trust.jks"; 
  private final static String TRUST_STORE_PASSWORD="Testpassw0rd"; 
  X509TrustManager xtm;
      
  public MyTrustManager() throws Exception { 
    // 载入 jks 文件
    KeyStore ks = KeyStore.getInstance("JKS"); 
    ks.load(new FileInputStream(TRUST_STORE),TRUST_STORE_PASSWORD.toCharArray()); 
    TrustManagerFactory tmf =TrustManagerFactory.getInstance("SunX509", "SunJSSE"); 
    tmf.init(ks);
    TrustManager[] tms = tmf.getTrustManagers();
    // 筛选出 X509 名目标信任证书
    for (int i = 0; i < tms.length; i++) { 
      if (tms[i] instanceof X509TrustManager) { 
        xtm = (X509TrustManager) tms[i]; 
        return; 
      } 
    } 
  }
  // 处事端检讨客户端证书的接口
  public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException{ 
  }
 // 客户端检讨处事端证书的接口
  public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException{ 
    try{ 
      xtm.checkServerTrusted(chain, authType); 
    }catch(CertificateException excep){ 
      System.out.println(excep.getMessage()); 
      throw excep; 
    } 
  }
  // 获取可接管的刊行者
  public X509Certificate[] getAcceptedIssuers() { 
   //return xtm.getAcceptedIssuers(); 
    return null; 
  } 
 }

#p#副标题#e#

留意:

1. 当处事端代码 setNeedClientAuth(False) 时,客户端的 MyTrustManager 实现了 X509TrustManager 后,假如        checkServerTrusted() 要领的实现为空,则无论处事端利用什么证书,客户端城市默认接管;假如要对处事端证书举办查抄,还需要像清单 5        中的代码片断那样,捕获异常并处理惩罚。

2.getAcceptedIssuers() 要领凡是不需要详细实现,可是当处事端要求检讨客户端身份,也即 setNeedClientAuth(True) 时,处事端需也需要详细实现        X509TrustManager,且 getAcceptedIssuers() 要领要如清单 5 中注释部门代码那样实现。

调试 SSL/TSL 措施

打开调试开关调查通信日志

图 2 描写了 SSL/TSL 通信的握手进程。在实际编写措施的时候,大概会在这些环节碰着问题,导致无法通信,排查起交往往令人无从下手。这个时候我们可以将 SSL/TSL        通信的握手日志开关打开,举办调查。

图 2.SSL 通信协议握手进程

Java 和平套接字编程以及 keytool 操作最佳实践

(注:图片引自 《Java Secure Socket Extension (JSSE) Reference Guide》)

打开日志开关的要领同样是通过设定系统属性,可以从呼吁行添加虚拟机参数:

-Djavax.net.debug=ssl,handshake

虽然也可以利用 System.setProperty() 要领在代码中打开该开关。

打开日志开关后,可以搜索“ClientHello”、“ServerHello”等要害字;可能搜索“*** ”( 三个星号和一个空格 )        ——这是握手阶段每一个步调日志打印的开始符号。通过阅读日志来定位问题。更具体的开关信息,请参阅 JSSE 指导中的“Debugging Utilities”章节:

选择通信的 Cipher Suites

#p#分页标题#e#

有的时候为了做尝试,我们会选用特定的 Cipher Suites,我们可以利用 SSLServerSocket 或 SSLSocket 的        getEnabledCipherSuites() 调查到默认支持的所有加密套件的信息,然后利用 setEnabledCipherSuites() 举办配置。

清单 6 展示了从处事端过滤掉所有的匿名加密套件的代码。

清单 6. 过滤所有的匿名加密套件
String enabled[]=serverSocket.getEnabledCipherSuites(); 
Set<String> filter = new LinkedHashSet<String>(); 
for(int i = 0; i < enabled.length; i++) { 
if(enabled[i].indexOf("anon")<0){ 
filter.add(enabled[i]); 
} 
} 
serverSocket.setEnabledCipherSuites(filter.toArray(new String[filter.size()]));

竣事语

通过对本文的进修,我们可以快速相识如何利用 keytool 建造证书库和信任库,并利用 Java        编写安详套接字措施。文中贴出的呼吁行和代码片断都可以直接用于试验和实际应用中,可是要纯熟把握和领略,还发起凭据本文的步调,亲自动手试一试。

 

    关键字:

天才代写-代写联系方式