HTTPS,TLS/SSL
Hyper Text Transfer Protocol over Secure Socket Layer,安全的超文本传输协议,网景公式设计了SSL(Secure Sockets Layer)协议用于对Http协议传输的数据进行加密,保证会话过程中的安全性。
使用TCP端口默认为443
TLS:(Transport Layer Security,传输层安全协议),用于两个应用程序之间提供保密性和数据完整性。
SSL:(Secure Socket Layer,安全套接字层),位于可靠的面向连接的网络层协议和应用层协议之间的一种协议层。SSL通过互相认证、使用数字签名确保完整性、使用加密确保私密性,以实现客户端和服务器之间的安全通讯。
SSL协议即用到了对称加密也用到了非对称加密(公钥加密),在建立传输链路时,SSL首先对对称加密的密钥使用公钥进行非对称加密,链路建立好之后,SSL对传输内容使用对称加密。
对称加密 速度高,可加密内容较大,用来加密会话过程中的消息
公钥加密 加密速度较慢,但能提供更好的身份认证技术,用来加密对称加密的密钥
HTTPS单向认证
Https在建立Socket连接之前,需要进行握手,具体过程如下:
-
客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息;
-
服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书;
-
客户端使用服务端返回的信息验证服务器的合法性,包括:
-
证书是否过期;
-
发行服务器证书的CA是否可靠;(通过查询浏览器或本机内的CA证书)
-
返回的公钥是否能正确解开返回证书中的数字签名;(通过使用本机或浏览器内置的CA公钥进行解密)
-
服务器证书上的域名是否和服务器的实际域名相匹配;
-
验证通过后,将继续进行通信,否则,终止通信;
-
-
客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择;
-
服务器端在客户端提供的加密方案中选择加密程度最高的加密方式;
-
服务器将选择好的加密方案通过明文方式返回给客户端;
-
客户端接收到服务端返回的加密方式后,使用该加密方式生成产生随机码,用作通信过程中对称加密的密钥,使用服务端返回的公钥进行加密,将加密后的随机码发送至服务器;
-
服务器收到客户端返回的加密信息后,使用自己的私钥进行解密,获取对称加密密钥; 在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全;
HTTPS双向认证
双向认证和单向认证类似,它额外增加了服务端对客户端的认证:
-
客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息;
-
服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书;
-
客户端使用服务端返回的信息验证服务器的合法性,包括:
-
证书是否过期;
-
发行服务器证书的CA是否可靠;(通过查询浏览器或本机内的CA证书)
-
返回的公钥是否能正确解开返回证书中的数字签名;(通过使用本机或浏览器内置的CA公钥进行解密)
-
服务器证书上的域名是否和服务器的实际域名相匹配;
-
验证通过后,将继续进行通信,否则,终止通信;
-
-
服务端要求客户端发送客户端的证书即客户端证书公钥,客户端会将自己的证书发送至服务端;
-
验证客户端的证书,通过验证后,会获得客户端的公钥;
-
客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择
-
服务器端在客户端提供的加密方案中选择加密程度最高的加密方式;
-
将加密方案通过使用之前获取到的公钥进行加密,返回给客户端
-
客户端收到服务端返回的加密方案密文后,使用自己的私钥进行解密,获取具体加密方式,而后,产生该加密方式的随机码,用作加密过程中的密钥,使用之前从服务端证书中获取到的公钥进行加密后,发送给服务端;
-
服务端收到客户端发送的消息后,使用自己的私钥进行解密,获取对称加密的密钥,在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全;
java.https加载证书的API
一般的证书加载过程
-
用Certificate、KeyStore生成创建KeyManagerFactory和TrustManagerFactory
-
KeyManagerFactory和TrustManagerFactory用来创建KeyManager和TrustManager
-
而KeyManager和TrustManager用来初始化SSLContext
-
然后使用SSLContext,创建实际实现SSL/TLS协议的对象(SSLSocketFactory、SSLSocket或者SSLEngine)
-
SSLSocket和SSLEngine可以直接在通信对象中使用
-
KeyManager和TrustManager作用:
-
KeyManager负责向对等端显示使用的凭证(使用的密码标准、加密算法、证书、公钥、签名等)
-
TrustManager负责验证从对等端收到的凭证,验证凭证有多种方式:其中之一是创建CertPath对象,并让JDK的内置公钥基础结构(PKI)框架处理验证。 在内部,CertPath实现可能会创建一个Signature对象,并使用它来验证证书链中的每个签名
-
-
示例:生成SSLContext,并使用SSLContext初始化apache-httpClient
public static String postWithSSL(String url, String jsonBody) throws Exception {
SSLContext sslContext = getSslContext();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
sslContext, new String[]{"TLSv1.2", "TLSv1.1", "TLSv1"}, null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(3000)
.setSocketTimeout(3000)
.build();
CloseableHttpClient client = HttpClients.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.setDefaultRequestConfig(config).build();
HttpPost httpPost = new HttpPost(url);
//httpPost.setHeaders(headers);
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
httpPost.setHeader("Accept", "application/json");
httpPost.setEntity(new StringEntity(jsonBody, StandardCharsets.UTF_8));
HttpResponse response = client.execute(httpPost);
HttpEntity responseEntity = response.getEntity();
String result = EntityUtils.toString(responseEntity, "UTF-8");
return result;
}
//双向加密 SSLContext
private static SSLContext getSslContext() throws Exception {
//自身私钥
KeyStore identityKeyStore = KeyStore.getInstance("jks");
FileInputStream identityKeyStoreFile = new FileInputStream("/root/myServer.jks");
identityKeyStore.load(identityKeyStoreFile, "password1".toCharArray());
//服务端信任证书
KeyStore trustKeyStore = KeyStore.getInstance("jks");
FileInputStream trustKeyStoreFile = new FileInputStream("/root/trustKeyStore.jks");
trustKeyStore.load(trustKeyStoreFile, "password".toCharArray());
//构建SSLContexts
return SSLContexts.custom()
.loadKeyMaterial(identityKeyStore, "password1".toCharArray()) // load identity keystore
.loadTrustMaterial(trustKeyStore, null) // load trust keystore
.build();
}
//双向加密 SSLContext 方式二
private static SSLContext getSslContext2() throws Exception{
//自身私钥
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore keystore = KeyStore.getInstance("jks");
keystore.load(new FileInputStream(new File("/root/myServer.jks")), "password".toCharArray());
keyFactory.init(keystore, "password".toCharArray());
KeyManager[] keyManagers = keyFactory.getKeyManagers();
//服务端信任证书
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance("SunX509");
KeyStore tsStore = KeyStore.getInstance("jks");
tsStore.load(new FileInputStream(new File("/root/trustKeyStore.jks")), "password".toCharArray());
trustFactory.init(tsStore);
TrustManager[] trustManagers = trustFactory.getTrustManagers();
//初始化SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
return sslContext;
}
Java SSL 客户端和服务器示例
演示如何建立安全的 SSL 连接
案例一
服务器不校验客户端证书
SSL 服务器示例:
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
public class SSLServerExample {
public static void main(String[] args) {
int port = 8888; // 监听的端口
try {
// 创建 SSL 上下文
SSLContext sslContext = SSLContext.getInstance("TLS");
// 初始化 SSL 上下文,使用自签名证书
KeyStore keyStore = KeyStore.getInstance("JKS");
InputStream keystoreInputStream = SSLServerExample.class.getResourceAsStream("server_keystore.jks");
char[] password = "password".toCharArray();
keyStore.load(keystoreInputStream, password);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, password);
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
// 创建 SSLServerSocket
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
SSLServerSocket serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);
System.out.println("SSL Server is running...");
// 监听并处理连接
while (true) {
SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
System.out.println("Client connected: " + clientSocket.getInetAddress());
// 在这里处理客户端的输入输出流
// InputStream inputStream = clientSocket.getInputStream();
// OutputStream outputStream = clientSocket.getOutputStream();
// 关闭客户端连接
// clientSocket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
SSL 客户端示例:
当你在客户端建立 SSL 连接时,一个关键的步骤是验证服务器的证书,以确保你正在与预期的服务器进行通信,而不是受到中间人攻击。通常情况下,客户端会验证服务器证书的合法性,包括证书是否由受信任的证书颁发机构签发,以及证书是否与服务器域名匹配。
不校验服务器证书
import javax.net.ssl.*;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
public class SSLClientExample {
public static void main(String[] args) {
String serverAddress = "localhost"; // 服务器地址
int serverPort = 8888; // 服务器端口
try {
// 创建 SSL 上下文
SSLContext sslContext = SSLContext.getInstance("TLS");
// 初始化 SSL 上下文,信任所有证书
sslContext.init(null, new TrustManager[]{new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
}}, new java.security.SecureRandom());
// 创建 SSLSocket
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(serverAddress, serverPort);
System.out.println("Connected to server...");
// 在这里处理输入输出流
// InputStream inputStream = socket.getInputStream();
// OutputStream outputStream = socket.getOutputStream();
// 关闭连接
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在示例代码中,这一部分的目的是创建一个虚拟的 TrustManager,它会接受任何服务器证书,无论它是否由受信任的证书颁发机构签发。这是通过实现 X509TrustManager
接口的方法来实现的,但这些方法内部什么也不做,从而绕过了证书验证。
这种做法在测试环境中可能会有一些用途,但在实际生产环境中,强烈建议对服务器证书进行严格的验证,以确保与服务器的通信是安全的。如果不验证服务器证书,那么就容易受到中间人攻击,攻击者可以劫持通信并获取敏感信息。
验证服务器证书
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
public class MyTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
// 客户端证书验证逻辑,如果需要客户端证书验证
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) throws javax.security.cert.CertificateException {
// 服务器证书验证逻辑
for (X509Certificate cert : certs) {
try {
cert.checkValidity(); // 检查证书是否有效
// 在此处可以添加更多验证逻辑,例如是否由受信任的证书颁发机构签发,与预期的服务器域名匹配等
} catch (Exception e) {
throw new javax.security.cert.CertificateException("Server certificate is not valid.", e);
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
然后,在客户端代码中,将 MyTrustManager
实例传递给 SSLContext
的初始化方法:
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new MyTrustManager()}, new java.security.SecureRandom());
案例二
SSL 服务器端示例,验证客户端证书:
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
public class SSLServerWithClientCertificateExample {
public static void main(String[] args) {
int port = 8888; // 监听的端口
try {
// 创建 SSL 上下文
SSLContext sslContext = SSLContext.getInstance("TLS");
// 初始化 SSL 上下文,使用服务器证书和私钥
KeyStore keyStore = KeyStore.getInstance("jks");
InputStream keystoreInputStream = SSLServerWithClientCertificateExample.class.getResourceAsStream("server_keystore.jks");
char[] password = "password".toCharArray();
keyStore.load(keystoreInputStream, password);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, password);
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
// 创建 SSLServerSocket
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
SSLServerSocket serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);
System.out.println("SSL Server is running...");
// 监听并处理连接
while (true) {
SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
System.out.println("Client connected: " + clientSocket.getInetAddress());
// 客户端证书验证
clientSocket.startHandshake(); // 触发 SSL 握手,验证客户端证书
System.out.println("Client certificate validated.");
// 在这里处理客户端的输入输出流
// InputStream inputStream = clientSocket.getInputStream();
// OutputStream outputStream = clientSocket.getOutputStream();
// 关闭客户端连接
// clientSocket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
在这个示例中,我们使用了客户端发送的证书来进行客户端的认证。服务器端需要配置自己的证书和私钥(使用 server_keystore.jks
文件),以便进行安全的通信。在客户端握手之前,服务器会触发客户端证书验证,确保只有持有有效证书的客户端能够连接。
客户端证书验证通常包括以下几个步骤:
-
握手阶段触发验证: 在 SSL 握手阶段,当服务器请求客户端证书时,客户端会在
ClientKeyExchange
消息中发送其证书。服务器通过触发startHandshake()
方法来启动握手,从而触发客户端证书的验证。 -
证书链验证: 服务器会验证客户端证书的整个证书链,从客户端证书本身开始,逐级验证其上层证书,直到根证书(通常是 CA 证书)。验证的过程包括检查每个证书的有效性、颁发机构的合法性等。
-
证书有效性检查: 服务器会检查客户端证书的有效期,确保证书在其有效期内。如果证书过期,验证会失败。
-
颁发机构检查: 服务器会检查颁发客户端证书的证书颁发机构(CA),以确保 CA 受信任且有效。
-
主题匹配: 服务器会检查证书的主题信息(通常包括域名等)是否与服务器期望的客户端匹配。这有助于防止使用错误证书的客户端连接。
如果在客户端证书验证过程中任何一个步骤失败,服务器会拒绝客户端连接或在一些情况下终止连接。这样,服务器就确保了只有合法的客户端才能与之通信。
在实际代码中,当服务器调用 startHandshake()
方法时,它会自动处理证书的验证过程,你不需要在代码中显式编写验证逻辑。服务器端的示例代码中的以下行触发了客户端证书验证:
clientSocket.startHandshake(); // 触发 SSL 握手,验证客户端证书
这将会启动握手过程,并在握手中验证客户端的证书。如果证书验证失败,startHandshake()
方法会抛出相应的异常。
客户端使用一个 KeyStore 文件来配置信任的服务器证书,并且向服务器发送客户端证书以进行客户端认证
import javax.net.ssl.SSLContext;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
import java.security.KeyStore;
public class SSLClientWithCertificateExample {
public static void main(String[] args) {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
// 初始化 SSLContext,配置客户端证书和用于服务器证书验证的 TrustManager
KeyManager[] keyManagers = getKeyManagers();
TrustManager[] trustManagers = getTrustManagers();
sslContext.init(keyManagers, trustManagers, new java.security.SecureRandom());
// 使用 sslContext 创建 SSLSocket 或者 HttpClient 等
} catch (Exception e) {
e.printStackTrace();
}
}
private static KeyManager[] getKeyManagers() throws Exception {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("jks");
ks.load(new FileInputStream(new File("/root/clientKeyStore.jks")), "password".toCharArray());
keyManagerFactory.init(ks, "password".toCharArray());
return keyManagerFactory.getKeyManagers();
}
private static TrustManager[] getTrustManagers() throws Exception {
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance("SunX509");
KeyStore tsStore = KeyStore.getInstance("jks");
tsStore.load(new FileInputStream(new File("/root/trustKeyStore.jks")), "password".toCharArray());
trustFactory.init(tsStore);
return trustFactory.getTrustManagers();
}
}
参考:
Java Platform SE 8
一文看懂HTTPS、证书机构(CA)、证书、数字签名、私钥、公钥 - 简书文章来源:https://www.toymoban.com/news/detail-637115.html
基础篇:java.security框架之签名、加密、摘要及证书文章来源地址https://www.toymoban.com/news/detail-637115.html
到了这里,关于HTTPS安全通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!