Https详解

Https详解

一、Https介绍

https(全称:Hypertext Transfer Protocol Secure),是以安全为目标的 HTTP 通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性。

  • HTTPS 是一种应用层协议,是一种透过计算机网络进行安全通信的传输协议。
  • HTTPS 经由 HTTP 进行通信,但是在 HTTP 的基础上引入了一个加密层,使用 SSL/TLS 来加密数据包。
  • HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。
  • HTTPS 默认工作在 TCP 协议443端口(HTTP默认80端口)

既然要保证数据安全,就需要进行“加密”,即网络传输中不再直接传输明文,而是加密之后的“密文”。加密的方式有很多,但是整体可以分为两大类:对称加密非对称加密

二、相关名词解释

  • 明文:要传输的原始的消息

  • 密文:通过一定的规则将明文变换后的内容

  • 加密:将明文变成密文

  • 解密:将密文变成明文

  • 密钥:在加密和解密的过程中,往往需要一个或多个中间的数据来辅助该过程,这样的数据称为密钥

  • 对称加密与非对称加密:

    • 对称加密:又称为传统密码算法,加密密钥和解密密钥是相同的。对称加密算法要求通信双方在开始通信前,要首先商定一个用于加密和解密的密钥。算法的安全性就依赖于这个密钥,如果这个密钥被泄露了,就意味着通信不再安全。

    • 非对称加密:非对称加密算法的加密密钥是公开的,理论上任何人都可以获得这个公开的加密密钥进行数据加密。但是,使用公开的加密密钥加密的信息只有相应的解密密钥才能解开,而这个解密密钥一般是不公开的。在非对称加密算法中,加密密钥也叫公钥,解密密钥称为私钥

  • 公钥与私钥:

    • 公钥和私钥成对出现
    • 公开的密钥叫公钥,只有自己知道的叫私钥
    • 用公钥加密的数据只有对应的私钥可以解密
    • 用私钥加密的数据只有对应的公钥可以解密
    • 如果可以用公钥解密,则必然是对应的私钥加的密
    • 如果可以用私钥解密,则 必然是对应的公钥加的密

举个栗子:

假设一下,我找了两个数字,一个是1,一个是2。我喜欢2这个数字,就保留起来,不告诉你们,然后我告诉大家,1是我的公钥。

我有一个文件,不能让别人看,我就用1加密了。别人找到了这个文件,但是他不知道2就是解密的私钥啊,所以他解不开,只有我可以用数字2,就是我的私钥,来解密。这样我就可以保护数据了。

我的好朋友用我的公钥1加密了文件a,加密后成了b,放在网上。别人偷到了这个文件,但是别人解不开,因为别人不知道2就是我的私钥,只有我才能解密,解密后就得到a。这样,我们就可以传送加密的数据了。

我的好朋友说有人冒充我给他发消息,怎么杜绝这种情况呢,让他来判断这个消息到底是不是由我发出的呢?
我把我要发的消息,内容是c,用我的私钥2加密,加密后的内容是d,发给我的朋友,再告诉他解密看是不是c。他用我的公钥1解密,发现果然是c。这个时候,他会想到,能够用我的公钥解密的数据,必然是用我的私钥加的密。只有我知道我得私钥,因此他就可以确认确实是我发的东西。这样我们就能确认发送方身份了。

总结来说,就是四点:

  • 公钥私钥成对出现
  • 私钥只有我知道
  • 大家可以用我的公钥给我发加密的消息,我用私钥可以解开,得到你发送的原始消息
  • 大家用我的公钥解密信的内容,看看能不能解开, 能解开,说明是经过我的私钥加密了,就可以确认确实是我发的

用公钥加密数据,用私钥来解密数据,用私钥加密数据(数字签名),用公钥来验证数字签名;在实际的使用中,公钥不会单独出现,总是以数字证书的方式出现。

三、SSL/TLS

SSL(Secure Sockets Layer 安全套接字协议),及其继任者 - 传输层安全(Transport Layer Security,TLS)是为网络通信提供 安全数据完整性 的一种安全协议。TLS与SSL在 传输层与应用层之间 对网络连接进行加密。

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

