Https加密浅析

2020-03-24  本文已影响0人  ColorfulXL

目录
1.概述
2.加密算法
3.数字证书
4.根证书
5.证书链
6.证书的指纹和签名
7.证书的验证
8.整个通信流程概述
9.在Android中添加可信证书






以百度为例,在浏览器上访问 “www.baidu.com” 域名,地址连左侧有一个小锁的标志,点击就能查看百度的数字证书,如下图所示(使用的是Edge浏览器)

image

在图片的顶部,我们看到这样一个层次关系:

GlobalSign Root CA -> GlobalSign Organization Validation CA -> baidu.com

这个层次可以抽象为三个级别:

  1. end-user:即 baidu.com,该证书包含百度的公钥,访问者就是使用该公钥将数据加密后再传输给百度,即在 HTTPS 中使用的证书
  2. intermediates:即上文提到的 签发人 Issuer,用来认证公钥持有者身份的证书,负责确认 HTTPS 使用的 end-user 证书确实是来源于百度。这类 intermediates 证书可以有很多级,也就是说 签发人 Issuer 可能会有有很多级
  3. root:可以理解为 最高级别的签发人 Issuer,负责认证 intermediates 身份的合法性

这其实代表了一个信任链条,最终的目的就是为了保证 end-user 证书是可信的,该证书的公钥也就是可信的。

image

结合实际的使用场景对证书链进行一个归纳:

  1. 为了获取 end-user 的公钥,需要获取 end-user 的证书,因为公钥就保存在该证书中
  2. 为了证明获取到的 end-user 证书是可信的,就要看该证书是否被 intermediate 权威机构认证,等价于是否有权威机构的数字签名
  3. 有了权威机构的数字签名,而权威机构就是可信的吗?需要继续往上验证,即查看是否存在上一级权威认证机构的数字签名
  4. 信任链条的最终是Root CA,他采用自签名,对他的签名只能无条件的信任
image

还有一个小问题,Root 根证书从何而来呢?除了自行下载安装之外,浏览器、操作系统等都会内置一些 Root 根证书,称之为 Rrusted Root Certificates。比如 Apple MacOS 官网就记录了操作系统中内置的可信任根证书列表。


签名和指纹的作用就是在验证证书的时候,首先通过机构的根公钥去解密证书的数字签名,解密成功以后得到证书的指纹和指纹算法,指纹是一个hash值,代表着证书的原始内容,然后通过指纹算法计算证书内容得到另外一个hash值,如果这两个hash值相同,则代表证书没有被篡改过。


假设这是一个浏览器的HTTPS请求

一:首先浏览器通过URL网址去请求服务端,服务端接收到请求后,就会给浏览器发送一个自己的CA数字证书

二:浏览器接收到证书以后,就要开始进行验证工作了。首先从证书中获取证书的颁发机构,然后从浏览器系统中去寻找此颁发机构的根证书。上面我们也看到,世界上权威CA机构的根证书都是预先嵌入到浏览器系统中的,如果在浏览器系统中没有找到对应的根证书,就代表此机构不是受信任的,那么就会警告无法确认证书的真假。

如果是受信任的CA机构,我们就从根证书中获取到根公钥,用根公钥去解密证书的数字签名,成功解密以后得到证书的指纹和指纹算法,指纹是证书内容通过指纹算法计算得到的一个hash值,这里我们称之为h1,h1代表证书的原始内容;然后用指纹算法对当前接收到的证书内容再进行一次hash计算得到另一个值h2,h2则代表当前证书的内容,如果此时h1和h2是相等的,就代表证书没有被修改过。如果证书被篡改过,h2和h1是不可能相同的,因为hash值具有唯一性,不同内容通过hash计算得到的值是不可能相同的。

在证书没有被修改过的基础上,再检查证书上的使用者的URL和我们请求的URL是否相等,如果相等,那么就可以证明当前浏览器链接的网站也是正确的,而不是一些钓鱼网之类的。

但如果浏览器的连接被某个中间人截取了,中间人也可以发一个由权威的CA机构颁发的证书给浏览器,然后也可以通过证书没有被篡改的验证,但是在证书没有被篡改的前提下,通过对比证书上的URL和我们请求的URL是否相同,就可以判断当前接收到的证书是否为服务器发的证书。可以这么理解,因为URL具有唯一性,所以中间人的证书的上的URL和我们的证书的URL是不可能相同的,如果中间人修改了自己证书上的URL,但是通过不了证书没有被篡改的验证,所以中间人的证书也是欺骗不了我们的。

