HTTPS安全通信

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连接之前,需要进行握手,具体过程如下:

 

  1. 客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息;

  2. 服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书;

  3. 客户端使用服务端返回的信息验证服务器的合法性,包括:

    • 证书是否过期;

    • 发行服务器证书的CA是否可靠;(通过查询浏览器或本机内的CA证书)

    • 返回的公钥是否能正确解开返回证书中的数字签名;(通过使用本机或浏览器内置的CA公钥进行解密)

    • 服务器证书上的域名是否和服务器的实际域名相匹配;

    • 验证通过后,将继续进行通信,否则,终止通信;

  4. 客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择;

  5. 服务器端在客户端提供的加密方案中选择加密程度最高的加密方式;

  6. 服务器将选择好的加密方案通过明文方式返回给客户端;

  7. 客户端接收到服务端返回的加密方式后,使用该加密方式生成产生随机码,用作通信过程中对称加密的密钥,使用服务端返回的公钥进行加密,将加密后的随机码发送至服务器;

  8. 服务器收到客户端返回的加密信息后,使用自己的私钥进行解密,获取对称加密密钥; 在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全;

HTTPS双向认证

双向认证和单向认证类似,它额外增加了服务端对客户端的认证:

  1. 客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息;

  2. 服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书;

  3. 客户端使用服务端返回的信息验证服务器的合法性,包括:

    • 证书是否过期;

    • 发行服务器证书的CA是否可靠;(通过查询浏览器或本机内的CA证书)

    • 返回的公钥是否能正确解开返回证书中的数字签名;(通过使用本机或浏览器内置的CA公钥进行解密)

    • 服务器证书上的域名是否和服务器的实际域名相匹配;

    • 验证通过后,将继续进行通信,否则,终止通信;

  4. 服务端要求客户端发送客户端的证书即客户端证书公钥,客户端会将自己的证书发送至服务端;

  5. 验证客户端的证书,通过验证后,会获得客户端的公钥;

  6. 客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择

  7. 服务器端在客户端提供的加密方案中选择加密程度最高的加密方式;

  8. 将加密方案通过使用之前获取到的公钥进行加密,返回给客户端

  9. 客户端收到服务端返回的加密方案密文后,使用自己的私钥进行解密,获取具体加密方式,而后,产生该加密方式的随机码,用作加密过程中的密钥,使用之前从服务端证书中获取到的公钥进行加密后,发送给服务端;

  10. 服务端收到客户端发送的消息后,使用自己的私钥进行解密,获取对称加密的密钥,在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全;

 

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 文件),以便进行安全的通信。在客户端握手之前,服务器会触发客户端证书验证,确保只有持有有效证书的客户端能够连接。

客户端证书验证通常包括以下几个步骤:

  1. 握手阶段触发验证: 在 SSL 握手阶段,当服务器请求客户端证书时,客户端会在 ClientKeyExchange 消息中发送其证书。服务器通过触发 startHandshake() 方法来启动握手,从而触发客户端证书的验证。

  2. 证书链验证: 服务器会验证客户端证书的整个证书链,从客户端证书本身开始,逐级验证其上层证书,直到根证书(通常是 CA 证书)。验证的过程包括检查每个证书的有效性、颁发机构的合法性等。

  3. 证书有效性检查: 服务器会检查客户端证书的有效期,确保证书在其有效期内。如果证书过期,验证会失败。

  4. 颁发机构检查: 服务器会检查颁发客户端证书的证书颁发机构(CA),以确保 CA 受信任且有效。

  5. 主题匹配: 服务器会检查证书的主题信息(通常包括域名等)是否与服务器期望的客户端匹配。这有助于防止使用错误证书的客户端连接。

如果在客户端证书验证过程中任何一个步骤失败,服务器会拒绝客户端连接或在一些情况下终止连接。这样,服务器就确保了只有合法的客户端才能与之通信。

在实际代码中,当服务器调用 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)、证书、数字签名、私钥、公钥 - 简书

基础篇:java.security框架之签名、加密、摘要及证书

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hello_中年人

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值