介: 本文主要介绍了网络安全通讯协议 SSL/TLS 和 Java 中关于安全通讯的实现部分。并通过一个简单的样例程序实现,来展示如何在 Java 平台上正确建立安全通讯。

在人类建立了通信系统之后,如何保证通信的安全始终是一个重要的问题。伴随着现代化通信系统的建立,人们利用数学理论找到了一些行之有效的方法来保证数字
通信的安全。简单来说就是把两方通信的过程进行保密处理,比如对双方通信的内容进行加密,这样就可以有效防止偷听者轻易截获通信的内容。目前
SSL(Secure Sockets Layer) 及其后续版本 TLS(Transport Layer
Security)是比较成熟的通信加密协议,它们常被用于在客户端和服务器之间建立加密通信通道。各种开发语言都给出 SSL/TLS
协议的具体实现,Java 也不例外。在 JDK 中有一个 JSSE(javax.net.ssl)包,提供了对 SSL 和 TLS
的支持。通过其所提供的一系列 API,开发者可以像使用普通 Socket 一样使用基于 SSL 或 TLS 的安全套接字,而不用关心 SSL 和
TLS 协议的细节,例如握手的流程等等。这使得利用 Java 开发安全的 SSL/TLS
服务器或客户端非常容易,本文将通过具体的例子来说明如何用 Java 语言来开发 SSL/TLS 应用。

SSL/TLS 协议的介绍

SSL/TLS 协议(RFC2246 RFC4346)处于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持。

从协议内部的功能层面上来看,SSL/TLS 协议可分为两层:

1. SSL/TLS 记录协议(SSL/TLS Record Protocol),它建立在可靠的传输层协议(如 TCP)之上,为上层协议提供数据封装、压缩、加密等基本功能。

2. SSL/TLS 握手协议(SSL/TLS Handshake Protocol),它建立在 SSL/TLS 记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等初始化协商功能。

从协议使用方式来看,又可以分成两种类型:

1. SSL/TLS
单向认证,就是用户到服务器之间只存在单方面的认证,即客户端会认证服务器端身份,而服务器端不会去对客户端身份进行验证。首先,客户端发起握手请求,服
务器收到握手请求后,会选择适合双方的协议版本和加密方式。然后,再将协商的结果和服务器端的公钥一起发送给客户端。客户端利用服务器端的公钥,对要发送
的数据进行加密,并发送给服务器端。服务器端收到后,会用本地私钥对收到的客户端加密数据进行解密。然后,通讯双方都会使用这些数据来产生双方之间通讯的
加密密钥。接下来,双方就可以开始安全通讯过程了。

2.SSL/TLS
双向认证,就是双方都会互相认证,也就是两者之间将会交换证书。基本的过程和单向认证完全一样,只是在协商阶段多了几个步骤。在服务器端将协商的结果和服
务器端的公钥一起发送给客户端后,会请求客户端的证书,客户端则会将证书发送给服务器端。然后,在客户端给服务器端发送加密数据后,客户端会将私钥生成的
数字签名发送给服务器端。而服务器端则会用客户端证书中的公钥来验证数字签名的合法性。建立握手之后过程则和单向通讯完全保持一致。

SSL/TLS 协议建立通讯的基本流程如图 1 所示,

图 1. SSL/TLS 基本流程图

步骤 1. ClientHello – 客户端发送所支持的 SSL/TLS 最高协议版本号和所支持的加密算法集合及压缩方法集合等信息给服务器端。

步骤 2. ServerHello – 服务器端收到客户端信息后,选定双方都能够支持的 SSL/TLS 协议版本和加密方法及压缩方法,返回给客户端。

(可选)步骤 3. SendCertificate – 服务器端发送服务端证书给客户端。

(可选)步骤 4. RequestCertificate – 如果选择双向验证,服务器端向客户端请求客户端证书。

步骤 5. ServerHelloDone – 服务器端通知客户端初始协商结束。

(可选)步骤 6. ResponseCertificate – 如果选择双向验证,客户端向服务器端发送客户端证书。