到这里我们认证了三点信息:

1.证书是否为受信任的权威机构颁发的

2.证书是否被篡改

3.证书是否为服务器发过来的,而不是第三方发的

三:基于上面的三点信息认证都没有问题的情况下,下一步我们有一个重要的任务就是,如何将一个对称加密算法的秘钥安全地发给服务器。

首先随机生成一个字符串S作为我们的秘钥,然后通过证书公钥加密成密文,将密文发送给服务器。因为此密文是用公钥加密的,这是一个非对称加密,我们知道,这个密文只有私钥的持有者才能进行解密,在这里私钥的持有者当然是服务器了,所以说任何第三方截取到密文也是没用的,因为没有对应的私钥也解析不出来。

还有一个关键步骤,发送密文的时候也会对消息内容进行签名操作。签名上面讲解过,就是对密文内容进行hash计算得到的hash值再通过公钥或私钥加密得到的一段数字串,这个签名和消息内容一起发送出去。接收方收到消息以后,通过私钥或公钥解析出密文和签名的hash值,同时也会对接收的消息内容进行同样的hash计算得到另一个hash值,通过比对两个hash值是否相同来判断密文是否有修改过

四:通过了上面的步骤以后,此时客户端和服务端都持有了对称加密算法的同一个秘钥,然后兄弟俩就可以愉快地安全通信了



购买证书毕竟是花钱的,使用自签名证书就是另外一种常见的方式了。所谓的自签名证书就是没有通过受信任的证书颁发机构,自己给自己颁发的证书。最典型的就是12306火车购票,使用的证书就不是受信任的证书颁发机构颁发的,而是旗下SRCA颁发的证书。

使用自签名证书,因此客户端不信任服务器,会抛出异常:javax.net.ssl.SSLHandshakeException:。为此,我们需要自定义信任处理器(TrustManager)来替代系统默认的信任处理器,这样我们才能正常的使用自定义的证书或者非android认可的证书颁发机构颁发的证书。

分为以下两种情况:一种是安全性要求不高的情况下,客户端无需内置证书;另外一种则是客户端内置证书。
第一种情况下,需要自定义TrustManager时重写checkServerTrusted()方法,并在该方法中校验证书,不做验证的话空实现即可。但是如不对服务器证书做任何验证,存在严重安全漏洞,会被拦截请求,伪造任意证书,做中间人攻击。
推荐第二种方式:客户端内置证书
使用自签名证书大致要经过以下几步:

将证书添加到工程中
自定义信任管理器TrustManager
用自定义TrustManager代替系统默认的信任管理器
添加证书到工程

比如现在我们有个证书media.bks,首先将其放在res/raw目录下,当然你可以放在assets目录下。

自定义TrustManager
这里需要实现本地证书的加载

protected static SSLSocketFactory getSSLSocketFactory(Context context, int[] certificates) {
        if (context == null) {
            throw new NullPointerException("context == null");
        }
        //CertificateFactory用来证书生成
        CertificateFactory certificateFactory;
        try {
            certificateFactory = CertificateFactory.getInstance("X.509");
            //Create a KeyStore containing our trusted CAs
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null, null);
            for (int i = 0; i < certificates.length; i++) {
                //读取本地证书
                InputStream is = context.getResources().openRawResource(certificates[i]);
                keyStore.setCertificateEntry(String.valueOf(i), certificateFactory.generateCertificate(is));
                if (is != null) {
                    is.close();
                }
            }
            //Create a TrustManager that trusts the CAs in our keyStore
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
 
            //Create an SSLContext that uses our TrustManager
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

用自定义TrustManager代替系统默认的信任管理器

private void onHttpCertficates(Context context, OkHttpClient.Builder builder) {
        int[] certficates = new int[]{R.raw.media};
        builder.socketFactory(getSSLSocketFactory(context, certficates));
}

其实不难发现,使用非android认证证书颁发机构颁发的证书的关键在于:修改android中SSLContext自带的TrustManager以便能让我们的签名通过验证。

部分参考:https://blog.csdn.net/liuxingrong666/article/details/83869161
https://blog.csdn.net/zhouxinxin250/article/details/81164921
https://www.jianshu.com/p/fcd0572c4765

上一篇 下一篇

猜你喜欢

热点阅读