SSL 认证体系备忘录

本贴最后更新于 2803 天前,其中的信息可能已经事过境迁

本文不涉及 SSL 通信相关的知识,只介绍一下最近通过我的调研,了解到的关于 SSL 认证相关的东西。

  • JSSE Reference Guide
    一个关于 SSL 的比较专业的介绍,来自 JSSE(Java Secure Socket Extension)

什么是密钥

要说证书,需要先说一下 密钥 是怎么回事。
目前的加密算法,分为对称加密和非对称加密两类,而密钥,可以把它想像成一把钥匙:

  • 对称加密: 使用同一把钥匙,即可实现对一段信息的加密和解密
  • 非对称加密:加密和解密信息,需要使用不同的钥匙

对称加密的优点就是速度快,所以可以用来加密大段的信息,但缺点也很明显,因为通信双方需要提前交换密钥,那么密钥本身就存在泄露的风险。

而非对称加密,由于加密和解密使用不同的密钥,所以只需要将其中一个密钥发送给对方(传说中的 公钥),而另一个密钥由自己保管(也就是 私钥),这样就避免了在传输环节丢失的风险。

不过,非对称加密算法非常复杂,所以性能比对称加密要低很多,而且对处理的信息长度有限制。所以在实际使用中,信息仍然以对称加密算法进行加密,而对称加密算法使用的密钥,由非对称加密算法进行加密传输。

常见的加密算法:

  • 对称加密:DES 3DES AES
  • 非对称加密:RSA DSA ECC

什么是签名

签名 其实就是通过某种计算,将一段信息生成一个摘要,比如我们常见的 MD5 算法,就是干这个的。此外,常用的还有 SHA 算法。

签名并不是完美的

一般来讲,签名都是固定长度的,比如 MD5 生成的就是一个 128 位的二进制串。

不论源信息是一个字符,还是一整部小说,通过 MD5 算法生成的签名长度都是固定的,而固定长度的签名能表达的信息量是有限的,这也就导致了信息在转换过程中必定是有损不可逆的。

另一方面,由于签名只能表达有限个数的信息,而自然界中的信息数量则是无穷尽的,所以,必定导致不同的信息签名是会重复的。话虽如此,想要找到重复的签名并没有那么容易,比如 MD5 能够表达 2^128 种信息,想在这么多信息中找到一个重复的,想必也没那么容易。在术语中,将这种重复称之为 冲突碰撞

什么是证书

证书本身和密钥是两个平行的概念。证书存在的目的,是对某个东西的认证。比如,你当前访问的网站的真实性、电子合同的有效性等等。而证书存在的形式,大体上就是由一个认证机构标识一个由它签发的签名组成。

认证机构——我们称之为 CA (Certificate Authority)。只有受信的 CA 颁发的证书,才被信任。而一个 CA 是否被信任,则是由验证一方自己决定的。比如,操作系统会有一个受信的 CA 列表,在系统中的软件想要验证一个 CA 是否可信时,就可以把这个工作交给系统来完成。当然,也有自立门户的,比如 JRE,自己有一个受信的 CA 列表,完全无视操作系统的 CA 认证。

一些浏览器,比如 Chrome,也有自己的 CA 列表,不过他们应该是将自己的列表和系统的进行了一个合并。

这样就出现了如下情况:**某些机构的证书在一个系统中好使,在另一个系统中不好使,或者不同浏览器表现也不一样。**而 Java 从版本 7 开始,好似大幅减少了受信的 CA 列表,导致像沃通、StartSSL 的证书都不在列表中,所以在 Java7 中进行 SSL 握手时,对果对方是这两个机构的证书的话,默认是不可信的,也就导致了握手失败。

沃通是国内做得比较大的 CA 机构,之前我们服务器的证书都是从沃通申请的;StartSSL 是国际上用户量比较大的一个 CA。

在某个时间,沃通收购了 StartSSL,又在某个时间,Mozilla 宣布暂时将沃通和 StartSSL 移出受信列表,原因是他们使用不再安全的 SHA1 算法且恶意欺骗浏览器,来龙动脉可以关注知乎话题 如何看待 Mozilla 决定停止信任沃通 (WoSign) 和 StartCom 颁发的证书?

后来,Safari 和 Chrome 也跟随 Mozilla 取消了沃通和 StartSSL 的受信。不过,据说再后来的版本已经相继又恢复了这两个 CA 的受信。江湖实在险恶,我等凡夫俗子看个热闹就好。

