https双向认证

一、概念

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

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

客户端证书:由根证书签发,需要导入到服务器的信任库中。主要包含客户端公钥、域名和公司信息。

二、目标

1、单向验证:如果是你客户端,你需要拿到服务器的证书,并放到你的信任库中;如果是服务端,你要生成私钥和证书,并将这两个放到你的密钥库中,并且将证书发给所有客户端。

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

3、使用单向验证还是双向验证,是服务器决定的。

单向认证是只在客户端侧做证书校验,双向认证客户端和服务端都要做对方的证书校验。

三、生成密钥、证书

openssl 参数:

-x509:创建自签名证书。
-days:指定自签名证书的有效期限,默认30天,需要和"-x509"一起使用。

-name可用于指定server证书别名;
-caname用于指定ca证书别名

-chain指示同时添加证书链

1、服务器端密钥

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

2、客户端密钥

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

下面这一步转换编码可以省略,这一步是后面用在java代码中作为客户端使用。若不需要不用转换编码。
私钥在使用前为pkcs1格式,而java在不引用第三方包的情况下无法直接使用pkcs1格式的秘钥,需要将其转化为pkcs8编码。

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

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

# 生成 CA 私钥
openssl genrsa -out ca.key 2048
# X.509证书签署请求管理。
openssl req -new -key ca.key -out ca.csr
# X.509证书数据管理。
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt -days 3650

