美文网首页
Https实现机制详解

Https实现机制详解

作者: Gothrow | 来源:发表于2017-12-16 09:30 被阅读0次

    Https请求

    一、访问HTTPS站点

    两种方法来模拟发送HTTP请求,访问HTTP站点。一种方式是通过java.net自带的HttpURLConnection,另一种方式是通过Apache的HttpClient,这两种方式各有各的优势。这里也使用这两种方式来访问HTTPS站点,从下面的代码可以看到,和前面访问HTTP站点几乎完全一样。

    1.1使用HttpURLConnection

    @Test

    public void basicHttpsGet() throws

    Exception {

    String url =  "https://www.baidu.com";

    URL obj = new

    URL(url);

    HttpsURLConnection

    con = (HttpsURLConnection) obj.openConnection();

    con.setRequestProperty("User-Agent",

    "Mozilla/5.0 (Windows NT 6.1; WOW64) ...");

    con.setRequestProperty("Accept-Language",

    "en-US,en;q=0.5");

    con.setRequestMethod("GET");

    String

    responseBody = readResponseBody(con.getInputStream());

    System.out.println(responseBody);

    }

    1.2使用HttpClient

    @Test

    public void basicHttpsGet() throws

    Exception {

    String url =  "https://www.baidu.com";

    HttpGet request =

    new HttpGet(url);

    request.setHeader("User-Agent",

    "Mozilla/5.0 (Windows NT 6.1; WOW64) ...");

    CloseableHttpClient

    httpclient = HttpClients.createDefault();

    CloseableHttpResponse

    response = httpclient.execute(request);

    String

    responseBody = readResponseBody(response);

    System.out.println(responseBody);

    }

    具体的代码解释参见第一篇博客,这里不再赘述。一般情况下,访问HTTPS站点就和访问HTTP站点一样简单,无论是HttpURLConnection还是HttpClient,都将底层的实现细节封装了起来,给我们提供了一致的对外接口,所以我们不用关心HTTPS的实现原理。

    二、Java里的证书

    上面所介绍的是浏览器对证书进行验证的过程,浏览器保存了一个常用的CA证书列表,在验证证书链的有效性时,直接使用保存的证书里的公钥进行校验,如果在证书列表中没有找到或者找到了但是校验不通过,那么浏览器会警告用户,由用户决定是否继续。与此类似的,操作系统也一样保存有一份可信的证书列表,譬如在Windows系统下,你可以运行certmgr.msc打开证书管理器查看,这些证书实际上是存储在Windows的注册表中,一般情况下位于:\SOFTWARE\Microsoft\SystemCertificates\路径下。那么在Java程序中是如何验证证书的呢?

    和浏览器操作系统类似,Java在JRE的安装目录下也保存了一份默认可信的证书列表,这个列表一般是保存在$JRE/lib/security/cacerts文件中。要查看这个文件,可以使用类似KeyStore Explorer这样的软件,当然也可以使用JRE自带的keytool工具(后面再介绍),cacerts文件的默认密码为changeit(但是我保证,大多数人都不会change it)。

    我们知道,证书有很多种不同的存储格式,譬如CA在发布证书时,常常使用PEM格式,这种格式的好处是纯文本,内容是BASE64编码的,证书中使用"-----BEGIN CERTIFICATE-----"和"-----END CERTIFICATE-----"来标识。另外还有比较常用的二进制DER格式,在Windows平台上较常使用的PKCS#12格式等等。当然,不同格式的证书之间是可以相互转换的,我们可以使用openssl这个命令行工具来转换,参考SSL

    Converter,另外,想了解更多证书格式的,可以参考这里:Various

    SSL/TLS Certificate File Types/Extensions。

    在Java平台下,证书常常被存储在KeyStore文件中,上面说的cacerts文件就是一个KeyStore文件,KeyStore不仅可以存储数字证书,还可以存储密钥,存储在KeyStore文件中的对象有三种类型:Certificate、PrivateKey和SecretKey。Certificate就是证书,PrivateKey是非对称加密中的私钥,SecretKey用于对称加密,是对称加密中的密钥。KeyStore文件根据用途,也有很多种不同的格式:JKS、JCEKS、PKCS12、DKS等等,PixelsTech上有一系列文章对KeyStore有深入的介绍,可以学习下:Different

    types of keystore in Java。

    到目前为止,我们所说的KeyStore其实只是一种文件格式而已,实际上在Java的世界里KeyStore文件分成两种:KeyStore和TrustStore,这是两个比较容易混淆的概念,不过这两个东西从文件格式来看其实是一样的。KeyStore保存私钥,用来加解密或者为别人做签名;TrustStore保存一些可信任的证书,访问HTTPS时对被访问者进行认证,以确保它是可信任的。所以准确来说,上面的cacerts文件应该叫做TrustStore而不是KeyStore,只是它的文件格式是KeyStore文件格式罢了。

    除了KeyStore和TrustStore,Java里还有两个类KeyManager和TrustManager与此息息相关。JSSE的参考手册中有一张示意图,说明了各个类之间的关系:

    可以看出如果要进行SSL会话,必须得新建一个SSLSocket对象,而SSLSocket对象是通过SSLSocketFactory来管理的,SSLSocketFactory对象则依赖于SSLContext,SSLContext对象又依赖于keyManager、TrustManager和SecureRandom。我们这里最关心的是TrustManager对象,另外两个暂且忽略,因为正是TrustManager负责证书的校验,对网站进行认证,要想在访问HTTPS时通过认证,不报sun.security.validator.ValidatorException异常,必须从这里开刀。

    三、Java客户端访问https时证书验证处理规则

    客户端的TrustStore文件中保存着被客户端所信任的服务器的证书信息。客户端在进行SSL连接时,JSSE将根据这个文件中的证书决定是否信任服务器端的证书。在SunJSSE中,有一个信任管理器类负责决定是否信任远端的证书,这个类有如下的处理规则:

    1)

    若系统属性javax.net.sll.trustStore指定了TrustStore文件,那么信任管理器就去jre安装路径下的lib/security/目录中寻找并使用这个文件来检查证书。

    2)

    若该系统属性没有指定TrustStore文件,它就会去jre安装路径下寻找默认的TrustStore文件,这个文件的相对路径为:lib/security/jssecacerts

    3)

    若jssecacerts不存在,但是cacerts存在(它随J2SDK一起发行,含有数量有限的可信任的基本证书),那么这个默认的TrustStore文件就是lib/security/cacerts

    四、自定义TrustManager绕过证书检查进行https访问

    我们知道了TrustManager是专门负责校验证书的,那么最容易想到的方法应该就是改写TrustManager类,让它不要对证书做校验,这种方法虽然粗暴,但是却相当有效,而且Java中的TrustManager也确实可以被重写,下面是示例代码:

    @Test

    public void

    basicHttpsGetIgnoreCertificateValidation() throws Exception {

    String url =  "https://kyfw.12306.cn/otn/";

    // Create a trust

    manager that does not validate certificate chains

    TrustManager[]

    trustAllCerts = new TrustManager[] {

    new

    X509TrustManager() {

    public

    X509Certificate[] getAcceptedIssuers() {

    return

    null;

    }

    public

    void checkClientTrusted(X509Certificate[] certs, String authType) {

    //

    don't check

    }

    public

    void checkServerTrusted(X509Certificate[] certs, String authType) {

    //

    don't check

    }

    }

    };

    SSLContext ctx =

    SSLContext.getInstance("TLS");

    ctx.init(null,

    trustAllCerts, null);

    LayeredConnectionSocketFactory

    sslSocketFactory = new SSLConnectionSocketFactory(ctx);

    CloseableHttpClient

    httpclient = HttpClients.custom()

    .setSSLSocketFactory(sslSocketFactory)

    .build();

    HttpGet request =

    new HttpGet(url);

    request.setHeader("User-Agent",

    "Mozilla/5.0 (Windows NT 6.1; WOW64) ...");

    CloseableHttpResponse

    response = httpclient.execute(request);

    String responseBody

    = readResponseBody(response);

    System.out.println(responseBody);

    }

    我们新建了一个匿名类,继承自X509TrustManager接口,这个接口提供了三个方法用于验证证书的有效性:getAcceptedIssuers、checkClientTrusted、checkServerTrusted,我们在验证的函数中直接返回,不做任何校验,这样在访问HTTPS站点时,就算是证书不可信,也不会抛出异常,可以继续执行下去。

    这种方法虽然简单,但是却有一个最严重的问题,就是不安全。因为不对证书做任何合法性校验,而且这种处理是全局性的,不管青红皂白,所有的证书都不会做验证,所以就算遇到不信任的证书,代码依然会继续与之通信,至于通信的数据安全不安全就不能保证了。所以如果你只是想在测试环境做个实验,那没问题,但是如果你要将代码发布到生产环境,请慎重。

    五、使用证书进行https访问

    对于有些证书,我们基本上确定是可以信任的,但是这些证书又不在Java的cacerts文件中,譬如12306网站,或者使用了Let's Encrypt证书的一些网站,对于这些网站,我们可以将其添加到信任列表中,而不是使用上面的方法统统都相信,这样程序的安全性仍然可以得到保障。

    5.1使用keytool导入证书

    简单的做法是将这些网站的证书导入到cacerts文件中,这样Java程序在校验证书的时候就可以从cacerts文件中找到并成功校验这个证书了。上面我们介绍过JRE自带的keytool这个工具,这个工具小巧而强悍,拥有很多功能。首先我们可以使用它查看KeyStore文件,使用下面的命令可以列出KeyStore文件中的所有内容(包括证书、私钥等):

    $ keytool -list -keystore cacerts

    然后通过下面的命令,将证书导入到cacerts文件中:

    $ keytool -import -alias 12306 -keystore cacerts

    -file 12306.cer

    要想将网站的证书导入cacerts文件中,首先要获取网站的证书,譬如上面命令中的12306.cer文件,它是使用浏览器的证书导出向导保存的。如下图所示:

    关于keytool的更多用法,可以参考keytool的官网手册,SSLShopper上也有一篇文章列出了常用的keytool命令

    5.2使用KeyStore动态加载证书

    使用keytool导入证书,这种方法不仅简单,而且保证了代码的安全性,最关键的是代码不用做任何修改。所以我比较推荐这种方法。但是这种方法有一个致命的缺陷,那就是你需要修改JRE目录下的文件,如果你的程序只是在自己的电脑上运行,那倒没什么,可如果你的程序要部署在其他人的电脑上或者公司的服务器上,而你没有权限修改JRE目录下的文件,这该怎么办?如果你的程序是一个分布式的程序要部署在成百上千台机器上,难道还得修改每台机器的JRE文件吗?好在我们还有另一种通过编程的手段来实现的思路,在代码中动态的加载KeyStore文件来完成证书的校验,抱着知其然知其所以然的态度,我们在最后也实践下这种方法。通过编写代码可以更深刻的了解KeyStore、TrustManagerFactory、SSLContext以及SSLSocketFactory这几个类之间的关系。

    @Test

    public void

    basicHttpsGetUsingSslSocketFactory() throws Exception {

    String

    keyStoreFile = "D:\\code\\ttt.ks";

    String password =

    "poiuyt";

    KeyStore ks =

    KeyStore.getInstance(KeyStore.getDefaultType());

    FileInputStream

    in = new FileInputStream(keyStoreFile);

    ks.load(in,

    password.toCharArray());

    System.out.println(KeyStore.getDefaultType().toString());

    System.out.println(TrustManagerFactory.getDefaultAlgorithm().toString());

    TrustManagerFactory

    tmf =

    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

    tmf.init(ks);

    SSLContext ctx =

    SSLContext.getInstance("TLS");

    ctx.init(null,

    tmf.getTrustManagers(), null);

    LayeredConnectionSocketFactory

    sslSocketFactory = new SSLConnectionSocketFactory(ctx);

    String url =  "https://ttt.aneasystone.com";

    /**

    * Return

    the page with content:

    *  401

    Authorization Required

    */

    CloseableHttpClient

    httpclient = HttpClients.custom()

    .setSSLSocketFactory(sslSocketFactory)

    .build();

    HttpGet request =

    new HttpGet(url);

    request.setHeader("User-Agent",

    "Mozilla/5.0 (Windows NT 6.1; WOW64) ...");

    CloseableHttpResponse

    response = httpclient.execute(request);

    String

    responseBody = readResponseBody(response);

    System.out.println(responseBody);

    }

    上面的代码使用了HttpClient,如果是使用HttpsURLConnection只需要改动下面两行即可:

    HttpsURLConnection con =

    (HttpsURLConnection) obj.openConnection();

    con.setSSLSocketFactory(ctx.getSocketFactory());

    最后的最后,我们还可以通过下面的属性来指定trustStore,这样也不需要编写像上面那样大量繁琐的代码,另外,参考我前面的博客,这些属性还可以通过JVM的参数来设置。

    System.setProperty("javax.net.ssl.trustStore",

    "D:\\code\\ttt.ks");

    System.setProperty("javax.net.ssl.trustStorePassword",

    "poiuyt");

    若没有通过设置JVM参数来指定要加载的证书库文件,则使用jdk默认的jre\\lib\\cacerts证书库文件来验证请求站点的证书是否合法。

    5.3 Java请求https服务(真伪查询实例)

    publicstaticJSONObject SetSystsHttpsSSLPost(Stringurl, Stringargs, Stringappkey, Stringiv)throwsException {

    JSONObjectresult=newJSONObject();

    Stringargs= SSLPostTest.encryption(querydata);

    Stringiv=newBase64().encodeToString(IV.getBytes());

    StringkeyStoreFile="C:\\Users\\Gufung\\nubiacsm.keystore";

    /***方式一:使用keystore加载证书库文件****/

    /*String keyStoreFile = System.getProperty("java.home")+ "\\lib\\security\\cacerts";

    Stringpassword = "changeit";

    KeyStoreks= KeyStore.getInstance(KeyStore.getDefaultType());

    FileInputStreamin = new FileInputStream(keyStoreFile);

    ks.load(in,password.toCharArray());

    TrustManagerFactorytmf= TrustManagerFactory

    .getInstance(TrustManagerFactory.getDefaultAlgorithm());

    tmf.init(ks);

    SSLContextctx= SSLContext.getInstance("TLS");

    ctx.init(null,tmf.getTrustManagers(), null);

    URL myURL = new URL(url);

    HttpsURLConnectioncon= (HttpsURLConnection) myURL.openConnection();

    con.setSSLSocketFactory(ctx.getSocketFactory());*/

    /***方式一:使用keystore加载证书库文件****/

    /***方式二:通过设置JVM参数来指定要加载的证书库文件****/

    System.setProperty("javax.net.ssl.trustStore",keyStoreFile);

    System.setProperty("javax.net.ssl.trustStorePassword","changeit");

    URLmyURL=newURL(url);

    HttpsURLConnectioncon= (HttpsURLConnection)myURL.openConnection();

    /***方式二:通过设置JVM参数来指定要加载的证书库文件****/

    con.setDoOutput(true);

    con.setDoInput(true);

    con.setRequestMethod("POST");

    con.setUseCaches(false);

    con.connect();

    DataOutputStreamout=newDataOutputStream(con.getOutputStream());

    Stringcontent="args="+ URLEncoder.encode(args,"UTF-8");

    content+="&appkey="+ URLEncoder.encode(appkey,"UTF-8");

    content+="&iv="+ URLEncoder.encode(iv,"UTF-8");

    out.writeBytes(content);

    out.flush();

    out.close();

    intresultCode=con.getResponseCode();

    System.out.println(resultCode);

    if(HttpURLConnection.HTTP_OK==resultCode) {

    StringreadLine=newString();

    BufferedReaderresponseReader=newBufferedReader(

    newInputStreamReader(con.getInputStream(),"UTF-8"));

    while((readLine=responseReader.readLine()) !=null) {

    result=newJSONObject(readLine);

    }

    responseReader.close();

    }

    con.disconnect();

    System.out.println(result.toString());

    returnresult;

    }

    参考

    SSL如何工作

    SSL/TLS协议简介与实例分析

    SSL/TLS原理详解

    TLS握手优化详解

    三种解密HTTPS流量的方法介绍

    图解SSL/TLS协议

    SSL/TLS协议运行机制的概述

    HTTPS从原理到实战

    HTTPS工作原理和TCP握手机制

    扫盲HTTPS和SSL/TLS协议

    HTTPS那些事(一)HTTPS原理

    理解HTTPS协议

    SSL/TLS协议安全系列:SSL/TLS概述

    Different types of keystore in Java -- Overview

    Different types of keystore in Java -- JKS

    Java中用HttpsURLConnection访问Https链接的问题

    Where is the certificate folder in Windows 7?

    数字证书及CA的扫盲介绍

    数字证书原理

    数字证书

    Java使用自签证书访问https站点

    12306的证书问题

    数字签名是什么?

    在线买火车票为什么要安装根证书?

    Java加密技术(八)——数字证书

    Java加密技术(九)——初探SSL

    常见的数字证书格式

    keyStore vs trustStore

    Difference between trustStore and keyStore in Java - SSL

    Java Secure Socket Extension (JSSE) Reference Guide

    Disable Certificate Validation in Java SSL Connections

    javax.net.ssl.SSLHandshakeException:

    sun.security.validator.ValidatorException: PKIX path building failed

    How to solve javax.net.ssl.SSLHandshakeException?

    SSL Converter

    The Most Common Java Keytool Keystore Commands

    keytool - Key and Certificate Management Tool

    原文链接:http://www.aneasystone.com/archives/2016/04/java-and-https.html

    参考资料:Java安全通信:HTTPS与SSL

    http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html

    http://www.aneasystone.com/archives/2016/04/java-and-https.html

    http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html

    https://my.oschina.net/zhlmmc/blog/42111

    https://www.sslshopper.com/article-most-common-java-keytool-keystore-commands.html

    http://blog.csdn.net/csdnbenbenchong/article/details/7388260

    http://blog.csdn.net/u011042133/article/details/51671801

    http://snowolf.iteye.com/blog/397693

    http://www.iamlbk.com/blog/20160731/tomcat-https/?utm_source=tuicool&utm_medium=referral

    http://blog.chenxiaosheng.com/posts/2013-12-26/java-use-self_signed_certificate.html

    http://www.zhixing123.cn/jsp/49937.html

    http://www.cnblogs.com/JeffreySun/archive/2010/06/24/1627247.html

    https://publib.boulder.ibm.com/tividd/td/TRM/SC23-4822-00/zh_CN/HTML/user276.htm

    http://op.baidu.com/2015/04/https-s01a01/

    http://lukejin.iteye.com/blog/605634

    http://ln-ydc.iteye.com/blog/1335213

    https://blog.cnbluebox.com/blog/2014/03/24/shu-zi-zheng-shu/

    http://blog.csdn.net/sfdev/article/details/2957240

    https://segmentfault.com/a/1190000002554673#articleHeader0

    http://www.cnblogs.com/devinzhang/archive/2012/02/28/2371631.html

    相关文章

      网友评论

          本文标题:Https实现机制详解

          本文链接:https://www.haomeiwen.com/subject/ggxswxtx.html