我们的服务器现在用的是 LetsEncrypt 颁发的证书,通过测试,这个证书在各类客户端中兼容性都还不错,不过在 Java7 中还是不认识这个证书。

如何证明“我就是我”

刚才说的那套证书认证体系,要想工作得很好,就必须解决两个问题:

  1. 如何知道一个证书就是由某个 CA 签发的,而不是由别人伪造的
  2. 如何验证某个证书所认证的内容是否发生了改变

解决第 1 个问题

这就要用到之前说的 非对称加密 技术了,流程大概是这个样子的:

  1. CA 生成自己的一套证书,其中包含 公钥私钥
  2. CA 将 私钥 放进自己的保险库,并雇佣黑帮 7*24 小时看守
  3. CA 将 公钥 公之于众
  4. 验证者通过公开信息获取到这个 CA 的 公钥,并将此 CA 列入受信列表
  5. CA 用自己的 私钥 签发一个证书
  6. 验证者拿到证书后,根据证书上的标识找到这个 CA,发现其在自己的受信列表中,于是找到这个 CA 的 公钥,并使用 公钥 来尝试解密证书内容
  7. 如果解密成功,则证书验证通过,否则验证失败

解决第 2 个问题

这就要用到之前说的 签名 了。

每个证书除了包含对自己的身份验证信息外,还包含了对所认证内容的签名。验证者将需要验证的内容以相同的算法做签名,然后将此签名和证书上记录的签名做对比,如果签名相同,则表示内容无误,否则说明内容已经被修改了。

证书链(Certificate Chain)

在实际使用过程中,一个 SSL 证书很可能是多级认证的,也就是说 A 机构认证了 B,B 又认证了 C,而证书最终是由 C 签发的。一般来讲,在这整个环节中,只有 A 是受信的 CA,而 B 和 C 有可能是 A 自己生成的分级证书,或是 A 的合作机构。

下面是一张画得非常棒的图示,很好的说明了证书链的工作模式:
证书链的工作模式
图片来自 http://blog.csdn.net/shen_guo/article/details/49891459

可以使用 openssl 命令来查看某个网站的证书信息:
openssl 查看某网站的证书

其中的 0/1/2 就是证书链信息。

解决 Java 高版本中的证书问题

前文提到,Java 从版本 7 开始,取消了大量的受信 CA,很不幸我们自己的证书 CA (LetsEncrypt) 也被取消了。好在各种前端系统还是认我们的证书的,所以这只是对我们后台程序之间互相通信产生了影响。

那如何让 Java 信任我们的 CA 呢?就是将我们的 CA 手动加入到信任列表中。有两种办法可以做到:

  1. 将 CA 根证书导入 Java 的受信 CA 列表,这样所有的 Java 程序都将信任此 CA,但缺点是需要在所有机器上进行手动导入。
  2. 在程序中将 CA 根证书加入信任列表,这样只有在当前会话中,该 CA 才被认可,不对其它程序产生影响,且无需手动修改机器配置。

我决定使用第 2 种方式来实现我们的需求。

zlhcommon 库中,包 com.zhilehuo.server.zlhcommon.ssl 下面有两个类:CompositeX509TrustManagerCompositeX509TrustManagerAgent。其中,CompositeX509TrustManagerAgentCompositeX509TrustManager 的基础上,可以快速将沃通、StartSSL、LetsEncrypt 证书添加至受信列表,也可以方便的将其它证书添加至受信列表。

此外,com.zhilehuo.server.zlhcommon.httptools.ZlhHttpClientBuilder 这个类,可以快速生成基于 CompositeX509TrustManagerAgentCloseableHttpClient,并结合 SimpleRequest 类,进行 HTTPS 请求。

Java 中自定义受信 CA 的坑

  • 默认情况下,Java 在设置了自定义 CA 后,系统就不再认识原有的 CA 列表,这就要求我们在请求之前,先判断目标站点是否是受信站点,如果不是,再加载我们自己的证书,这样比较麻烦
  • 默认情况下,自定义 CA 每次只允许设置一个证书文件,无法同时将多个 CA 证书导入,这样实际情况下也会遇到一些麻烦

所以,我们才在 zlhcommon 库中,集成了 CompositeX509TrustManager 类,来解决这些问题。

  • 签名
    4 引用 • 3 回帖
  • SSL

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

    70 引用 • 193 回帖 • 416 关注
  • 证书
    11 引用 • 41 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...