Java爬虫(八)-- httpClient进阶:HTTPS和证书认证(原理总结篇)

Richard_Yi 2018年04月05日 88次浏览

一、前言

本篇文章承接上一篇,对应讲述一些我在接触SSL协议、证书认证时学到的一些原理性知识。因为本身不是科班出身,网络方面很多对我来说都是新知识,特在此记录一下。

二、HTTPS

HTTPS = HTTP + SSL/TLS 协议,即加密过后的HTTP通信。它其实还是HTTP协议,只是在外面加了一层,SSL 是一种加密安全协议,引入SSL的目的是为了解决HTTP协议在不可信网络中使用明文传输数据导致的安全性问题。可以说,整个互联网的通信安全,都是建立在 SSL/TLS 的安全性之上的。

ProtocolYearRFCDescription
SSL1.01994 NetScape公司设计1.0版 但是未发布
SSL2.01995.02 NetScape公司发布SSL 2.0版
SSL3.01996RFC 6101NetScape公司发布SSL 3.0版
TLS 1.01999RFC 2246互联网标准化组织ISOC接替NetScape公司,发布了SSL的升级版TLS 1.0版。
TLS 1.12006.04RFC 4346发布TLS1.1版
TLS 1.22008.08RFC 5246发布TLS1.2版

2.1 SSL/TLS 协议及其握手过程

SSL/TLS 的基本运行过程如下:

  1. 客户端向服务端索要公钥并且进行验证
  2. 双方协商生成“对话密钥”
  3. 双方采用“对话密钥”进行加密通信

第一步中的这个公钥就是放在证书中的,防止篡改,只要证书是可信的,那么公钥也是可信的。

这里先说下,SSL/TLS协议是采用了非对称加密的加密思路,也就是说客户端通过用服务端传过来的公钥进行加密,然后服务端收到密文之后用自己的私钥进行解密。

那么第2步的“对话密钥”是什么呢,这里就涉及对称加密的思路了。因为非对称加密的缺点就是速度慢,所以在每次session中,客户端和服务端之间都会生成一个“对话密钥”(session key),用它来加密信息。因为对称加密的优点就是速度快,而服务器发来的公钥就只是用来加密“对话密钥”而已。

所以说SSL/TLS协议的设计中运用了大量的密码学原理和思想。

在这里顺便科普一下常见的加密算法:

  • 哈希(散列)加密
    • 哈希算法又称散列,它是一种将任意长度的数据转化为固定长度的算法
    • 哈希算法是不可逆的
    • 常见的哈希算法有 MD5 和 SHA1
  • 对称加密
    • 对称加密指的是加密和解密使用相同一个密钥
    • 对称加密的优点是速度快,缺点是密钥管理不方便,必须共享密钥
    • 常见的对称加密算法有 DES、AES、Blowfish 等
  • 非对称加密
    • 非对称加密指的是加密和解密使用不同的密钥,其中一个是公钥,另一个是私钥,公钥是公开的,私钥只有自己知道
    • 使用公钥加密的数据必须使用私钥解密,使用私钥加密的数据必须使用公钥解密
    • 公钥和私钥之间存在着某种联系,但是从公钥不能(或很难)推导出私钥
    • 非对称加密的缺点是速度慢,优点是密钥管理很方便
    • 常见的非对称加密算法有 RSA、ECC 等

至于具体的算法原理我暂时没有去深究。

这个过程中的前两步又称为“握手过程”。

说到握手,阮一峰先生的博客文章《图解SSL/TLS协议》中通过cloudFlare的几张生动的说明图很形象地描述了这个过程。具体可以看这篇文章哦。

这里从另外一篇参考文章中截取了一张图来说明:

undefined

第一步(ClientHello),客户端给出协议版本号、一个客户端生成的随机数(Client random),以及客户端支持的加密方法。

第二步(SeverHello),服务端确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random)。

第三步,客户端确认数字证书有效,然后生成一个新的随机数(Premaster secret),并使用数字证书中的公钥,加密这个随机数,发给服务端。

第四步,服务端使用自己的私钥,获取客户端发来的随机数(即Premaster secret)。

第五步,客户端和服务端根据约定的加密方法,使用前面的三个随机数,生成"对话密钥"(session key),用来加密接下来的整个对话过程。

第一步第二步打比方就是男女双方打招呼(say hello),告诉对方自己的一些基本情况,然后双方就开始尝试交往了。

如果有看我前一篇对应的博文的话,会发现我第一个报错,

    Unsupported record version SSLv2Hello
    javax.net.ssl.SSLException: Unsupported record version SSLv2Hello
    ...
    ...

就是第二步中,服务器确认使用的加密通信协议版本,发现客户端与服务器支持的版本不一致,服务器关闭加密通信导致的。

而第二个错,

javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
...
...
Caused by: java.io.EOFException: SSL peer shut down incorrectly
...
...