SSL协议可分为两层:

  • SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
  • SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。
  • 警报协议:解决出现的问题

SSL提供的服务:

  • 认证用户和服务器,确保数据发送到正确的客户机和服务器;

  • 加密数据以防止数据中途被窃取;

  • 维护数据的完整性,确保数据在传输过程中不被改变。

四、Https双向认证

img

  • 客户端发起建立HTTPS连接请求,将SSL协议版本的信息发送给服务端;

  • 服务器端将本机的公钥证书(server.crt)发送给客户端;

  • 客户端读取公钥证书(server.crt),取出了服务端公钥;

  • 客户端将客户端公钥证书(client.crt)发送给服务器端;

  • 服务器端使用根证书(root.crt)解密客户端公钥证书,拿到客户端公钥;

  • 客户端发送自己支持的加密方案给服务器端;

  • 服务器端根据自己和客户端的能力,选择一个双方都能接受的加密方案,使用客户端的公钥加密后发送给客户端;

  • 客户端使用自己的私钥解密加密方案,生成一个随机数R,使用服务器公钥加密后传给服务器端;

  • 服务端用自己的私钥去解密这个密文,得到了密钥R

  • 服务端和客户端在后续通讯过程中就使用这个密钥R进行通信

根证书:是生成服务器证书和客户端证书的基础,是信任的源头,也可以叫自签发证书,即CA证书。

服务器证书:由根证书签发,并发送给客户,让客户安装在浏览器里的证书。主要包含服务端的公钥、域名和公司信息,浏览器客户端会验证自己请求的地址是否和证书里面的地址是否相同。

客户端证书:由根证书签发,需要导入到服务器的信任库中。主要包含客户端公钥、域名和公司信息。
1、单向认证:如果是你客户端,你需要拿到服务器的证书,并放到你的信任库中;如果是服务端,你要生成私钥和证书,并将这两个放到你的密钥库中,并且将证书发给所有客户端。

2、双向认证:如果你是客户端,你要生成客户端的私钥和证书,将它们放到密钥库中,并将证书发给服务端,同时,在信任库中导入服务端的证书。如果你是服务端,除了在密钥库中保存服务器的私钥和证书,还要在信任库中导入客户端的证书。

3、使用单向验证还是双向验证,是服务器决定的。
单向认证是只在客户端侧做证书校验,双向认证客户端和服务端都要做对方的证书校验。

五、自签名证书

CA(这一步是CA机构操作,我们自己代劳而已)

# 生成CA秘钥对
openssl genrsa -out ca.key 2048

# 生成CA证书csr签发文件
openssl req -new -out ca.csr -key ca.key -subj "/C=CN/ST=Guangdong/L=Shenzhen/CN=root"

# 生成X509格式CA根证书
openssl x509 -req -in ca.csr -out ca.crt -signkey ca.key -CAcreateserial -days 3650

# CA证书转为P12格式
openssl pkcs12 -export -clcerts -in ca.crt -inkey ca.key -name "ca" -out ca.p12

Server

# 生成服务端秘钥对
openssl genrsa -out server.key 2048

# 生成服务器端公钥
openssl rsa -in server.key -pubout -out server.pem

# 生成服务端csr签发文件
openssl req -new -out server.csr -key server.key -subj "/C=CN/ST=Guangdong/L=Shenzhen/CN=haoren.com" 

# 生成服务端crt证书
openssl x509 -req -in server.csr -signkey server.key -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out server.crt

# 生成服务端p12格式证书,需要输入一个密码(这一步也不是必须,只是用在某些场合方便而已)
openssl pkcs12 -export -clcerts -in server.crt -inkey server.key -name "server" -out server.p12
  • server.key:服务端秘钥对。
  • server.csr:证书签发文件。
  • srever.crt:有效期十年的服务器端公钥证书,使用根证书和服务器端私钥文件一起生成。

Client

# 生成客户端秘钥对
openssl genrsa -out client.key 2048

# 生成客户端公钥
openssl rsa -in client.key -pubout -out client.pem

