我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java安全套接字编程以及keytool使用最佳实践分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java安全套接字编程以及keytool使用最佳实践分析

这篇文章将为大家详细讲解有关Java安全套接字编程以及keytool使用最佳实践分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

概述

利用 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 使用帮助。

生成证书请求文件

命令: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 文件中

注意这里的命令和上述导入信任证书的命令在形式上完全一样,但作用不同。

这里的目标 jks 文件里要含有指定的别名,这样 keytool 工具才会理解命令为更新证书,并以 PrivateKeyEntry 的形式保存。

在更新被签发证书之前,一定要先将相应的 CA 证书,导入进 jks 文件,否则会报错“keytool 错误 : java.lang.Exception:         无法从回复中建立链”。

打印证书内容

命令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 》)

编程的步骤可以简单的小结为以下几步:

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

使用 KeyManagerFactory 和加载了证书库的 Keystore 实例,产生 KeyManager 实例数组;

使用 TrustManagerFactory 和加载了信任库的 Keystore 实例,产生 TrustManager 实例数组;

使用 SSLContext 初始化 KeyManager 实例数组和 TrustManager 实例数组,从而设定好通信的环境。

利用 SSLContext 产生的 SSLSocket 或 SSLServerSocket 进行通信。

在编写具体程序之前,我们需要利用前文对keytool 工具的知识介绍,准备如下 jks 文件:

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

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

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

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

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

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

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

这种编写方式比较简单直观,可以通过给 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();      }    }   }

客户端程序

对应于清单 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"

-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”生成处,再稍作代码调整即可。

(注:实际上,在使用 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() 的地方。

这里需要说明的是,如果 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()};  &hellip;&hellip; &hellip;&hellip;  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;    }   }

注意:

当服务端代码 setNeedClientAuth(False) 时,客户端的 MyTrustManager 实现了 X509TrustManager 后,如果         checkServerTrusted() 方法的实现为空,则无论服务端使用什么证书,客户端都会默认接受;如果要对服务端证书进行检查,还需要像清单 5         中的代码片段那样,捕捉异常并处理。

getAcceptedIssuers() 方法通常不需要具体实现,但是当服务端要求检验客户端身份,也即 setNeedClientAuth(True) 时,服务端需也需要具体实现         X509TrustManager,且 getAcceptedIssuers() 方法要如清单 5 中注释部分代码那样实现。

调试 SSL/TSL 程序

打开调试开关观察通信日志

图 2 描述了 SSL/TSL 通信的握手过程。在实际编写程序的时候,可能会在这些环节遇到问题,导致无法通信,排查起来往往令人无从下手。这个时候我们可以将 SSL/TSL         通信的握手日志开关打开,进行观察。

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

Java安全套接字编程以及keytool使用最佳实践分析

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

打开日志开关的方法同样是通过设定系统属性,可以从命令行添加虚拟机参数:

-Djavax.net.debug=ssl,handshake

当然也可以使用 System.setProperty() 方法在代码中打开该开关。

打开日志开关后,可以搜索“ClientHello”、“ServerHello”等关键字;或者搜索“*** ”( 三个星号和一个空格 )         &mdash;&mdash;这是握手阶段每一个步骤日志打印的开始标志。通过阅读日志来定位问题。更详细的开关信息,请参阅 JSSE 指导中的“Debugging Utilities”章节:

选择通信的 Cipher Suites

有的时候为了做实验,我们会选用特定的 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()]));

关于Java安全套接字编程以及keytool使用最佳实践分析就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Java安全套接字编程以及keytool使用最佳实践分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java安全套接字编程以及keytool使用最佳实践分析

这篇文章将为大家详细讲解有关Java安全套接字编程以及keytool使用最佳实践分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。概述利用 Java 的 JSSE(Java Secure S
2023-06-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录