HTTPS 证书
在可靠传输的传输层协议 TCP 基础上,为 TCP 传输的有效载荷定义人类可读的文本的规则,得到了 HTTP。这些 HTTP 的内容没有被加密,因而可以被任何中间设备看到。为了保护数据的安全,在将 HTTP 数据放入 TCP 之前,使用 SSL/TLS 加密 HTTP 数据,就得到了 HTTPS。
建立 HTTPS 的过程:
- TCP 三次握手建立 TCP 连接
- TLS 四次握手建立 TLS 连接
四次握手主要完成两件事:- 客户端对服务端身份认证(证书验证)
- 对称密钥协商
密钥与服务端身份认证
通常两台通信的计算机就算直接使用网线连接,也可能在物理层面被窃听,更不用说互联网上通信的两端之间有很多设备。这些设备可以拷贝甚至修改通过该设备的数据。
对于总是可以确定的通信两端,我们可以生成一个密钥,双方都使用这个密钥加解密。双方使用相同密钥的方式,叫做对称加密。
对称加密的问题是,这个密钥在一方生成后,需要通过可信的方式同步给另一方。如果是可以确定的、数量不多的通信,可以通过交通运输的方式同步。如果和不同的组织通信,还需要生成不同的密钥,避免各方伪造其他组织的身份。
但互联网上的通信设备巨多,无法这样一一地生成对称密钥,再通过交通运输方式同步,而且这种方式的延迟也特别高。
如果有一种加密算法能够实现同一段密文的加密和解密的密钥不同,那么就可以将其中一个密钥公布出去(称为公钥),另一个密钥放在提供服务的那一方(称为私钥)。聪明的人们从数学上验证了其可行性。
现在问题转变为:公钥以什么形式公布?如果所有设备都内置这些公钥,那么设备里将会有茫茫多的公钥,不管有用的还是没用的都放进去。如果不内置,那么如何确保网上看到的公钥是官方的?就算内置了公钥,或者确认公钥是官方的,也没法保证服务方的私钥不会泄露,一旦泄露就会被中间人解密。
现在需要一个机制,确保客户端可以即时地获取服务端公钥,并且验证公钥确实是服务端生成的。
在现实生活中,如果我们要让一个第三方机构来确保一个信息的可信,会要求他们在文件上签名,或者盖上公章。在公钥的验证中,服务端可以找第三方权威机构给它的公钥签名。如果有第三方权威机构用它自己的私钥给服务端公钥签名,那么我们只需验证第三方权威机构的可信,就能够信任服务端给的公钥。
现在问题转变为:如何验证第三方机构的可信?由于服务端找第三方机构签名,那么客户端所需验证的数量从茫茫多的服务端减少到数量不多的第三方机构的数量,这时就可以将这些权威的第三方机构的公钥直接保存在设备中。
当然,这还是有风险的。正如前面说过,第三方机构也需要确保它自己的私钥不泄露。由于第三方机构的主要职责是保证安全性,因此它会投入大量的资源来确保自己的私钥不泄露。这样其他组织想要获取这些机构的私钥就会非常困难。但这也不代表不可行,例如美国政府可以在某些情况下根据法律要求权威机构提供私钥。
我们在完全信任权威机构的基础上继续讨论这个问题。既然第三方权威机构的私钥不会泄露,那么只要我们使用存储于设备上的权威机构公钥能够解开用权威机构私钥加密过的内容,就证明这些内容经过可信权威机构的认证。
总结一下,在验证服务端可信的过程中,对于服务端公钥的验证是最主要的内容,服务端公钥的可信度由权威机构保证,权威机构的可信由预先存储于设备上的权威机构公钥保证。
实际使用时,不只有公钥,还会有其他辅助信息。包含了所有相关信息的数据被称为“证书”。当然,证书里必须包含最重要的公钥。
证书与签名
由于证书内容多,如果用私钥给整个证书加密,则密文占用数据量大,解密时间长。我们需要减少加密的内容。
最先想到的是压缩。这的确可行,但内容仍然很多。能否进一步减少?
我们要验证的是整个证书的可信,而不是避免被别人看到证书的内容。证书本身的内容不需要保密,给任何人看都可以。所以我们可以找到一个信息,它尽可能地小,又能唯一代表这个证书,只要让权威机构用私钥加密这个信息即可。
hash 是一种不可逆的加密方式,它可以把任意内容转换成信息量很少的 hash 值,同时又在一定程度上确保了很难通过另一个不同的内容计算出相同的 hash 值。
使用 hash 算法计算证书的 hash 值,这个值就代表了证书本身。由权威机构计算出并加密这个 hash 值得到证书签名,将签名交给服务端,服务端会把签名跟证书一起传给客户端。
客户端收到服务端证书后,如果用权威机构的公钥能够解密签名得到 hash 值,就表示这个 hash 值是权威机构通过 hash 算法计算某个证书得到的。如果客户端自己计算证书的 hash 值,结果与解密签名后得到的 hash 值一致,就表示这个证书跟权威机构计算 hash 值时使用的证书是同一个。因此证明了这个证书是可信的。
证书的内容
服务端传给客户端的证书包含三大部分:
- 签名前的证书,包含了公钥在内的相关信息
- 签名算法
- 签名,由权威机构私钥加密签名前的证书的 hash 值生成
签名前的证书包含以下内容(仅列出部分):
- 签发对象(部分内容)
- 使用者
- 域名
- 公钥
- 服务端生成的非对称密钥中的公钥,客户端用该公钥加密的内容只有服务端能解开
- 签名 Hash 算法(如 sha256)
- 对签名前的证书内容做 hash
- 指纹
- 验证证书是否被篡改。对签名后的证书做 hash。
- 授权信息访问(Authority Information Access)
- 证书颁发者信息,包括证书颁发者自身的证书下载地址。
- 签发信息
- 颁发者
- 地址
- 有效期从
- 有效期至
- CRL(证书吊销列表)
- 在证书到期之前吊销证书,并于证书到期后删除该信息
- 现用 OCSP(在线证书检查) 替代。 CRL 用于兼容。
- 签名算法
- 对【签名 Hash 算法】得到的 Hash 值做签名
- 如 sha256RSA ,表示用 RSA 加密的 sha256
- 签名
- 签名算法对签名 Hash 算法生成的 Hash 值签名后的内容,用于验证证书信任链。
证书验证
客户端:发送支持的 SSL 或 TLS 版本,以及客户端随机数(Client Random,未加密)。
服务端:选择加密方式,并返回证书和服务端随机数(Server Random,未加密)。
证书内容:客户端:验证证书
- 使用签名 Hash 算法取得证书的 Hash 值
- 使用颁发者公钥解密签名得到 Hash 值
- 对比两个 Hash 值,相等则验证成功
- 验证颁发者的证书 …,直到证书是 Root 证书
客户端:生成预主密钥(Premaster Secret),用服务端证书公钥加密后发给服务端。
- 对称密钥 = gen(客户端随机数 + 服务端随机数 + 预主密钥)
服务端:生成对称密钥
- 对称密钥 = gen(客户端随机数 + 服务端随机数 + 预主密钥)
为什么不能只使用 Premaster Secret 生成对称密钥?
防止重放攻击。
什么是重放攻击?
中间人获取客户端发送的请求,然后多次发送该请求。
比如客户端的请求是转账 10 万元,多次请求后会导致多次转账。
为什么能够防止重放攻击?
中间人发送请求的时候,会建立新连接,此时的客户端随机数与原先的随机数不一样。服务端生成的对称密钥与原先的对称密钥不一致,无法解密。
为什么中间人不避免重新建立连接,直接使用原先的连接不就行了?
每个请求里面都有一个序列号,从 0 开始递增。服务端会维护一个滑动窗口,在这个窗口以外的序列号或者窗口内已有的序列号的请求会被丢弃。
参考
https://en.wikipedia.org/wiki/Public_key_certificate
https://www.jianshu.com/p/46e48bc517d0
https://www.jianshu.com/p/6bf2f9a37feb
http://www.moserware.com/2009/06/first-few-milliseconds-of-https.html