# 将私钥进行pkcs8编码
openssl pkcs8 -topk8 -inform PEM -in client.key -outform PEM -out client_private.pem -nocrypt

# 生成客户端csr签发文件
openssl req -new -out client.csr -key client.key -subj "/C=CN/ST=Guangdong/L=Shenzhen/CN=haoren.com" 

# 生成客户端crt证书
openssl x509 -req -in client.csr -out client.crt -signkey client.key -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650

# 生成客户端p12格式证书,需要输入一个密码(这一步也不是必须,只是用在某些场合方便而已)
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -name "client" -out client.p12

# server端,将客户端证书导入为受信任的证书
keytool -import -trustcacerts -file client.crt -alias client -storepass password -keystore client.jks
# 注意也要将ca证书导入jks里,否则会报Received fatal alert: bad_certificate(这个是真坑,因为这个问题我研究了很久)
keytool -import -trustcacerts -file ca.crt -alias ca -storepass password -keystore client.jks
  • client.key:服务端秘钥对。

  • client.csr:证书签发文件。

  • client.p12:使用根证书和客户端私钥一起生成,这个证书文件包含客户端的公钥和私钥,主要用来给浏览器访问使用。

使用双向认证的SSL/TLS协议通信,客户端和服务器端都要设置用于证实自己身份的安全证书,并且还要设置信任对方的哪些安全证书。

理论上一共需要准备四个文件,两个keystore文件和两个truststore文件。

通信双方分别拥有一个keystore和一个truststore,keystore用于存放自己的密钥和公钥,truststore用于存放所有需要信任方的公钥。

扩展:CSR中各参数的含义

C-国家(Country Name)

ST-省份(State or Province Name)

L-城市(Locality Name)

O-公司(Organization Name)

OU-部门(Organizational Unit Name)

CN-产品名(Common Name)服务器主机名

使用ca证书签署服务端证书或者客户端证书时可能会出现以下错误:

  • 报错1:
Using configuration from /etc/pki/tls/openssl.cnf
/etc/pki/CA/index.txt: No such file or directory
unable to open '/etc/pki/CA/index.txt'
140190598678416:error:02001002:system library:fopen:No such file or directory:bss_file.c:402:fopen('/etc/pki/CA/index.txt','r')
140190598678416:error:20074002:BIO routines:FILE_CTRL:system lib:bss_file.c:404:

执行以下命令解决:

sudo touch /etc/pki/CA/index.txt
  • 报错2:
Using configuration from /etc/pki/tls/openssl.cnf
/etc/pki/CA/serial: No such file or directory
error while loading serial number
140419572684688:error:02001002:system library:fopen:No such file or directory:bss_file.c:402:fopen('/etc/pki/CA/serial','r')
140419572684688:error:20074002:BIO routines:FILE_CTRL:system lib:bss_file.c:404:

执行以下命令解决:

echo 01 | sudo tee /etc/pki/CA/serial

P12证书与JKS证书的转换:

# server与client的p12证书转换为JKS证书
keytool -importkeystore -srckeystore ca.p12 -destkeystore ca.jks -deststoretype pkcs12
keytool -importkeystore -srckeystore server.p12 -destkeystore server.jks -deststoretype pkcs12
keytool -importkeystore -srckeystore client.p12 -destkeystore client.jks -deststoretype pkcs12

# 查看JKS证书信息
keytool -list -v -keystore server.jks 
keytool -list -v -keystore client.jks 

六、SpringBoot开启SSL认证

application.properties新增以下配置:

# HTTPS SSL配置
server.ssl.enabled=true
server.ssl.key-store=server.p12
server.ssl.keyStoreType=pkcs12
server.ssl.key-store-password=password
server.ssl.key-alias=server
##############如果是单向认证,以下内容不必添加##############
# 开启双向验证 即对客户端的证书也需要校验
server.ssl.client-auth=need
# 通讯连接时会在信任库中检查对方证书是否我们信任
server.ssl.trust-store=client.jks
server.ssl.trust-store-type=JKS
server.ssl.trust-store-password=password

启动springboot,浏览器访问:
在这里插入图片描述