这里是填写,颁发者的信息,即CA机构的信息(这里我们代劳了其实也是填写自己公司的信息):

Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:省份拼音,比如 SiChuan
Locality Name (eg, city) [Default City]:地市拼音,比如 ChengDu
Organization Name (eg, company) [Default Company Ltd]:公司名称拼音,比如 Google
Organizational Unit Name (eg, section) []:你所在公司的部门拼音,比如 YanFa
Common Name (eg, your name or your server's hostname) []:域名或者ip,比如 192.168.111.201
Email Address []:邮箱地址,比如 [email protected]
A challenge password []:密码,比如 123456
An optional company name []:不填,直接回车

4、颁发证书(这一步是CA机构操作,我们自己代劳而已)

颁发证书,即签名服务器证书,生成crt文件

颁发服务端证书

# 服务器端需要向 CA 机构申请签名证书
openssl req -new -key server.key -out server.csr
# 向 CA 机构申请证书,签名过程需要 CA 的证书和私钥参与,最终颁发一个带有 CA 签名的证书
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt -days 3650

颁发客户端证书

# client 端
openssl req -new -key client.key -out client.csr
# client 端到 CA 签名
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in client.csr -out client.crt -days 3650

这里是填写,使用者的信息,即自己公司的信息

Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:省份拼音,比如 SiChuan
Locality Name (eg, city) [Default City]:地市拼音,比如 ChengDu
Organization Name (eg, company) [Default Company Ltd]:公司名称拼音,比如 Google
Organizational Unit Name (eg, section) []:你所在公司的部门拼音,比如 YanFa
Common Name (eg, your name or your server's hostname) []:域名或者ip,比如 192.168.111.201
Email Address []:邮箱地址,比如 [email protected]
A challenge password []:密码,比如 123456
An optional company name []:不填,直接回车

5、生成pkcs12格式证书(这一步也不是必须,只是用在某些场合方便而已)

生成pkcs12格式证书(该格式证书包含CA证书,私钥和自己的证书)
在tomcat中实现双向认证有时需要pkcs12格式的证书(该证书包含根证书、服务器端或客户端的证书和密钥文件)

生成pkcs12服务器证书 server.p12,需要输入2次密码,设置密码为123456

openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12 -name server -CAfile ca.crt -caname ca

生成pkcs12客户端证书 client.p12,需要输入2次密码,设置密码为123456

openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name client -CAfile ca.crt -caname ca

注意:若要设置密码为空,不用输入密码,直接回车。

服务器端,将客户端证书导入为受信任的证书

keytool -import -trustcacerts -file client.crt -alias client -storepass 123456 -keystore client.jks

四、tomcat实现单向、双向认证。在实际开发中绝大多数使用的单向认证(默认服务端信任所有客户端)。

@RestController
public class SslContoller {

    //http请求:http://192.168.111.201:8081/ssl/hello?name=123
    //https请求:https://192.168.111.201:8443/ssl/hello?name=123
    @GetMapping("/hello")
    public String hello(String name) {
        return name;
    }
}

单向验证配置 vi  /tomcat/conf/server.xml


clientAuth:是否验证客户端,false为单向认证,true为双向认证。

 

 这里要注意:上面一个是 http ,下面一个是 https 别输入错误。

将fiddler的根证书导入到受信任的根证书服务器,使用fiddler抓包,也能正常抓到。

https双向认证_第1张图片

双向验证配置 vi  /tomcat/conf/server.xml

    
    

clientAuth:是否验证客户端,false为单向认证,true为双向认证。

当未安装服务端证书时:报以下错误。

https双向认证_第2张图片

 客户端导入 client.p12 证书,windows系统双击安装即可。

再次访问提示选择证书:

https双向认证_第3张图片

选择证书,点击确定后,再次请求后成功:

 https双向认证_第4张图片

 使用fiddler抓包,已经不正常抓到

https双向认证_第5张图片

 ,这时需要在fiddler中加入客户端的证书(若本地已经安装可以直接导出一个client.cer的证书,这里我用的是我们自己生成的client.crt)。

https双向认证_第6张图片

 再次用fiddler已经可以抓到

https双向认证_第7张图片

不管单向还是双向,都不存在中间人攻击问题,我这里能用fiddler抓包的前提是浏览器使用的了fiddler的根证书。在代码中加入下面的代理,使用代码的方式调用接口(代码在结尾)

        System.setProperty("http.proxyHost", "127.0.0.1");
        System.setProperty("https.proxyHost", "127.0.0.1");
        System.setProperty("http.proxyPort", "8888");
        System.setProperty("https.proxyPort", "8888");

https双向认证_第8张图片

 https双向认证_第9张图片

发现fiddler根本抓不到包,所以不论单向还是双向不存在中间人攻击

四、Springboot配置ssl实现单向、双向认证。在实际开发中绝大多数使用的单向认证(默认服务端信任所有客户端)。

将生成好的 server.p12 和 client.jks 复制到 springboot 中的  resources/ssl/ 文件目录下,并配置

单向验证配置 application.properties

#端口号
server.port=8443
#是否启用SSL证书
server.ssl.enabled=true
#服务端私钥
server.ssl.key-store=classpath:ssl/server.p12
server.ssl.key-store-password=123456
#是否验证客户端(双向验证)
server.ssl.client-auth=none

双向验证配置 application.properties

#端口号
server.port=8443
#是否启用SSL证书
server.ssl.enabled=true
#服务端私钥
server.ssl.key-store=classpath:ssl/server.p12
server.ssl.key-store-password=123456
#是否验证客户端(双向验证)
server.ssl.client-auth=need
#信任证书,客户端公钥
server.ssl.trust-store=classpath:ssl/client.jks
server.ssl.trust-store-password=123456

在浏览器 https://192.168.111.201:8443/hello?name=123 测试方式同上面tomcat的方式。

五、查看windows系统的证书是否成功安装

cmd 运行 certmgr.msc

https双向认证_第10张图片

 六、java代码客户端

单向验证

在单向验证中,假如你只管调用成功就行,不去验证服务器端证书,可以使用以下方式,但是不安全,还是建议验证服务器端的证书。

package com.ssl;

import javax.net.ssl.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

public class Http5 {

    public static void main(String[] args) throws Exception {
        //发送请求
        String url = "https://192.168.111.201:8443/ssl/hello?name=123";
        String sendGet = sendGet(url);
        //打印结果返回数据
        System.out.println(sendGet);
    }

    /**
     * Get请求
     */
    public static String sendGet(String url) {
        HttpsURLConnection con = null;
        ByteArrayOutputStream baos = null;
        InputStream is = null;
        try {
            // 打开连接
            con = (HttpsURLConnection) new URL(url).openConnection();
            // 绕过证书验证
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[]{new MyTrustAnyTrustManager()}, new java.security.SecureRandom());
            con.setSSLSocketFactory(sc.getSocketFactory());
            // 绕过验证主机名和服务器验证方案的匹配是可接受的
            con.setHostnameVerifier(new CustomizedHostnameVerifier());
            //
            con.setRequestMethod("GET");
            con.setConnectTimeout(10000);//是建立连接的超时时间
            con.setReadTimeout(10000);//是inputStream.read()超时时间
            //请求头
            // con.setRequestProperty("Accept-Encoding", "gzip, deflate");//千万不要使用这个请求头,因为我们不会去解压。
            // con.setRequestProperty("Accept", "application/octet-stream");//二进制流数据
            // con.setRequestProperty("Accept", "application/json");
            // con.setRequestProperty("Connection", "keep-alive");//抓包发现,不加也是这个
            // 开启连接
            con.connect();
            if (con.getResponseCode() == 200) {
                is = con.getInputStream();
                baos = new ByteArrayOutputStream();
                byte[] buf = new byte[1024];
                int len;
                while ((len = is.read(buf)) != -1) {
                    baos.write(buf, 0, len);
                    baos.flush();
                }
                return baos.toString("UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.close();
                }
                if (is != null) {
                    is.close();
                }
                if (con != null) {
                    con.disconnect();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Post请求
     */
    public static String sendPost(String url, String params, String accesstoken) {
        HttpsURLConnection con = null;
        ByteArrayOutputStream baos = null;
        OutputStream os = null;
        InputStream is = null;
        try {
            // 打开连接
            con = (HttpsURLConnection) new URL(url).openConnection();
            // 绕过证书验证
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[]{new MyTrustAnyTrustManager()}, new java.security.SecureRandom());
            con.setSSLSocketFactory(sc.getSocketFactory());
            // 绕过验证主机名和服务器验证方案的匹配是可接受的
            con.setHostnameVerifier(new CustomizedHostnameVerifier());
            //
            con.setRequestMethod("POST");
            con.setDoOutput(true);
            con.setDoInput(true);
            con.setUseCaches(false);
            con.setConnectTimeout(10000);//是建立连接的超时时间
            con.setReadTimeout(10000);//是inputStream.read()超时时间
            //请求头
            // con.setRequestProperty("Accept-Encoding", "gzip, deflate");//千万不要使用这个请求头,因为我们不会去解压。
            // con.setRequestProperty("Accept", "application/octet-stream");//二进制流数据
            // con.setRequestProperty("Accept", "application/json;charset=UTF-8");
            // con.setRequestProperty("Connection", "keep-alive");//抓包发现,不加也是这个
            // 开启连接
            con.connect();
            //请求参数
            if (params != null && params.length() > 0) {
                os = con.getOutputStream();
                os.write(params.getBytes(StandardCharsets.UTF_8));
                os.flush();
            }
            //响应
            // System.out.println(con.getResponseCode());
            if (con.getResponseCode() == 200) {
                is = con.getInputStream();
                baos = new ByteArrayOutputStream();
                byte[] buf = new byte[1024];
                int len;
                while ((len = is.read(buf)) != -1) {
                    baos.write(buf, 0, len);
                    baos.flush();
                }
                return baos.toString("UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.close();
                }
                if (os != null) {
                    os.close();
                }
                if (is != null) {
                    is.close();
                }
                if (con != null) {
                    con.disconnect();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 受信任的客户端处理
     * 自定义X509TrustManager中的checkClientTreusted和checkServerTrusted都是空实现,也就是不检查服务端的SSL证书信息。
     * @date 2022/8/10 9:36
     */
    static class MyTrustAnyTrustManager implements X509TrustManager {
        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
        }

        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    }

    /**
     * host验证
     * 自定义HostnameVerifier中的verify方法返回true,默认信任所有域名,否则在请求时会报错。
     * @date 2022/8/10 9:37
     */
    static class CustomizedHostnameVerifier implements HostnameVerifier {
        // 重写验证方法
        @Override
        public boolean verify(String urlHostName, SSLSession session) {
            // 所有都正确
            return true;
        }
    }

}

双向验证

以下https请求使用的都是自签名证书,不支持系统证书,防止被抓包。

方式一:

涉及到的证书上面都有直接使用即可

package com.ssl;

import javax.net.ssl.*;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;

public class Test3 {
    private static String serverPath = "F:/server.crt";//服务端证书(服务端公钥)
    private static String clientPath = "F:/client.p12";//客户端证书(客户端私钥)
    private static String password = "123456";


    public static void main(String[] args) throws Exception {
        //发送请求
        String url = "https://192.168.111.201:8443/hello?name=123";
        String sendGet = sendGet(url);
        //打印结果返回数据
        System.out.println(sendGet);
    }

    /**
     * Get请求
     */
    public static String sendGet(String url) {
        HttpsURLConnection con = null;
        ByteArrayOutputStream baos = null;
        InputStream is = null;
        try {
            // 打开连接
            con = (HttpsURLConnection) new URL(url).openConnection();
            //ssl验证
            con.setSSLSocketFactory(getSocketFactory());
            // 绕过验证主机名和服务器验证方案的匹配是可接受的
            con.setHostnameVerifier(new CustomizedHostnameVerifier());
            //
            con.setRequestMethod("GET");
            con.setConnectTimeout(10000);//是建立连接的超时时间
            con.setReadTimeout(10000);//是inputStream.read()超时时间
            //请求头
            // con.setRequestProperty("Accept-Encoding", "gzip, deflate");//千万不要使用这个请求头,因为我们不会去解压。
            // con.setRequestProperty("Accept", "application/octet-stream");//二进制流数据
            // con.setRequestProperty("Accept", "application/json");
            // con.setRequestProperty("Connection", "keep-alive");//抓包发现,不加也是这个
            // 开启连接
            con.connect();
            if (con.getResponseCode() == 200) {
                is = con.getInputStream();
                baos = new ByteArrayOutputStream();
                byte[] buf = new byte[1024];
                int len;
                while ((len = is.read(buf)) != -1) {
                    baos.write(buf, 0, len);
                    baos.flush();
                }
                return baos.toString("UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.close();
                }
                if (is != null) {
                    is.close();
                }
                if (con != null) {
                    con.disconnect();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Post请求
     */
    public static String sendPost(String url, String params, String accesstoken) {
        HttpsURLConnection con = null;
        ByteArrayOutputStream baos = null;
        OutputStream os = null;
        InputStream is = null;
        try {
            // 打开连接
            con = (HttpsURLConnection) new URL(url).openConnection();
            //ssl验证
            con.setSSLSocketFactory(getSocketFactory());
            // 绕过验证主机名和服务器验证方案的匹配是可接受的
            con.setHostnameVerifier(new CustomizedHostnameVerifier());
            //
            con.setRequestMethod("POST");
            con.setDoOutput(true);
            con.setDoInput(true);
            con.setUseCaches(false);
            con.setConnectTimeout(10000);//是建立连接的超时时间
            con.setReadTimeout(10000);//是inputStream.read()超时时间
            //请求头
            // con.setRequestProperty("Accept-Encoding", "gzip, deflate");//千万不要使用这个请求头,因为我们不会去解压。
            // con.setRequestProperty("Accept", "application/octet-stream");//二进制流数据
            // con.setRequestProperty("Accept", "application/json;charset=UTF-8");
            // con.setRequestProperty("Connection", "keep-alive");//抓包发现,不加也是这个
            // 开启连接
            con.connect();
            //请求参数
            if (params != null && params.length() > 0) {
                os = con.getOutputStream();
                os.write(params.getBytes(StandardCharsets.UTF_8));
                os.flush();
            }
            //响应
            // System.out.println(con.getResponseCode());
            if (con.getResponseCode() == 200) {
                is = con.getInputStream();
                baos = new ByteArrayOutputStream();
                byte[] buf = new byte[1024];
                int len;
                while ((len = is.read(buf)) != -1) {
                    baos.write(buf, 0, len);
                    baos.flush();
                }
                return baos.toString("UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.close();
                }
                if (os != null) {
                    os.close();
                }
                if (is != null) {
                    is.close();
                }
                if (con != null) {
                    con.disconnect();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public static SSLSocketFactory getSocketFactory() throws Exception {
        InputStream serverInputStream = null;
        InputStream clientInputStream = null;
        SSLSocketFactory socketFactory;
        try {
            serverInputStream = new FileInputStream(serverPath);
            clientInputStream = new FileInputStream(clientPath);

            //加载受信任证书(服务端公钥)
            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, password.toCharArray());
            keyManagerFactory.init(keyStore, password.toCharArray());
            KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagers, trustManagers, new SecureRandom());
            socketFactory = sslContext.getSocketFactory();
        } finally {
            if (serverInputStream != null) {
                serverInputStream.close();
            }
            if (clientInputStream != null) {
                clientInputStream.close();
            }
        }
        return socketFactory;
    }

    static class CustomizedHostnameVerifier implements HostnameVerifier {
        // 重写验证方法
        @Override
        public boolean verify(String urlHostName, SSLSession session) {
            //这里假如客户端不需要验证服务端直接返回true就可以
            if ("192.168.111.201".equals(urlHostName)) {
                return true;
            }
            return false;
            // 所有都正确
            // return true;
        }
    }

}

方式二:

在很多时候,我们只是客户端去调用别人的服务器,别人给的证书格式又不一样,这点我就很烦为什么ssl证书乱七八糟这么多格式,就是因为这样才有了这篇博文,自己搭建了一套来测试。

1、服务端证书
2、客户端证书
3、客户端私钥

package com.ssl;

import sun.misc.BASE64Decoder;

import javax.net.ssl.*;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;

public class Test5 {
    private static String serverPath = "F:/server.crt";//服务端证书
    private static String clientPath = "F:/client.crt";//客户端证书
    private static String clientKeyPath = "F:/client_private.pem";//客户端私钥
    private static String password = "123456";


    public static void main(String[] args) throws Exception {
        //发送请求
        String url = "https://192.168.111.201:8443/hello?name=123";
        String sendGet = sendGet(url);
        //打印结果返回数据
        System.out.println(sendGet);
    }

    /**
     * Get请求
     */
    public static String sendGet(String url) {
        HttpsURLConnection con = null;
        ByteArrayOutputStream baos = null;
        InputStream is = null;
        try {
            // 打开连接
            con = (HttpsURLConnection) new URL(url).openConnection();
            //ssl验证
            con.setSSLSocketFactory(getSocketFactory());
            // 绕过验证主机名和服务器验证方案的匹配是可接受的
            con.setHostnameVerifier(new CustomizedHostnameVerifier());
            //
            con.setRequestMethod("GET");
            con.setConnectTimeout(10000);//是建立连接的超时时间
            con.setReadTimeout(10000);//是inputStream.read()超时时间
            //请求头
            // con.setRequestProperty("Accept-Encoding", "gzip, deflate");//千万不要使用这个请求头,因为我们不会去解压。
            // con.setRequestProperty("Accept", "application/octet-stream");//二进制流数据
            // con.setRequestProperty("Accept", "application/json");
            // con.setRequestProperty("Connection", "keep-alive");//抓包发现,不加也是这个
            // 开启连接
            con.connect();
            if (con.getResponseCode() == 200) {
                is = con.getInputStream();
                baos = new ByteArrayOutputStream();
                byte[] buf = new byte[1024];
                int len;
                while ((len = is.read(buf)) != -1) {
                    baos.write(buf, 0, len);
                    baos.flush();
                }
                return baos.toString("UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.close();
                }
                if (is != null) {
                    is.close();
                }
                if (con != null) {
                    con.disconnect();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Post请求
     */
    public static String sendPost(String url, String params, String accesstoken) {
        HttpsURLConnection con = null;
        ByteArrayOutputStream baos = null;
        OutputStream os = null;
        InputStream is = null;
        try {
            // 打开连接
            con = (HttpsURLConnection) new URL(url).openConnection();
            //ssl验证
            con.setSSLSocketFactory(getSocketFactory());
            // 绕过验证主机名和服务器验证方案的匹配是可接受的
            con.setHostnameVerifier(new CustomizedHostnameVerifier());
            //
            con.setRequestMethod("POST");
            con.setDoOutput(true);
            con.setDoInput(true);
            con.setUseCaches(false);
            con.setConnectTimeout(10000);//是建立连接的超时时间
            con.setReadTimeout(10000);//是inputStream.read()超时时间
            //请求头
            // con.setRequestProperty("Accept-Encoding", "gzip, deflate");//千万不要使用这个请求头,因为我们不会去解压。
            // con.setRequestProperty("Accept", "application/octet-stream");//二进制流数据
            // con.setRequestProperty("Accept", "application/json;charset=UTF-8");
            // con.setRequestProperty("Connection", "keep-alive");//抓包发现,不加也是这个
            // 开启连接
            con.connect();
            //请求参数
            if (params != null && params.length() > 0) {
                os = con.getOutputStream();
                os.write(params.getBytes(StandardCharsets.UTF_8));
                os.flush();
            }
            //响应
            // System.out.println(con.getResponseCode());
            if (con.getResponseCode() == 200) {
                is = con.getInputStream();
                baos = new ByteArrayOutputStream();
                byte[] buf = new byte[1024];
                int len;
                while ((len = is.read(buf)) != -1) {
                    baos.write(buf, 0, len);
                    baos.flush();
                }
                return baos.toString("UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.close();
                }
                if (os != null) {
                    os.close();
                }
                if (is != null) {
                    is.close();
                }
                if (con != null) {
                    con.disconnect();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public static SSLSocketFactory getSocketFactory() throws Exception {
        InputStream serverInputStream = null;
        InputStream clientInputStream = null;
        SSLSocketFactory socketFactory;
        try {
            serverInputStream = new FileInputStream(serverPath);
            clientInputStream = new FileInputStream(clientPath);

            //1、加载受信任证书(服务端公钥)

            // CA证书用于对服务器进行认证,信任指定的服务端
            //证书工厂(CertificateFactory)。密钥库(KeyStore)。
            //CA(公钥证书)经PKIX工作组改造的X.509规范
            //JKS是Java Keystore,一种专为Java设计的专有密钥库类型。 可以用于存储用于SSL通信的私钥和证书,但是它不能存储密钥。
            CertificateFactory serverCA = CertificateFactory.getInstance("X.509");
            X509Certificate ca = (X509Certificate) serverCA.generateCertificate(serverInputStream);
            KeyStore caks = KeyStore.getInstance("JKS");
            caks.load(null, null);
            caks.setCertificateEntry("server", ca);
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");//  Trust信任的,设置信任的服务端
            tmf.init(caks);


            //2、加载密钥库(客户端私钥)

            // 客户端密钥和证书被发送到服务器,以便服务器对我们进行身份验证
            CertificateFactory clientCA = CertificateFactory.getInstance("X.509");
            X509Certificate cert = (X509Certificate) clientCA.generateCertificate(clientInputStream);
            KeyStore certks = KeyStore.getInstance("JKS");//默认就是JKS
            certks.load(null, null);
            certks.setCertificateEntry("client", cert);
            certks.setKeyEntry("privateKey", getPrivateKey(clientKeyPath), password.toCharArray(), new java.security.cert.Certificate[]{cert});
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
            kmf.init(certks, password.toCharArray());

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
            socketFactory = sslContext.getSocketFactory();
        } finally {
            if (serverInputStream != null) {
                serverInputStream.close();
            }
            if (clientInputStream != null) {
                clientInputStream.close();
            }
        }
        return socketFactory;
    }


    /**
     * 获取文件中的密钥
     */
    public static PrivateKey getPrivateKey(String path) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(path));
            String line;
            StringBuilder sb = new StringBuilder();
            while ((line = br.readLine()) != null) {
                if (!line.startsWith("-")) {
                    sb.append(line);
                }
            }
            // // byte[] buffer = Base64.getDecoder().decode(getPem(path));
            BASE64Decoder base64decoder = new BASE64Decoder();
            byte[] buffer = base64decoder.decodeBuffer(sb.toString());
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
            return privateKey;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    static class CustomizedHostnameVerifier implements HostnameVerifier {
        // 重写验证方法
        @Override
        public boolean verify(String urlHostName, SSLSession session) {
            //这里假如客户端不需要验证服务端直接返回true就可以
            if ("192.168.111.201".equals(urlHostName)) {
                return true;
            }
            return false;
            // 所有都正确
            // return true;
        }
    }

}

假如你的证书还不和上面一样,那就想办法转换成上面给的2种方式的证书,建议使用 openssl 命令直接转换成第一种方式。其实第二种方式的证书就可以转换为第一种方式,第二种方式就很麻烦,但是可以让我们更好地了解java的 KeyStore 模式是怎么去实现的。这里给一个 KeyStore 的api地址http://jszx-jxpt.cuit.edu.cn/JavaAPI/java/security/KeyStore.html

有盾就有茅,手机https的抓包方式

Fiddler利用Edxposed框架+TrustMeAlready来突破SSL pinning抓取手机APP数据_小百菜的博客-CSDN博客

你可能感兴趣的:(java,https,ssl)