上节已经介绍了SSL/TLS协议的通信模式,而对于这些底层协议,如果要每个开发者都自己去实现显然会带来不必要的麻烦,正是为了解决这个问题Java为广大开发者提供了Java安全套接字扩展——JSSE,它包含了实现Internet安全通信的一系列包的集合,是SSL和TLS的纯Java实现,同时它是一个开放的标准,每个公司都可以自己实现JSSE,通过它可以透明地提供数据加密、服务器认证、信息完整性等功能,就像使用普通的套接字一样使用安全套接字,大大减轻了开发者的负担,使开发者可以很轻松将SSL协议整合到程序中,并且JSSE能将安全隐患降到了最低点。

在用JSSE实现SSL通信过程中主要会遇到以下类和接口,由于过程中涉及到加解密、密钥生成等运算的框架和实现,所以也会间接用到JCE包的一些类。如图3-1-7-2为JSSE接口的主要类图:

图3-1-7-2 JSSE API主要类图

①  通信核心类——SSLSocket和SSLServerSocket。对于使用过socket进行通信开发的朋友比较好理解,它们对应的就是Socket与ServerSocket,只是表示实现了SSL协议的Socket和ServerSocket,同时它们也是Socket与ServerSocket的子类。SSLSocket负责的事情包括设置加密套件、管理SSL会话、处理握手结束时间、设置客户端模式或服务器模式。

②  客户端与服务器端Socket工厂——SSLSocketFactory和SSLServerSocketFactory。在设计模式中工厂模式是专门用于生产出需要的实例,这里也是把SSLSocket、SSLServerSocket对象创建的工作交给这两个工厂类。

③  SSL会话——SSLSession。安全通信握手过程需要一个会话,为了提高通信的效率,SSL协议允许多个SSLSocket共享同一个SSL会话,在同一个会话中,只有第一个打开的SSLSocket需要进行SSL握手,负责生成密钥及交换密钥,其余SSLSocket都共享密钥信息。

④  SSL上下文——SSLContext。它是对整个SSL/TLS协议的封装,表示了安全套接字协议的实现。主要负责设置安全通信过程中的各种信息,例如跟证书相关的信息。并且负责构建SSLSocketFactory、SSLServerSocketFactory和SSLEngine等工厂类。

⑤  SSL非阻塞引擎——SSLEngine。假如你要进行NIO通信,那么将使用这个类,它让通过过程支持非阻塞的安全通信。

⑥  密钥管理器——KeyManager。此接口负责选择用于证实自己身份的安全证书,发给通信另一方。KeyManager对象由KeyManagerFactory工厂类生成。

⑦  信任管理器——TrustManager。此接口负责判断决定是否信任对方的安全证书,TrustManager对象由TrustManagerFactory工厂类生成。

⑧  密钥证书存储设施——KeyStore。这个对象用于存放安全证书,安全证书一般以文件形式存放,KeyStore负责将证书加载到内存。

通过上面这些类就可以完成SSL协议的安全通信了,在利用SSL/TLS进行安全通信时,客户端跟服务器端都必须要支持SSL/TLS协议,不然将无法进行通信。而且客户端和服务器端都可能要设置用于证实自己身份的安全证书,并且还要设置信任对方的哪些安全证书。

关于身份认证方面有个名词叫客户端模式,一般情况客户端要对服务器端的身份进行验证,但是无需向服务器证实自己的身份,这样不用向对方证实自己身份的通信端我们就说它处于客户模式,否则成它处于服务器模式。SSLSocket的setUseClientMode(Boolean mode)方法可以设置客户端模式或服务器模式。

往下看一个用JSSE简单实现SSL通信的例子。

①  解决证书问题。

一般而言作为服务器端必须要有证书以证明这个服务器的身份,并且证书应该描述此服务器所有者的一些基本信息,例如公司名称、联系人名等。证书由所有人以密码形式签名,基本不可伪造,证书获取的途径有两个:一是从权威机构购买证书,权威机构担保它发出的证书的真实性,而且这个权威机构被大家所信任,进而你可以相信这个证书的有效性;另外一个是自己用JDK提供的工具keytool创建一个自我签名的证书,这种情况下一般是我只想要保证数据的安全性与完整性,避免数据在传送的过程中被窃听或篡改,此时身份的认证已不重要,重点已经在端与端传输的秘密性上,证书的作用只体现在加解密签名。

另外,关于证书的一些概念在这里陈述,一个证书是一个实体的数字签名,这个实体可以是一个人、一个组织、一个程序、一个公司、一个银行,同时证书还包含这个实体的公共钥匙,此公共钥匙是这个实体的数字关联,让所有想同这个实体发生信任关系的其他实体用来检验签名。而这个实体的数字签名是实体信息用实体的私钥加密后的数据,这条数据可以用这个实体的公共钥匙解密,进而鉴别实体的身份。这里用到的核心算法是非对称加密算法。