Http通讯类:

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.ToString;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zhang_hr
 * @description http请求工具类
 * @since 2023/9/9 10:56
 **/
@Getter
@Setter
public class HttpClient {

    /** 请求URL */
    private String url;

    /** 请求内容 */
    private String body = StrUtil.EMPTY;

    /** 字符集编码 */
    private String charset = "UTF-8";

    /** 连接超时时间 */
    private Integer connectionTimeout;

    /** read超时时间 */
    private Integer readTimeout;

    /** http请求头 */
    private Map<String, String> headers = new HashMap<>();

    /** SSL参数 */
    private SSLParam sslParam = null;

    public HttpClient(String url, String body, String charset) {
        this.url = url;
        this.body = body;
        this.charset = charset;
    }

    public HttpClient(String url, Integer connectionTimeout, Integer readTimeout) {
        this.url = url;
        this.connectionTimeout = connectionTimeout;
        this.readTimeout = readTimeout;
    }

    public HttpClient(String url, String body, String charset, Integer connectionTimeout, Integer readTimeout) {
        this.url = url;
        this.body = body;
        this.charset = charset;
        this.connectionTimeout = connectionTimeout;
        this.readTimeout = readTimeout;
    }

    public HttpClient(String url, String body, String charset, Integer connectionTimeout, Integer readTimeout, Map<String, String> headers) {
        this.url = url;
        this.body = body;
        this.charset = charset;
        this.connectionTimeout = connectionTimeout;
        this.readTimeout = readTimeout;
        this.headers = headers;
    }

    /**
     * 发送soap请求
     *
     * @return 响应信息
     */
    @SneakyThrows
    public String post() {
        PrintWriter printWriter = null;
        BufferedReader bufferedReader = null;
        StringBuilder responseResult = new StringBuilder();
        HttpURLConnection httpURLConnection = null;
        //第一步:创建服务地址
        try {
            URL url = new URL(this.url);
            // 第二步:打开一个通向服务地址的连接
            httpURLConnection = (HttpURLConnection) url.openConnection();
            // Https请求配置
            if (this.url.startsWith("https")) {
                HttpTrustManager httpTrustManager = new HttpTrustManager();
                SSLContext ctx = httpTrustManager.getInstance();
                SSLSocketFactory sslFactory;
                if (this.sslParam != null) {
                    sslFactory = httpTrustManager.getSocketFactory(this.sslParam.serverCertPath,
                            this.sslParam.clientCertPath, this.sslParam.clientPassword);
                } else {
                    sslFactory = ctx.getSocketFactory();
                }
                ((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(sslFactory);
                // 在握手期间,如果 URL 的主机名和服务器的标识主机名不匹配,则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。
                ((HttpsURLConnection) httpURLConnection).setHostnameVerifier((arg0, arg1) -> true);
            }

            // 第三步:设置参数
            // 设置请求时间
            httpURLConnection.setConnectTimeout(this.connectionTimeout * 1000);
            httpURLConnection.setReadTimeout(this.readTimeout * 1000);
            // 设置请求方式
            httpURLConnection.setRequestMethod("POST");
            // 设置请求头
            for (String key : headers.keySet()) {
                String header = headers.get(key);
                httpURLConnection.setRequestProperty(key, header);
            }
            // 发送POST请求必须设置如下两行
            httpURLConnection.setDoOutput(true);
            httpURLConnection.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            printWriter = new PrintWriter(new OutputStreamWriter(httpURLConnection.getOutputStream(), StandardCharsets.UTF_8));
            printWriter.write(body);
            // flush输出流的缓冲
            printWriter.flush();
            // 第五步:接收服务端响应,打印
            int responseCode = httpURLConnection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                // 定义BufferedReader输入流来读取URL的ResponseData
                bufferedReader = new BufferedReader(new InputStreamReader(
                        httpURLConnection.getInputStream(), Charset.forName(charset)));
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    responseResult.append(line);
                }
            } else {
                throw new Exception("调用接口失败:服务器端返回HTTP code:" + responseCode + "信息:"
                        + httpURLConnection.getResponseMessage());
            }
            return responseResult.toString();
        } finally {
            if (httpURLConnection != null) {
                httpURLConnection.disconnect();
            }
            IoUtil.close(printWriter);
            IoUtil.close(bufferedReader);
        }
    }