步骤 7. ClientKeyExchange – 客户端使用服务器端的公钥,对客户端公钥和密钥种子进行加密,再发送给服务器端。

(可选)步骤 8. CertificateVerify – 如果选择双向验证,客户端用本地私钥生成数字签名,并发送给服务器端,让其通过收到的客户端公钥进行身份验证。

步骤 9. CreateSecretKey – 通讯双方基于密钥种子等信息生成通讯密钥。

步骤 10. ChangeCipherSpec – 客户端通知服务器端已将通讯方式切换到加密模式。

步骤 11. Finished – 客户端做好加密通讯的准备。

步骤 12. ChangeCipherSpec – 服务器端通知客户端已将通讯方式切换到加密模式。

步骤 13. Finished – 服务器做好加密通讯的准备。

步骤 14. Encrypted/DecryptedData – 双方使用客户端密钥,通过对称加密算法对通讯内容进行加密。

步骤 15. ClosedConnection – 通讯结束后,任何一方发出断开 SSL 连接的消息。

除了以上的基本流程,SSL/TLS 协议本身还有一些概念需要在此解释说明一下。

Key:Key 是一个比特(bit)字符串,用来加密解密数据的,就像是一把开锁的钥匙。

对称算法(symmetric cryptography):就是需要双方使用一样的 key
来加密解密消息算法,常用密钥算法有 Data Encryption Standard(DES)、triple-strength
DES(3DES)、Rivest Cipher 2 (RC2)和 Rivest Cipher 4(RC4)。因为对称算法效率相对较高,因此
SSL 会话中的敏感数据都用通过密钥算法加密。

非对称算法(asymmetric cryptography):就是 key
的组成是公钥私钥对 (key-pair),公钥传递给对方私钥自己保留。公钥私钥算法是互逆的,一个用来加密,另一个可以解密。常用的算法有
Rivest Shamir
Adleman(RSA)、Diffie-Hellman(DH)。非对称算法计算量大比较慢,因此仅适用于少量数据加密,如对密钥加密,而不适合大量数
据的通讯加密。

公钥证书(public key certificate):公钥证书类似数字护照,由受信机构颁
发。受信组织的公钥证书就是 certificate
authority(CA)。多证书可以连接成证书串,第一个是发送人,下一个是给其颁发证书实体,往上到根证书是世界范围受信组织,包括
VeriSign, Entrust, 和 GTE CyberTrust。公钥证书让非对称算法的公钥传递更安全,可以避免身份伪造,比如 C
创建了公钥私钥,对并冒充 A 将公钥传递给 B,这样
C 与 B 之间进行的通讯会让 B 误认是 A 与 B 之间通讯。

加密哈希功能(Cryptographic Hash Functions): 加密哈希功能与
checksum 功能相似。不同之处在于,checksum
用来侦测意外的数据变化而前者用来侦测故意的数据篡改。数据被哈希后产生一小串比特字符串,微小的数据改变将导致哈希串的变化。发送加密数据时,SSL
会使用加密哈希功能来确保数据一致性,用来阻止第三方破坏通讯数据完整性。SSL 常用的哈希算法有 Message Digest 5(MD5)和
Secure Hash
Algorithm(SHA)。

消息认证码(Message Authentication Code): 消息认证码与加密哈希
功能相似,除了它需要基于密钥。密钥信息与加密哈希功能产生的数据结合就是哈希消息认证码(HMAC)。如果 A 要确保给 B 发的消息不被 C
篡改,他要按如下步骤做 --A 首先要计算出一个 HMAC 值,将其添加到原始消息后面。用 A 与 B 之间通讯的密钥加密消息体,然后发送给
B。B 收到消息后用密钥解密,然后重新计算出一个 HMAC,来判断消息是否在传输中被篡改。SSL
用 HMAC 来保证数据传输的安全。

数字签名(Digital Signature):一个消息的加密哈希被创建后,哈希值用发送者的私钥加密,加密的结果就是叫做数字签名。

 

回页首

JSSE(Java Secure Socket Extension)使用介绍