SSL协议通信涉及密钥储存的文件格式比较多,很容易搞混,例如xxx.cer、xxx.pfx、xxx.jks、xxx.keystore、xxx.truststore等格式文件。如图3-1-7-3,搞清楚他们有助于理解后面的程序,.cer格式文件俗称证书,但这个证书中没有私钥,只包含了公钥;.pfx格式文件也称为证书,它一般供浏览器使用,而且它不仅包含了公钥,还包含了私钥,当然这个私钥是加密的,不输入密码是解不了密的;.jks格式文件表示java密钥存储器(javakey store),它可以同时容纳N个公钥跟私钥,是一个密钥库;.keystore格式文件其实跟.jks基本是一样的,只是不同公司叫法不太一样,默认生成的证书存储库格式;.truststore格式文件表示信任证书存储库,它仅仅包含了通信对方的公钥,当然你可以直接把通信对方的jks作为信任库(就算如此你也只能知道通信对方的公钥,要知道密钥都是加密的,你无从获取,只要算法不被破解)。有些时候我们需要把pfx或cert转化为jks以便于用java进行ssl通信,例如一个银行只提供了pfx证书,而我们想用java进行ssl通信时就要将pfx转化为jks格式。

图3-1-7-3 密钥存储文件格式

按照理论上,我们一共需要准备四个文件,两个keystore文件和两个truststore文件,通信双方分别拥有一个keystore和一个truststore,keystore用于存放自己的密钥和公钥,truststore用于存放所有需要信任方的公钥。这里为了方便直接使用jks即keystore替代truststore(免去证书导来导去),因为对方的keystore包含了自己需要的信任公钥。

下面使用jdk自带的工具分别生成服务器端证书,通过如下命令并输入姓名、组织单位名称、组织名称、城市、省份、国家信息即可生成证书密码为tomcat的证书,此证书存放在密码也为tomcat的tomcat.jks证书存储库中。如果你继续创建证书将继续往tomcat.jks证书存储库中添加证书。如果你仅仅输入keytool -genkey -alias tomcat -keyalg RSA -keypass tomcat -storepasstomcat,不指定证书存储库的文件名及路径,则工具会在用户的home
directory目录下生产一个“.keystore”文件作为证书存储库。

类似的,客户端证书也用此方式进行生成。如下

②  服务端TomcatSSLServer.java

public class TomcatSSLServer {

privatestatic final String SSL_TYPE = "SSL";

privatestatic final String KS_TYPE = "JKS";

privatestatic final String X509 = "SunX509";

privatefinal static int PORT = 443;

privatestatic TomcatSSLServer sslServer;

privateSSLServerSocket svrSocket;

publicstatic TomcatSSLServer getInstance() throws Exception {

if(sslServer == null) {

sslServer= new TomcatSSLServer();

}

returnsslServer;

}

privateTomcatSSLServer() throws Exception{

SSLContextsslContext = createSSLContext();

SSLServerSocketFactoryserverFactory = sslContext.getServerSocketFactory();

svrSocket=(SSLServerSocket) serverFactory.createServerSocket(PORT);

svrSocket.setNeedClientAuth(true);

String[]supported = svrSocket.getEnabledCipherSuites();

svrSocket.setEnabledCipherSuites(supported);

}

privateSSLContext createSSLContext() throws Exception{

KeyManagerFactorykmf = KeyManagerFactory.getInstance(X509);

TrustManagerFactorytmf = TrustManagerFactory.getInstance(X509);

String serverKeyStoreFile ="c:\\tomcat.jks";

StringsvrPassphrase = "tomcat";

char[]svrPassword = svrPassphrase.toCharArray();

KeyStoreserverKeyStore = KeyStore.getInstance(KS_TYPE);

serverKeyStore.load(newFileInputStream(serverKeyStoreFile), svrPassword);

kmf.init(serverKeyStore,svrPassword);

StringclientKeyStoreFile = "c:\\client.jks";

StringcntPassphrase = "client";

char[]cntPassword = cntPassphrase.toCharArray();

KeyStoreclientKeyStore = KeyStore.getInstance(KS_TYPE);

clientKeyStore.load(newFileInputStream(clientKeyStoreFile),cntPassword);

tmf.init(clientKeyStore);

SSLContextsslContext  =SSLContext.getInstance(SSL_TYPE);

sslContext.init(kmf.getKeyManagers(),tmf.getTrustManagers(), null);

returnsslContext;

}

publicvoid startService() {

SSLSocketcntSocket = null;

BufferedReaderioReader = null;

PrintWriterioWriter = null;

StringtmpMsg = null;

while(true ) {

try{

cntSocket=(SSLSocket) svrSocket.accept();

ioReader= new BufferedReader(new InputStreamReader(cntSocket.getInputStream()));

ioWriter= new PrintWriter(cntSocket.getOutputStream());

while( (tmpMsg = ioReader.readLine()) != null) {

System.out.println("客户端通过SSL协议发送信息:"+tmpMsg);

tmpMsg="欢迎通过SSL协议连接";

ioWriter.println(tmpMsg);

ioWriter.flush();

}

}catch(IOException e) {

e.printStackTrace();

}finally {

try{

if(cntSocket!= null) cntSocket.close();

}catch(Exception ex) {ex.printStackTrace();}

}

}

}

publicstatic void main(String[] args) throws Exception {

TomcatSSLServer.getInstance().startService();

}

}