    @Getter
    @Setter
    @ToString
    public static class SSLParam {

        public SSLParam(String serverCertPath, String clientCertPath, String clientPassword) {
            this.serverCertPath = serverCertPath;
            this.clientCertPath = clientCertPath;
            this.clientPassword = clientPassword;
        }

        public SSLParam(String serverCertPath, String clientCertPath, String clientPassword, String keyStoreType) {
            this.serverCertPath = serverCertPath;
            this.clientCertPath = clientCertPath;
            this.clientPassword = clientPassword;
            this.keyStoreType = keyStoreType;
        }

        /** crt证书路径 */
        private String serverCertPath;

        /** keyStore路径 */
        private String clientCertPath;

        /** 证书密码 */
        private String clientPassword;

        /** 证书类型, 默认JKS */
        private String keyStoreType = "JKS";

    }
}
package com.spring.demo.api.common.util;

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

/**
 * @author zhang_hr
 * @description HTTPS Trust Manager
 * @since 2023/9/9 20:22
 **/

public class HttpTrustManager extends X509ExtendedTrustManager {

    /**
     * 获取SSLContext
     *
     * @return SSLContext
     */
    public SSLContext getInstance() {
        TrustManager[] trustManager = {new HttpTrustManager()};
        SSLContext context = null;
        try {
            context = SSLContext.getInstance("TLS");
            context.init(null, trustManager, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return context;
    }

    /**
     * 获取SSLSocketFactory
     *
     * @param serverCertPath server证书路径
     * @param clientCertPath client证书路径
     * @param clientPassword client证书密码
     * @return SSLSocketFactory
     */
    public SSLSocketFactory getSocketFactory(String serverCertPath, String clientCertPath, String clientPassword) throws Exception {
        SSLSocketFactory socketFactory;
        try (InputStream serverInputStream = new FileInputStream(serverCertPath);
             InputStream clientInputStream = new FileInputStream(clientCertPath)) {
            // 加载受信任证书(服务端公钥)
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            Certificate ca = certificateFactory.generateCertificate(serverInputStream);
            keyStore.load(null, null);
            // 设置公钥
            keyStore.setCertificateEntry("server", ca);
            trustManagerFactory.init(keyStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

            // 加载密钥库(客户端私钥)
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(clientInputStream, clientPassword.toCharArray());
            keyManagerFactory.init(keyStore, clientPassword.toCharArray());
            KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagers, trustManagers, new SecureRandom());
            socketFactory = sslContext.getSocketFactory();
        }
        return socketFactory;
    }

    @Override
    public void checkClientTrusted(X509Certificate[] arg0, String arg1) {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] arg0, String arg1) {
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }

    @Override
    public void checkClientTrusted(X509Certificate[] arg0, String arg1, Socket arg2) {
    }

    @Override
    public void checkClientTrusted(X509Certificate[] arg0, String arg1, SSLEngine arg2) {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] arg0, String arg1, Socket arg2) {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] arg0, String arg1, SSLEngine arg2) {
    }

}

import com.spring.demo.api.common.util.HttpClient;

/**
 * @author zhang_hr
 * @description https双向认证客户端测试类
 * @since 2023/11/8 16:26
 **/
public class ClientTest {
    public static void main(String[] args) {
        HttpClient httpClient = new HttpClient("https://localhost:8080/demo", 30000, 30000);
        httpClient.setSslParam(new HttpClient.SSLParam("E:\\server.crt",
                "E:\\client.p12",
                "password"));
        System.out.println(httpClient.post());
    }
}

在这里插入图片描述

七、特殊场景

最近做的一个项目,他们也是要求https双向认证,只给我们提供了一个ca证书:ca.cer,这一下也给我搞懵逼了,感觉所学的东西跟真实场景完全不搭噶,经过研究,大概整理了一些思路;

扩展