在 Java SDK 中有一个叫 JSSE(javax.net.ssl)包,这个包中提供了一些类来建立 SSL/TLS 连接。通过这些类,开发者就可以忽略复杂的协议建立流程,较为简单地在网络上建成安全的通讯通道。JSSE 包中主要包括以下一些部分:

  • 安全套接字(secure socket)和安全服务器端套接字
  • 非阻塞式 SSL/TLS 数据处理引擎(SSLEngine)
  • 套接字创建工厂 , 用来产生 SSL 套接字和服务器端套接字
  • 套接字上下文 , 用来保存用于创建和数据引擎处理过程中的信息
  • 符合 X.509 规范密码匙和安全管理接口

下面将通过一个简单的例子来展示如何通过 JSSE,在客户端和服务器端建立一个 SSL/TLS 连接。设计两个类 SSLClient 和
SSLServer,分别来表示客户端和服务器端。客户端将会向服务器端发起连接请求,在通过服务器端验证建立 SSL
连接后,服务器端将会向客户端发送一串内容,客户端将会把收到的内容打印出来。样例代码如下,

SSLClient Source code:

 package example.ssl.codes; 

 import java.io.*;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory; class SSLClient { private SSLSocket socket = null; public SSLClient() throws IOException {
// 通过套接字工厂,获取一个客户端套接字
SSLSocketFactory socketFactory = (SSLSocketFactory)
SSLSocketFactory.getDefault();
socket = (SSLSocket) socketFactory.createSocket("localhost", 7070);
} public void connect() {
try {
// 获取客户端套接字输出流
PrintWriter output = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream()));
// 将用户名和密码通过输出流发送到服务器端
String userName = "principal";
output.println(userName);
String password = "credential";
output.println(password);
output.flush(); // 获取客户端套接字输入流
BufferedReader input = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
// 从输入流中读取服务器端传送的数据内容,并打印出来
String response = input.readLine();
response += "\n " + input.readLine();
System.out.println(response); // 关闭流资源和套接字资源
output.close();
input.close();
socket.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} finally {
System.exit(0);
}
} public static void main(String args[]) throws IOException {
new SSLClient().connect();
}
}

SSLServer Source code:

 package example.ssl.codes; 

 import java.io.*;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket; class SSLServer { // 服务器端授权的用户名和密码
private static final String USER_NAME = "principal";
private static final String PASSWORD = "credential";
// 服务器端保密内容
private static final String SECRET_CONTENT =
"This is confidential content from server X, for your eye!"; private SSLServerSocket serverSocket = null; public SSLServer() throws Exception {
// 通过套接字工厂,获取一个服务器端套接字
SSLServerSocketFactory socketFactory = (SSLServerSocketFactory)
SSLServerSocketFactory.getDefault();
serverSocket = (SSLServerSocket)socketFactory.createServerSocket(7070);
} private void runServer() {
while (true) {
try {
System.out.println("Waiting for connection...");
// 服务器端套接字进入阻塞状态,等待来自客户端的连接请求
SSLSocket socket = (SSLSocket) serverSocket.accept(); // 获取服务器端套接字输入流
BufferedReader input = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
// 从输入流中读取客户端用户名和密码
String userName = input.readLine();
String password = input.readLine(); // 获取服务器端套接字输出流
PrintWriter output = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream())); // 对请求进行认证,如果通过则将保密内容发送给客户端
if (userName.equals(USER_NAME) && password.equals(PASSWORD)) {
output.println("Welcome, " + userName);
output.println(SECRET_CONTENT);
} else {
output.println("Authentication failed, you have no
access to server X...");
} // 关闭流资源和套接字资源
output.close();
input.close();
socket.close(); } catch (IOException ioException) {
ioException.printStackTrace();
}
}
} public static void main(String args[]) throws Exception {
SSLServer server = new SSLServer();
server.runServer();
}
}

SSL 样例程序:

 java -cp ./build/classes example.ssl.codes.SSLServer
java -cp ./build/classes example.ssl.codes.SSLClient

执行结果如下:

服务器端输出:

 Waiting for connection...