基本顺序是先得到一个SSLContext实例,再对SSLContext实例进行初始化,密钥管理器及信任管理器作为参数传入,证书管理器及信任管理器按照指定的密钥存储器路径和密码进行加载。接着设置支持的加密套件,最后让SSLServerSocket开始监听客户端发送过来的消息。

③  客户端TomcatSSLClient.java

public class TomcatSSLClient {

privatestatic final String SSL_TYPE = "SSL";

privatestatic final String X509 = "SunX509";

privatestatic final String KS_TYPE = "JKS";

privateSSLSocket sslSocket;

publicTomcatSSLClient(String targetHost,int port) throws Exception {

SSLContextsslContext = createSSLContext();

SSLSocketFactorysslcntFactory =(SSLSocketFactory) sslContext.getSocketFactory();

sslSocket= (SSLSocket) sslcntFactory.createSocket(targetHost, port);

String[]supported = sslSocket.getSupportedCipherSuites();

sslSocket.setEnabledCipherSuites(supported);

}

privateSSLContext createSSLContext() throws Exception{

KeyManagerFactorykmf = KeyManagerFactory.getInstance(X509);

TrustManagerFactorytmf = TrustManagerFactory.getInstance(X509);

StringclientKeyStoreFile = "c:\\client.jks";

StringcntPassphrase = "client";

char[]cntPassword = cntPassphrase.toCharArray();

KeyStoreclientKeyStore = KeyStore.getInstance(KS_TYPE);

clientKeyStore.load(newFileInputStream(clientKeyStoreFile),cntPassword);

StringserverKeyStoreFile = "c:\\tomcat.jks";

StringsvrPassphrase = "tomcat";

char[]svrPassword = svrPassphrase.toCharArray();

KeyStoreserverKeyStore = KeyStore.getInstance(KS_TYPE);

serverKeyStore.load(newFileInputStream(serverKeyStoreFile), svrPassword);

kmf.init(clientKeyStore,cntPassword);

tmf.init(serverKeyStore);

SSLContextsslContext  =SSLContext.getInstance(SSL_TYPE);

sslContext.init(kmf.getKeyManagers(),tmf.getTrustManagers(), null);

returnsslContext;

}

publicString sayToSvr(String sayMsg) throws IOException{

BufferedReaderioReader = new BufferedReader(new InputStreamReader(

sslSocket.getInputStream()));

PrintWriterioWriter = new PrintWriter(sslSocket.getOutputStream());

ioWriter.println(sayMsg);

ioWriter.flush();

returnioReader.readLine();

}

publicstatic void main(String[] args) throws Exception {

TomcatSSLClientsslSocket = new TomcatSSLClient("127.0.0.1",443);

BufferedReaderioReader = new BufferedReader(new InputStreamReader(System.in));

StringsayMsg = "";

StringsvrRespMsg= "";

while((sayMsg = ioReader.readLine())!= null ) {

svrRespMsg= sslSocket.sayToSvr(sayMsg);

if(svrRespMsg!= null && !svrRespMsg.trim().equals("")) {

System.out.println("服务器通过SSL协议响应:"+svrRespMsg);

}

}

}

}

客户端的前面操作基本跟服务器端的一样,先创建一个SSLContext实例,再用密钥管理器及信任管理器对SSLContext进行初始化,当然这里密钥存储的路径是指向客户端的client.jks。接着设置加密套件,最后使用SSLSocket进行通信。

注意服务器端有行代码svrSocket.setNeedClientAuth(true);它是非常重要的一个设置方法,用于设置是否验证客户端的身份。假如我们把它注释掉或设置为false,此时客户端将不再需要自己的密钥管理器,即服务器不需要通过client.jks对客户端的身份进行验证,把密钥管理器直接设置为null也可以跟服务器端进行通信。

