如何让你的传输更安全--基于SSL协议的通信模式
之前发表在另一个平台的文章http://www.jointforce.com/jfperiodical/article/1218,现在发表到自己的博客上。
对于SSL/TLS协议,如果要每个开发者都自己去实现显然会带来不必要的麻烦,正是为了解决这个问题Java为广大开发者提供了Java安全套接字扩展——JSSE,它包含了实现Internet安全通信的一系列包的集合,是SSL和TLS的纯Java实现,同时它是一个开放的标准,每个公司都可以自己实现JSSE,通过它可以透明地提供数据加密、服务器认证、信息完整性等功能,就像使用普通的套接字一样使用安全套接字,大大减轻了开发者的负担,使开发者可以很轻松将SSL协议整合到程序中,并且JSSE能将安全隐患降到了最低点。
在利用SSL/TLS进行安全通信时,客户端跟服务器端都必须要支持SSL/TLS协议,不然将无法进行通信。而且客户端和服务器端都可能要设置用于证实自己身份的安全证书,并且还要设置信任对方的哪些安全证书。
关于身份认证方面有个名词叫客户端模式,一般情况客户端要对服务器端的身份进行验证,但是无需向服务器证实自己的身份,这样不用向对方证实自己身份的通信端我们就说它处于客户模式,否则成它处于服务器模式。SSLSocket的setUseClientMode(Boolean mode)方法可以设置客户端模式或服务器模式。
下面看具体实现的过程
① 解决证书问题。
一般而言作为服务器端必须要有证书以证明这个服务器的身份,并且证书应该描述此服务器所有者的一些基本信息,例如公司名称、联系人名等。证书由所有人以密码形式签名,基本不可伪造,证书获取的途径有两个:一是从权威机构购买证书,权威机构担保它发出的证书的真实性,而且这个权威机构被大家所信任,进而你可以相信这个证书的有效性;另外一个是自己用JDK提供的工具keytool创建一个自我签名的证书,这种情况下一般是我只想要保证数据的安全性与完整性,避免数据在传送的过程中被窃听或篡改,此时身份的认证已不重要,重点已经在端与端传输的秘密性上,证书的作用只体现在加解密签名。
SSL协议通信涉及密钥储存的文件格式比较多,很容易搞混,例如xxx.cer、xxx.pfx、xxx.jks、xxx.keystore、xxx.truststore等格式文件。如图3-1-7-3,搞清楚他们有助于理解后面的程序,.cer格式文件俗称证书,但这个证书中没有私钥,只包含了公钥;.pfx格式文件也称为证书,它一般供浏览器使用,而且它不仅包含了公钥,还包含了私钥,当然这个私钥是加密的,不输入密码是解不了密的;.jks格式文件表示java密钥存储器(java key 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 -storepass tomcat,不指定证书存储库的文件名及路径,则工具会在用户的home directory目录下生产一个“.keystore”文件作为证书存储库。
类似的,客户端证书也用此方式进行生成。如下
② 服务端TomcatSSLServer.java
public class TomcatSSLServer {
private static final String SSL_TYPE = "SSL";
private static final String KS_TYPE = "JKS";
private static final String X509 = "SunX509";
private final static int PORT = 443;
private static TomcatSSLServer sslServer;
private SSLServerSocket svrSocket;
public static TomcatSSLServer getInstance() throws Exception {
if (sslServer == null) {
sslServer = new TomcatSSLServer();
}
return sslServer;
}
private TomcatSSLServer() throws Exception{
SSLContext sslContext = createSSLContext();
SSLServerSocketFactory serverFactory = sslContext.getServerSocketFactory();
svrSocket =(SSLServerSocket) serverFactory.createServerSocket(PORT);
svrSocket.setNeedClientAuth(true);
String[] supported = svrSocket.getEnabledCipherSuites();
svrSocket.setEnabledCipherSuites(supported);
}
private SSLContext createSSLContext() throws Exception{
KeyManagerFactory kmf = KeyManagerFactory.getInstance(X509);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(X509);
String serverKeyStoreFile = "c:\\tomcat.jks";
String svrPassphrase = "tomcat";
char[] svrPassword = svrPassphrase.toCharArray();
KeyStore serverKeyStore = KeyStore.getInstance(KS_TYPE);
serverKeyStore.load(new FileInputStream(serverKeyStoreFile), svrPassword);
kmf.init(serverKeyStore, svrPassword);
String clientKeyStoreFile = "c:\\client.jks";
String cntPassphrase = "client";
char[] cntPassword = cntPassphrase.toCharArray();
KeyStore clientKeyStore = KeyStore.getInstance(KS_TYPE);
clientKeyStore.load(new FileInputStream(clientKeyStoreFile),cntPassword);
tmf.init(clientKeyStore);
SSLContext sslContext = SSLContext.getInstance(SSL_TYPE);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext;
}
public void startService() {
SSLSocket cntSocket = null;
BufferedReader ioReader = null;
PrintWriter ioWriter = null;
String tmpMsg = 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();}
}
}
}
public static void main(String[] args) throws Exception {
TomcatSSLServer.getInstance().startService();
}
}
基本顺序是先得到一个SSLContext实例,再对SSLContext实例进行初始化,密钥管理器及信任管理器作为参数传入,证书管理器及信任管理器按照指定的密钥存储器路径和密码进行加载。接着设置支持的加密套件,最后让SSLServerSocket开始监听客户端发送过来的消息。
③ 客户端TomcatSSLClient.java
public class TomcatSSLClient {
private static final String SSL_TYPE = "SSL";
private static final String X509 = "SunX509";
private static final String KS_TYPE = "JKS";
private SSLSocket sslSocket;
public TomcatSSLClient(String targetHost,int port) throws Exception {
SSLContext sslContext = createSSLContext();
SSLSocketFactory sslcntFactory =(SSLSocketFactory) sslContext.getSocketFactory();
sslSocket = (SSLSocket) sslcntFactory.createSocket(targetHost, port);
String[] supported = sslSocket.getSupportedCipherSuites();
sslSocket.setEnabledCipherSuites(supported);
}
private SSLContext createSSLContext() throws Exception{
KeyManagerFactory kmf = KeyManagerFactory.getInstance(X509);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(X509);
String clientKeyStoreFile = "c:\\client.jks";
String cntPassphrase = "client";
char[] cntPassword = cntPassphrase.toCharArray();
KeyStore clientKeyStore = KeyStore.getInstance(KS_TYPE);
clientKeyStore.load(new FileInputStream(clientKeyStoreFile),cntPassword);
String serverKeyStoreFile = "c:\\tomcat.jks";
String svrPassphrase = "tomcat";
char[] svrPassword = svrPassphrase.toCharArray();
KeyStore serverKeyStore = KeyStore.getInstance(KS_TYPE);
serverKeyStore.load(new FileInputStream(serverKeyStoreFile), svrPassword);
kmf.init(clientKeyStore, cntPassword);
tmf.init(serverKeyStore);
SSLContext sslContext = SSLContext.getInstance(SSL_TYPE);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext;
}
public String sayToSvr(String sayMsg) throws IOException{
BufferedReader ioReader = new BufferedReader(new InputStreamReader(
sslSocket.getInputStream()));
PrintWriter ioWriter = new PrintWriter(sslSocket.getOutputStream());
ioWriter.println(sayMsg);
ioWriter.flush();
return ioReader.readLine();
}
public static void main(String[] args) throws Exception {
TomcatSSLClient sslSocket = new TomcatSSLClient("127.0.0.1",443);
BufferedReader ioReader = new BufferedReader(new InputStreamReader(System.in));
String sayMsg = "";
String svrRespMsg= "";
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实现BIO模式的SSL协议通信的例子已完成。
喜欢java的同学交个朋友:
如何让你的传输更安全--基于SSL协议的通信模式的更多相关文章
- JAVA基础知识之网络编程——-基于UDP协议的通信例子
UDP是一种不可靠的协议,它在通信两端各建立一个socket,这两个socket不会建立持久的通信连接,只会单方面向对方发送数据,不检查发送结果. java中基于UDP协议的通信使用DatagramS ...
- Python中的端口协议之基于UDP协议的通信传输
UDP协议: 1.python中基于udp协议的客户端与服务端通信简单过程实现 2.udp协议的一些特点(与tcp协议的比较) 3.利用socketserver模块实现udp传输协议的并 ...
- 基于SSL协议的双向认证 - SSL协议 [1]
1 概要说明 在互联网通信方式中,目前用的最广泛的是HTTPS配合SSL和数字证书来保证传输和认证安全了. 2 详细介绍 2.1 HTTPS HTTPS全称:Hypertext Transf ...
- python中基于tcp协议的通信(数据传输)
tcp协议:流式协议(以数据流的形式通信传输).安全协议(收发信息都需收到确认信息才能完成收发,是一种双向通道的通信) tcp协议在OSI七层协议中属于传输层,它上承用户层的数据收发,下启网络层.数据 ...
- Android之基于HTTP协议的通信详解
Android系统中本身是有下载机制的,比如浏览器使用的DownloadManager.可遗憾的是,DownloadManager只提供给浏览器使用,一般的应用程序没法调用它. 另外,如果下载调用频繁 ...
- 基于SSL协议的双向认证 - 双向认证 [3]
1 SSL双向认证的实现 这里是基于SSL和Tomcat配置实现的,配置方法如下: 1.1 生成CA数字证书 首先需要配置OPENSSL环境变量. 我的OPENSSL配置文件路径是“D ...
- 基于TCP协议Socket通信
服务器线程处理类 package demo4; import java.io.*; import java.net.Socket; /** * 服务器线程处理类 * @ClassName Server ...
- Tftp文件传输服务器(基于UDP协议)
一个简单的UDP服务端与客户端 服务端: from socket import * #创建套接字 udp_server = socket(AF_INET,SOCK_DGRAM) msg_server ...
- 为何基于tcp协议的通信比基于udp协议的通信更可靠?
tcp协议一定是先建好双向链接,发一个数据包要得到确认才算发送完成,没有收到就一直给你重发:udp协议没有链接存在,udp直接丢数据,不管你有没有收到. TCP的可靠保证,是它的三次握手双向机制,这一 ...
随机推荐
- ListView常见的优化方式简述
ListView的优化 对于ListView来说,应该算是布局中几种最常用的组件之一了,使用也十分方便,下面个大家介绍一下两种常见的优化方式. 1.条目复用优化 其实listview的工作原理就是,l ...
- NestedScrollView嵌套ViewPager
NestedScrollView嵌套ViewPager 效果图 重写ViewPager package com.kongqw.kbox.view; import android.content.Con ...
- Android开发使用Java8新特性
Android 支持所有 Java 7 语言功能,以及一部分 Java 8 语言功能(具体因平台版本而异).本文介绍您可以使用的新语言功能.如何正确配置项目以使用这些功能,以及您可能遇到的任何已知问题 ...
- 深入浅出如何解析xml文件---上篇
xml小伙伴们并不陌生,xml是可扩展标记语言,标准通用标记语言语言的子集,是一种用来标记电子文件使其具有结构性的标记语言.我们知道xml可以用dom与sax等方法进行解析,但是xml为什么要解析呢? ...
- Java进阶(四十六)简述ArrayList、Vector与LinkedList的异同点
简述ArrayList.Vector与LinkedList的异同点 Collection类的继承图如下: 从图中可以看出,LinkedList与ArrayList.ArrayDeque这三者都 ...
- Java基本语法-----java常量
1常量的概述 常量是指在程序运行过程中其值不能改变的量. 2常量类型 Java中常量的分类: 整数常量 : 所有整数 小数常量 : 所有小数 布尔常量 : 只有true和false 字符常量 :使用' ...
- GDAL 2.0版本RPC校正速度测试
GDAL2.0版本的更新日志中提到了对RPC校正的优化,今天测试了一下,发现提升的速度还是蛮快的,测试的数据是一个IRS-P5的数据. 单线程测试 首先使用一个线程进行测试,使用下面的批处理进行运行, ...
- <<精通iOS开发>>第14章例子代码彻底清除警告
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 上一篇我们解决了<<精通iOS开发>> ...
- 【Netty源码学习】入门示例
Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序. 也就是说,Netty ...
- Android异常:android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original
Android异常:android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that cr ...