一、编码格式
同样的 X.509 证书,可能有不同的编码格式,目前有以下两种编码格式。

1、PEM - Privacy Enhanced Mail

打开看文本格式,以“-----BEGIN...”开头,“-----END...”结尾,内容是 BASE64 编码。

查看 PEM 格式证书的信息: 
openssl x509 -in certificate.pem -text -noout

PEM格式是证书颁发机构颁发证书的最常见格式.PEM证书通常具有扩展名,例.pem,.crt,.cer和.key。它们是Base64编码的ASCII文件,包含“----- BEGIN CERTIFICATE -----”和“----- END CERTIFICATE -----”语句。服务器证书,中间证书和私钥都可以放入PEM格式。
Apache和其他类似服务器使用PEM格式证书。几个PEM证书,甚至私钥,可以包含在一个文件中,一个在另一个文件之下,但是大多数平台(例如Apache)希望证书和私钥位于单独的文件中.

2、DER - Distinguished Encoding Rules

DER格式只是证书的二进制形式,而不是ASCII PEM格式。它有时会有.der的文件扩展名,但它的文件扩展名通常是.cer所以判断DER .cer文件和PEM .cer文件之间区别的唯一方法是在文本编辑器中打开它并查找BEGIN / END语句。所有类型的证书和私钥都可以用DER格式编码。DER通常与Java平台一起使用,Java 和 Windows 服务器偏向于使用这种编码格式。SSL转换器只能将证书转换为DER格式

查看 DER 格式证书的信息:
openssl x509 -in certificate.der -inform der -text -noout 

二、相关的文件扩展名
这是比较误导人的地方,虽然我们已经知道有 PEM 和 DER 这两种编码格式,但文件扩展名并不一定就叫“PEM”或者“DER”,常见的扩展名除了 PEM 和 DER 还有以下这些,它们除了编码格式可能不同之外,内容也有差别,但大多数都能相互转换编码格式。

1、CRT
CRT 应该是 certificate 的三个字母,其实还是证书的意思。常见于 UNIX 系统,有可能是 PEM 编码,也有可能是 DER 编码,大多数应该是 PEM 编码。

2、CER
还是 certificate,还是证书。常见于 Windows 系统,同样的可能是 PEM 编码,也可能是 DER 编码,大多数应该是 DER 编码。

3、KEY
通常用来存放一个公钥或者私钥,并非 X.509 证书。编码同样的,可能是 PEM,也可能是 DER。
查看 KEY 的办法:  
openssl rsa -in mykey.key -text -noout 
如果是 DER 格式的话,同理应该这样了: 
openssl rsa -in mykey.key -text -noout -inform der

4、CSR
Certificate,Signing Request,即证书签名请求。这个并不是证书,而是向权威证书颁发机构获得签名证书的申请,其核心内容是一个公钥(当然还附带了一些别的信息)。在生成这个申请的时候,同时也会生成一个私钥,私钥要自己保管好。做过 iOS APP 的朋友都应该知道是,怎么向苹果申请开发者证书的吧。

查看的办法:
openssl req -noout -text -in my.csr 

5、PFX/P12
predecessor of PKCS#12,对 unix 服务器来说,一般 CRT 和 KEY 是分开存放在不同文件中的,但 Windows 的 IIS 则将它们存在一个 PFX 文件中,(因此这个文件包含了证书及私钥)这样会不会不安全?应该不会,PFX 通常会有一个“提取密码”,你想把里面的东西读取出来的话,它就要求你提供提取密码,PFX 使用的时 DER 编码,如何把 PFX 转换为 PEM 编码?
openssl pkcs12 -in for-iis.pfx -out for-iis.pem -nodes
这个时候会提示你输入提取代码,for-iis.pem 就是可读的文本。
生成 pfx 的命令类似这样:
openssl pkcs12 -export -in certificate.crt -inkey privateKey.key -out certificate.pfx -certfile CACert.crt
其中 CACert.crt 是 CA(权威证书颁发机构)的根证书,有的话也通过 -certfile 参数一起带进去。这么看来,PFX 其实是个证书密钥库。