javax.net.ssl.SSLHandshakeException: no cipher suites in common
Waiting for connection...
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1836)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:266)

客户端输出:

 javax.net.ssl.SSLException: Connection has been shutdown:
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at sun.security.ssl.SSLSocketImpl.checkEOF(SSLSocketImpl.java:1426)
at sun.security.ssl.AppInputStream.read(AppInputStream.java:92)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:154)
at java.io.BufferedReader.readLine(BufferedReader.java:317)
at java.io.BufferedReader.readLine(BufferedReader.java:382)
at example.ssl.codes.SSLClient.connect(SSLClient.java:29)
at example.ssl.codes.SSLClient.main(SSLClient.java:44)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1911)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1027)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake
(SSLSocketImpl.java:1262)
at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:680)
at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:85)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:295)
at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141)
at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229)
at java.io.PrintWriter.flush(PrintWriter.java:320)
at example.ssl.codes.SSLClient.connect(SSLClient.java:25)
... 1 more

通过程序的错误输出,我们能够发现 SSL 建立失败了,在握手阶段双方没有能够协商出加密方法等信息。这是因为默认情况下,java 虚拟机没有与 SSL 相关的配置,需要开发者自己按照文档进行一些配置。在 JDK 中提供了一个安全钥匙与证书的管理工具 Keytool。Keytool 把钥匙,证书以及和与它们相关联的证书链储存到一个 keystore 中,默任的实现 keystore 的是一个文件,它本身有一个访问密码来保护存储在其中的内容。就本样例程序而言,只需要配置客户端和服务器端双方信任就可以了。可以按照如下几步来完成:

1. 进入本地的 java 安装位置的 bin 目录中 cd /java/bin

2. 创建一个客户端 keystore 文件,如图 2 所示

keytool -genkey -alias sslclient -keystore sslclientkeys

图 2. 创建 keystore 文件

3. 将客户端 keystore 文件导出成证书格式

keytool -export -alias sslclient -keystore sslclientkeys -file sslclient.cer

4. 创建一个服务器端 keystore 文件

keytool -genkey -alias sslserver -keystore sslserverkeys

5. 将服务器端 keystore 文件导出成证书格式

keytool -export -alias sslserver -keystore sslserverkeys -file sslserver.cer

6. 将客户端证书导入到服务器端受信任的 keystore 中

 keytool -import -alias sslclient -keystore sslservertrust -file sslclient.cer

7. 将服务器端证书导入到客户端受信任的 keystore 中

 keytool -import -alias sslserver -keystore sslclienttrust -file sslserver.cer

以上所有步骤都完成后,还可以通过命令来查看 keystore 文件基本信息,如图 3 所示

keytool -list -keystore sslclienttrust

图 3. 查看 keystore 文件

将前面创建的所有 keystore 文件从 java 的 bin 目录中剪切出来,移动到样例程序的执行目录中,通过运行程序时候的系统属性来指定这些文件,重新执行一遍样例程序。

  java -cp ./build/classes
-Djavax.net.ssl.keyStore=sslserverkeys
-Djavax.net.ssl.keyStorePassword=123456
-Djavax.net.ssl.trustStore=sslservertrust
-Djavax.net.ssl.trustStorePassword=123456
example.ssl.codes.SSLServer
java -cp ./build/classes
-Djavax.net.ssl.keyStore=sslclientkeys
-Djavax.net.ssl.keyStorePassword=123456
-Djavax.net.ssl.trustStore=sslclienttrust
-Djavax.net.ssl.trustStorePassword=123456
example.ssl.codes.SSLClient

执行结果如下:

客户端输出

Welcome, principal
This is confidential content from server X, for your eye!

客户端与服务器端成功建立起 SSL 的连接,然后服务器端成功将字符串发送给客户端,客户端将其打印出来。

项目源码:http://download.csdn.net/detail/cyl937/6005991

原文参考:http://www.ibm.com/developerworks/cn/java/j-lo-ssltls/

http://blog.csdn.net/cyl937/article/details/10285761