则应该是在第四步中服务器在获取客户端信息时,要验证客户端的证书,发现没有证书导致的。

这里补充讲一下双向认证和单向认证的概念:

单向认证指的就是这个过程中,只有客户端对服务端的证书进行认证,然后获取服务端传来的公钥。

而双向认证指的就是,不只客户端对服务端要进行认证,服务端也要对客户端进行认证,客户端也需要带着证书过来,让服务端获取到客户端的公钥。

详述一下握手阶段的3-5步:

  1. 服务器要求客户发送客户自己的证书。收到后,服务器验证客户的证书,如果没有通过验证,拒绝连接;如果通过验证,服务器获得用户的公钥。
  2. 客户浏览器告诉服务器自己所能够支持的通讯对称密码方案。
  3. 服务器从客户发送过来的密码方案中,选择一种加密程度最高的密码方案,用客户的公钥加过密后通知浏览器。
  4. 浏览器针对这个密码方案,选择一个通话密钥,接着用服务器的公钥加过密后发送给服务器。
  5. 服务器接收到浏览器送过来的消息,用自己的私钥解密,获得通话密钥。
  6. 服务器、浏览器接下来的通讯都是用对称密码方案,对称密钥是加过密的

证书

一个数字证书包含如下信息:

  • 证书的发布机构
  • 证书的有效期
  • 公钥
  • 证书所有者(Subject)
  • 签名所使用的算法
  • 指纹以及指纹算法

数字证书可以保证数字证书里的公钥确实是这个证书的所有者(Subject)的,或者证书可以用来确认对方的身份。也就是说,我们拿到一个数字证书,我们可以判断出这个数字证书到底是谁的。

数字证书的介绍可以详见《数字证书原理》这篇博文,写的十分详细。

如果一个网站需要支持 HTTPS ,它就要一份证书来证明自己的身份,而这个证书必须从 CA 机构申请,CA指的是Certificate Authority,证书授权中心。大多数情况下申请数字证书的价格都不菲,不过也有一些免费的证书供个人使用。从安全性的角度来说,免费的和收费的证书没有任何区别,都可以为你的网站提供足够高的安全性,唯一的区别在于如果你从权威机构购买了付费的证书,一旦由于证书安全问题导致经济损失,可以获得一笔巨额的赔偿。

这里在讲一下上一篇博文中提到的一个报错:

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed

CA 证书可以具有层级结构,它建立了自上而下的信任链,下级 CA 信任上级 CA ,下级 CA 由上级 CA 颁发证书并认证。

浏览器在验证证书时,从根证书开始,沿着证书链的路径依次向下验证,根证书是整个证书链的安全之本,如果根证书被篡改,整个证书体系的安全将受到威胁。

根证书需要验证吗?Emm..根证书是自己验证自己,也就是不需要被验证,所以根证书是整个证书体系的安全之本。

在说回上面那个错误,PKIX,这是个什么东西呢?看下面:

PKI(Public Key Infrastructure)中文称作公钥基础设施,它提供公钥加密和数字签名服务的系统或平台,方便管理密钥和证书,从而建立起一个安全的网络环境。而数字证书最常见的格式是 X.509 ,所以这种公钥基础设施又称之为 PKIX 。

所以,上面那个报错的意思就是在沿着证书链的路径验证证书时出现异常,验证失败了。

Java中的证书

浏览器保存了一个常用的CA证书列表,在验证证书链的有效性时,直接使用保存的证书里的公钥进行校验,如果在证书列表中没有找到或者找到了但是校验不通过,那么浏览器会警告用户,由用户决定是否继续。与此类似的,操作系统也一样保存有一份可信的证书列表。

Java也是一样,在JRE的安装目录下可以找到相对应的证书目录$JRE/lib/security/cacerts,里面保存着一些常见的证书列表。可以使用JRE自带的keytool工具(后面再介绍)cacerts文件的默认密码为changeit。(change it...Emmmm, funny uhh.)

undefined

在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文件格式罢了。

在程序中怎么管理他们呢?

这就涉及到Java中的KeyManagerTrustManager

undefined

可以看出如果要进行SSL会话,必须得新建一个SSLSocket对象,而SSLSocket对象是通过SSLSocketFactory来管理的,SSLSocketFactory对象则依赖于SSLContextSSLContext对象又依赖于keyManagerTrustManagerSecureRandom

这几个对象在上篇博文中启用ssl会话的代码中都有体现。

小结

本篇文章中讲了一些我被卡在之前那几个bug的过程中,学到的一些知识。很感谢参考资料中的文章,都非常的优秀。

希望能够对你有所帮助。

参考资料

[1] https://www.cnblogs.com/UnGeek/p/6047843.html

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

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

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

[5] https://program-think.blogspot.com/2010/02/introduce-digital-certificate-and-ca.html#head-1

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