最后谈谈信任管理器,它的职责是觉得是否信任远端的证书,那么它凭借什么去判断呢?如果不显式设置信任存储器的文件路径,将遵循如下规则:①如果系统属性javax.net.ssl.truststore指定了truststore文件,那么信任管理器将去jre路径下的lib/security目录寻找这个文件作为信任存储器;②如果没设置①中的系统属性,则去寻找一个%java_home%/lib/security/jssecacerts文件作为信任存储器;③如果jssecacerts不存在而cacerts存在,则cacerts作为信任存储器。

至此,一个利用JSSE实现SSL协议通信的例子已完成。

喜欢研究java的同学可以交个朋友,下面是本人的微信号:

Java安全套接字扩展——JSSE的更多相关文章

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

    Java 安全套接字编程以及 keytool 使用最佳实践 http://www.ibm.com/developerworks/cn/java/j-lo-socketkeytool/

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

    概述 利用 Java 的 JSSE(Java Secure Socket Extension)技术,我们可以方便的编写安全套接字程序,关于 JSSE 的介绍,可以参阅 Oracle 网站提供的 JSS ...

  3. 驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接

    由于项目中必须得用JDK6来作为Java环境,于是连接SQLServer时出现了com.microsoft.sqlserver.jdbc.SQLServerException: 驱动程序无法通过使用安 ...

  4. JDBC连接数据库遇到的“驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。

    要从旧算法列表中删除3DES: 在JDK 8及更早版本中,编辑该 /lib/security/java.security文件并3DES_EDE_CBC从jdk.tls.legacyAlgorithms ...

  5. 采用truelicense进行Java规划license控制 扩展可以验证后,license 开始结束日期,验证绑定一个给定的mac住址

    采用truelicense进行Java规划license控制 扩展可以验证后,license 开始结束日期,验证绑定一个给定的mac住址. Truelicense 它是一个开源java license ...

  6. Java高并发 -- 并发扩展

    Java高并发 -- 并发扩展 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 死锁 死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象, ...

  7. 第10章 网络安全(3)_安全套接字层SSL

    4. 安全套接字层 4.1 安全套接字层(SSL)和传输层安全(TLS) (1)SSL/TLS提供的安全服务 ①SSL服务器鉴别,允许用户证实服务器的身份.支持SSL的客户端通过验证来自服务器的证书, ...

  8. Java套接字

    前言: 本文补充一下Java关于套接字方面的内容,因为其应用相对比较简单,所以下面介绍两个程序实例. ------------------------------------------------- ...

  9. C# Java 3DES加密解密 扩展及修正\0 问题

    注: C#已亲测及做扩展, Java 部分未做验证 /// <summary> /// 3DES加密解密 /// ------------------------------------- ...

随机推荐

  1. bzoj 1058: [ZJOI2007]报表统计

    Description 小Q的妈妈是一个出纳,经常需要做一些统计报表的工作.今天是妈妈的生日,小Q希望可以帮妈妈分担一些工 作,作为她的生日礼物之一.经过仔细观察,小Q发现统计一张报表实际上是维护一个 ...

  2. poj 1696 叉积理解

    Space Ant Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 3967   Accepted: 2489 Descrip ...

  3. bzoj3437小P的牧场 斜率优化dp

    3437: 小P的牧场 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1542  Solved: 849[Submit][Status][Discus ...

  4. 树链剖分模板(BZOJ3083)

    实现了路径修改,子树查询,及换根. 换根其实很简单,分三种情况讨论,画画图就明白了. #include <cstdio> #include <algorithm> using ...

  5. 【完整项目】使用Scrapy模拟HTTP POST,获取完美名字

    1. 背景 最近有人委托我给小孩起个名字,说名字最好符合周易五行生克理论,然后给了我个网址,说像是这个网站中的八字测名,输入名字和生辰八字等信息,会给出来这个名字的分数和对未来人生的预测.当父母的自然 ...

  6. shell中read使用

    (1) 下面的语句从输入中读取n个字符并存入变量variable_name: read -n number_of_chars variable_name例如:[root@host1 shell]# r ...

  7. Python中模块之collections系列

    collection系列功能介绍 1. 常用的集中类 1. Counter(计数器) 计数器的常用方法如下: 创建一个字典计数器 格式:collections.Counter(obj) 例如:prin ...

  8. 博客迁移,新地址:bfsan.github.io

    博客的新内容会在新地址发布(暂时),后期可能会考虑做一个整合同步.

  9. PLSQL(1)

      PLSQLl编程                       plsql是Oracle在标准的sql语言上的扩展 特点:可以在数据库中定义变量,常量,还可以使用条件语句和判断语句以及异常等   P ...

  10. 《深入理解mybatis原理》 MyBatis的架构设计以及实例分析

    作者博客:http://blog.csdn.net/u010349169/article/category/2309433 MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简 ...