6、PKCS#7 / P7B格式
PKCS#7或P7B格式通常以Base64 ASCII格式存储,文件扩展名为.p7b或.p7c。P7B证书包含“----- BEGIN PKCS7 -----”和“----- END PKCS7 -----”语句。P7B文件仅包含证书和链证书,而不包含私钥。多个平台支持P7B文件,包括Microsoft Windows和Java Tomcat

7、PKCS#12 / PFX格式
PKCS#12或PFX格式是二进制格式,用于将服务器证书,任何中间证书和私钥存储在一个可加密文件中。PFX文件通常具有扩展名,例如.pfx和.p12。PFX文件通常在Windows计算机上用于导入和导出证书和私钥。
将PFX文件转换为PEM格式时,OpenSSL会将所有证书和私钥放入一个文件中。您需要在文本编辑器中打开该文件,并将每个证书和私钥(包括BEGIN / END语句)复制到其各自的文本文件中,并将它们分别保存为certificate.cer,CACert.cer和privateKey.key。

  • 第一步,我们拿到.cer证书之后,使用keytool命令将.cer转换成.keystore或者.jks的证书。
 keytool -import -file root.cer -keystore root.jks
  • 第二步,编写java代码测试
import cn.hutool.core.io.IoUtil;
import lombok.SneakyThrows;

import javax.net.ssl.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.*;
import java.security.cert.CertificateException;


/**
 * @author zhang_hr
 * @description CA证书客户端请求
 * @since 2023/11/10 10:08
 **/
public class ClientTest2 {

    private static final String STORE_TYPE = "JKS"; // PKCS12 or "JKS"
    private static final char[] PASSWORD = "password".toCharArray();

    public static void main(String[] args) throws Exception {
        URL url = new URL("https://localhost:8080/demo");
        sendHttpsRequest(url);
    }

    private static void sendHttpsRequest(URL url) throws Exception {
        BufferedReader bufferedReader = null;
        StringBuilder responseResult = new StringBuilder();
        HttpURLConnection connection = null;

        try {
            // Load the certificate into a KeyStore object
            KeyStore keyStore = loadKeyStore("E:/root.jks", PASSWORD);

            // Get the KeyManagers to handle our keys
            KeyManager[] keyManagers = getKeyManagers(keyStore, PASSWORD);

            // Get the TrustManagers to handle trusted CAs
            TrustManager[] trustManagers = getTrustManagers();

            // Create an SSLContext with our KeyManagers and TrustManagers
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagers, trustManagers, null);

            // Create an HttpsURLConnection using our SSLContext
             connection = (HttpURLConnection) url.openConnection();
            ((HttpsURLConnection) connection).setSSLSocketFactory(sslContext.getSocketFactory());
            ((HttpsURLConnection) connection).setHostnameVerifier((arg0, arg1) -> true);

            // Send request
            connection.setRequestMethod("GET");
            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                // 定义BufferedReader输入流来读取URL的ResponseData
                bufferedReader = new BufferedReader(new InputStreamReader(
                        connection.getInputStream(), Charset.forName("UTF-8")));
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    responseResult.append(line);
                }
            } else {
                throw new Exception("调用接口失败:服务器端返回HTTP code:" + responseCode + "信息:"
                        + connection.getResponseMessage());
            }

            System.out.println(responseResult.toString());
        } finally {
            connection.disconnect();
            IoUtil.close(bufferedReader);
        }


    }

    private static KeyStore loadKeyStore(String path, char[] password)
            throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
        InputStream in = new FileInputStream(path);
        try {
            KeyStore ks = KeyStore.getInstance(STORE_TYPE);
            ks.load(in, password);
            return ks;
        } finally {
            in.close();
        }
    }

    @SneakyThrows
    private static KeyManager[] getKeyManagers(KeyStore keyStore, char[] password)
            throws UnrecoverableKeyException, KeyManagementException {
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(keyStore, password);
        return kmf.getKeyManagers();
    }

    @SneakyThrows
    private static TrustManager[] getTrustManagers() {
        TrustManager[] trustManagers = new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) {}

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) {}

            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        }};

        return trustManagers;
    }
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值