1.SSL安全套接层
SSL:(Secure Socket Layer) 安全套接层,于 1994 年由网景公司设计,并于 1995 年发布了 3.0 版本
TLS:(Transport Layer Security)传输层安全性协议,是 IETF 在 SSL3.0 的基础上设计的协议
这两种协议总体差别不大,实现的功能类似,以下都以SSL统称。
ssl处于网络层次中的会话层,位于传输层TCP之上,相比于TCP层上的Socke的连接,SSL上是SSLSocket,基于socket但比socket安全,安全在于认证通信两端身份、加密传输应用数据。
之前https://blog.csdn.net/sarafina527/article/details/89449006 TCP的通信,SSL是比TCP多了一层握手和加密传输
1.1 服务端实现
package communication.sslSocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.security.KeyStore;
import java.util.Arrays;
public class SSlServer {
private static final Logger logger = LoggerFactory.getLogger(SSlServer.class);
private static final char[] ksPass = "123456".toCharArray();
private static final char[] keyPass = "123456".toCharArray();
private static final int port = 8443;
public static void main(String[] args) throws Exception{
// 类似于serverSocket
SSLServerSocket sslServerSocket = createSSLServerSocket();
// 等待客户端连接 阻塞
SSLSocket socket = (SSLSocket) sslServerSocket.accept();
logger.info("客户端连接 " + socket.getInetAddress()+ " " +socket.getPort());
// 读取
InputStreamReader reader = new InputStreamReader(socket.getInputStream());
BufferedReader br = new BufferedReader(reader);
// 写入
PrintWriter writer = new PrintWriter(socket.getOutputStream());
String msg = br.readLine();
try{
while (true) {
while (msg != null) {
if("bye".equals(msg)) {
break;
}
System.out.println(msg);
writer.println(msg);
writer.flush();
msg = br.readLine();
}
}
}finally {
if(socket != null) {
socket.close();
}
}
}
// 创建服务端socket
public static SSLServerSocket createSSLServerSocket() throws Exception{
SSLContext sslContext = createContext();
SSLServerSocketFactory sslssf = sslContext.getServerSocketFactory();
// 绑定服务端口
SSLServerSocket serverSocket = (SSLServerSocket) sslssf.createServerSocket(port);
// 设置加密算法套件
String[] supported = sslssf.getSupportedCipherSuites();
serverSocket.setEnabledCipherSuites(supported);
logger.info("服务端可支持的加密算法套件有:" + Arrays.toString(supported));
return serverSocket;
}
// 创建上下文环境,主要用于保存安全通信的基本信息,如协议版本、证书管理方式
public static SSLContext createContext() throws Exception{
logger.info("开始服务端创建sslContext上下文......" );
SSLContext sslContext = SSLContext.getInstance("TLS");
logger.info(sslContext.getProtocol());
// 管理本端 的密钥(发送本端的证书用于对方认证我的身份)
logger.info(KeyManagerFactory.getDefaultAlgorithm());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// 管理对端证书的信任证书,用于认证对方的身份
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X.509");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("/Users/wangying49/certs/sslTestServer.jks"),ksPass);
kmf.init(ks,keyPass);
tmf.init(ks); // 信任证书和服务端私钥 放在同一个库里
// 上下文 初始化、两端证书的加载
sslContext.init(kmf.getKeyManagers(),tmf.getTrustManagers(),null);
logger.info("上下文信息:" + sslContext.getProtocol() + sslContext.getSocketFactory());
return sslContext;
}
}
1.2 客户端
package communication.sslSocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.io.*;
import java.security.KeyStore;
/**
* 〈一句话功能简述〉<br>
* 〈避免需要证书用于进行Https请求的HttpClient 〉
*/
public class SSLClient {
private static final Logger logger = LoggerFactory.getLogger(SSLClient.class);
private static final char[] ksPass = "123456".toCharArray();
private static final char[] keyPass = "123456".toCharArray();
private static final String protocol = "TLS";
public static void main(String[] args) throws Exception{
String host = "localhost";
String charset = "utf-8";
int port = 8443;
String msg = "hello ssl";
BufferedReader reader = null;
BufferedWriter writer = null;
SSLSocketFactory ssf = createSocketFactory();
SSLSocket socket = (SSLSocket) ssf.createSocket(host, port);
socket.setSoTimeout(10000);
msg = socket.getNeedClientAuth() + " " ;
// 写入请求
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), charset));
logger.info("send data to {}:{}", new Object[] { host, port });
logger.info("发送 REQUEST:{}", msg);
writer.write(msg + "\n");
writer.flush();// 在真正发送的时候才会握手
// 读取响应
reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), charset));
String result = reader.readLine();
logger.info("接收响应 :" + result);
}
@SuppressWarnings("unchecked")
public static SSLSocketFactory createSocketFactory() throws Exception
{
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("/Users/wangying49/certs/sslTestClient.jks"), ksPass);
// sun.security.validator.ValidatorException: PKIX path building failed: 选择不匹配的信任证书
// ks.load(new FileInputStream("/Users/wangying49/certs/ceb0305.jks"), ksPass);
if (ks == null) {
throw new Exception("没有证书库配置");
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X.509");
tmf.init(ks);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyPass); // 这里是私钥的密码
//
SSLContext ctx = SSLContext.getInstance(protocol);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLSocketFactory ssf = ctx.getSocketFactory();
return ssf;
}
}
1.3 执行结果
上文代码中的证书库里的证书是两对匹配的密钥,如sslTestServer.jks存放的是服务端的私钥,和客户端的信任证书,由于此处是用的自签名的证书,所以信任证书就是客户端证书
客户端的证书库存放的是客户端的私钥和服务端的信任证书
匹配时效果与普通的TCP通信差不多,但是当证书不匹配时,如我将客户端的证书库更换了,(见代码注释内容),就会报如下错误:
可见多了一个握手的过程,默认使用X509TrustManagerImpl来校验服务端证书