Https socket 连接的更多相关文章

  1. tomcat通过socket连接MySQL,不再占用服务端口【linux】

    MySQL连接方式的说明 http://icbm.iteye.com/blog/1840673 MySQL除了最常见的TCP连接方式外,还提供SOCKET(LINUX默认连接方式).PIPE和SHAR ...

  2. 转 Cocos网络篇[3.2](3) ——Socket连接(1)

    Cocos网络篇[3.2](3) ——Socket连接(1) 2015-03-05 22:24:13 标签:network http socket cocos [唠叨] 在客户端游戏开发中,使用HTT ...

  3. TCP/IP Http 和Https socket之间的区别

    TCP/IP Http 和Https  socket之间的区别 TCP/IP是个协议组,它分为网络层,传输层和应用层, 在网络层有IP协议.ICMP协议.ARP协议.RARP协议和BOOTP协议.   ...

  4. PHP 设置 socket连接

    摘要: 作者博文地址:https://www.cnblogs.com/liu-shuai/ nginx和fastcgi的通信方式有两种,一种是TCP的方式,一种是unix socket方式. sock ...

  5. 浅谈IM软件怎样建立安全socket连接、登录

    ----------------------------------------------------欢迎查看IM软件业务知识<专栏>-------------------------- ...

  6. HTML5 WebSocket与C# 建立Socket连接

    一.WebSocket 概述 WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议. WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务 ...

  7. 切实解决socket连接掉线检测

    原文:切实解决socket连接掉线检测 版权声明:欢迎转载,但是请保留出处说明 https://blog.csdn.net/lanwilliam/article/details/51698807 新公 ...

  8. Socket连接和Http连接

    Socket连接与HTTP连接我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有 ...

  9. 安卓Socket连接实现连接实现发送接收数据,openwrt wifi转串口连接单片机实现控制

    安卓Socket连接实现连接实现发送接收数据,openwrt wifi转串口连接单片机实现控制 socket 连接采用流的方式进行发送接收数据,采用thread线程的方式. 什么是线程?  详细代码介 ...

随机推荐

  1. shell 脚本规范

    shell 脚本规范 一.背景 1.使用哪一种shell? 必须使用bash shell 2.什么时候使用shell? 数量相对较少的操作 脚本文件少于100行 3.脚本文件扩展名是什么? shell ...

  2. 快速部署业务类为webapi服务

    接着前一篇博文,将接口快速打包固定请求格式,不需要修改代码,可以自动完成接口调用,实际上就是生成了一个接口的代理类. 那么仅仅是接口请求代理,没有服务端怎么行?所以需要将实现接口的类部署为webapi ...

  3. JavaScript 正则表达式-严格匹配

    创建语法 var pattern = /test/;   或者  var pattern =  new RegExp("test"); 可配合使用以下三个标志 1) .i  可以使 ...

  4. java super与this关键字图解、java继承的三个特点

  5. java两个引用指向同一个对象

  6. Django前后端分离跨域请求问题

    一.问题背景 之前使用django+vue进行前后端分离碰到跨域请求问题,跨域(域名或者端口不同)请求问题的本质是由于浏览器的同源策略导致的,当请求的响应不是处于同一个域名和端口下,浏览器不会接受响应 ...

  7. HugeGraph图数据库--测试

    2018年百度的HugeGraph.实现了Apache TinkerPop3框架及完全兼容Gremlin查询语言.开源项目https://github.com/hugegraph HugeGraph典 ...

  8. java 比较两个日期大小(2) 用before(), after()

    调试代码,我就不整理了,记下after()  before() 觉得这张图好美,从人家的博客上截的,找不到链接了

  9. Adblock Plus 添加过滤规则

    过滤掉相关的DIV 如要过滤某网站的 如例1:  home.firefoxchina.cn##div#module-game##元素#名字 过滤掉ID为名字的元素##div.名字 class为名字的D ...

  10. BiNGO的GO分析

    GO富集分析对老师们来说想必都不陌生,几乎在任何项目中都会出现.今天就给大家介绍一款简单易学又好用的富集分析小软件---BiNGO.它是Cytoscape软件中很出色的一个插件.它提供的结果中除了文本 ...