HTTPS(SSL/TLS)的加密机制虽然是大家都应了解的基本至少,但是网上很多相关文章会忽略一些内容,没有阐明完整的逻辑脉络。

对称与非对称加密、数字签名、数字证书等,在学习过程中,除了了解它是什么,你是否想过为什么是他,我任务理解了后者才真正理解了HTTS的加密机制

# 为什么需要加密

因为http的内容是明文传输,明文数据会经过中间代理服务器、路由器、wifi热点、通信服务运营商等多个物理节点,如果信息在传输过程中被劫持,传输的内容就完全暴露了。劫持者还可以篡改传输的信息且不被双方察觉,这就是中间人攻击。所以我们才需要对信息进行加密,最容易里面的就是对称加密

# 什么是对称加密

简单来说说就是有一个密钥,它可以加密一段信息,也可以对加密后的信息进行解密,和我们日常生活中的钥匙作用差不多

对称加密

# 用对称加密可行吗?

如果通信双方都各自持有同一个密钥,且没有别人知道,这两方的通信安全当然是可以被保证的(除非密钥被破解)

然后最大的问题就是这个密钥怎么让传输的双方知晓,同时不被别人知道。如果由服务器生成一个密钥并传输给浏览器,那么在这个传输过程中密钥被别人劫持到手了怎么办?之后他就能用密钥解开双方传输的内容了,所以怎么做当然不行。

换种思路?试想一下,如果浏览器内部就预存了网站A的密钥,且可以确保除了浏览器和网站A不会有任何人知道该密钥,那么理论上用对称加密是可以的,这样浏览器只要预存好世界上所有HTTS网站的密钥就可以了!怎么做显然不现实。

怎么办?所以我们就需要非对称加密

# 什么是非对称加密

简单来说就是有两把密钥,通常一把叫做公钥,一把叫私钥,用公钥加密的内容必须用私钥才可以解开,同样,私钥加密的内容只有公钥能解开

非对称加密

# 用非对称加密可行吗?

鉴于非对称加密的机制,我们可能会有这种思路:服务器先把公钥以明文方式传输给浏览器,之后浏览器想服务器传数据前先用这个公钥加密好在传,这条数据似乎就可以保证了,因为只有服务器有响应的私钥能解开公钥加密的数据

然后反过来由服务器到浏览器的这条路怎么保障安全呢?如果服务器用他的私钥加密数据传输给浏览器,那么浏览器用公钥可以解密它,而这个公钥是从一开始通过明文传输给浏览器的,若这个公钥被中间人劫持了,那他也能用公钥解密服务器传来的信息了。所以目前似乎只能保证浏览器向服务器传输数据的安全性(其实仍有漏洞,下文会说),那利用这点你能想到什么解决方案呢?

# 改良的非对称加密方案,似乎可以?

我们已经理解通过一组公钥私钥,可以保证单个方面传输的安全性,那用两组公钥私钥,是否就能保证数据安全了?

  1. 某网站服务器拥有公钥A与对应的私钥A',浏览器拥有公钥B和私钥B'
  2. 浏览器把公钥B明文传输给服务器
  3. 服务器把公钥A明文传输给浏览器
  4. 之后浏览器想服务器传输的内容都用公钥A加密,服务器收到后用私钥A'解密,由于只有服务器拥有私钥A',所以能保证这条数据安全
  5. 同理,服务器想浏览器传输的内容都用公钥B加密,浏览器收到后用私钥B'解密。同上也可以保证这条数据安全。

的确可以,抛开这里面的漏洞不谈,HTTS的加密却没有使用这种方案,为什么?很重要的原因是非堆成加密算法非常好使,而对称加密快的多。那我们能不能运用非对称加密的特性解决签名提到的对称加密的漏洞

# 非对称加密 + 对称加密

既然非对称加密耗时,那非对称加密+对称加密结合可以吗?而且尽量减少非对称加密的次数。当然可以,且非对称加密、解密个只需用一次即可。

  1. 某网站用于非对称加密的公钥A和私钥A',
  2. 浏览器向网站服务器请求,服务器把公钥A明文传输给浏览器
  3. 浏览器随机生成一个用于对称加密的密钥X,用公钥加密后传给服务器
  4. 服务器拿到这个私钥A'解密得到密钥X
  5. 这样双方都拥有了密钥X了,且别人无法知道它,之后双方所有数据都可以通过密钥X加密解密饥渴。

完美HTTPS基本就是采用了这种方案。完美?还是有漏洞

非对称加密+对称加密

如果数据传输过程中,中间人劫持了数据,此时他的确无法得到浏览器生成密钥X,这个密钥本身被公钥A加密了,只有服务器私钥A'揭开它,然而中间人却完全不需要拿到私钥A'就能干坏事了,请看

  1. 某网站有用非对称加密公钥A、私钥A',
  2. 浏览器向网站服务器请求,服务器把公钥明文传输给了浏览器。
  3. 中间人劫持到公钥A,保存下来,把数据包中的公钥A替换成自己伪造的公钥B(它当然也拥有公钥B对应的私钥B')
  4. 浏览器生成一个对称加密的密钥X,用公钥B(浏览器无法得知公钥被替换了)加密后传给服务器
  5. 中间人劫持到用私钥B'解密得到密钥X,再用公钥A加密后传给服务器
  6. 服务器拿到后用私钥A'解密得到密钥X

这样在双方都不会发现异常的情况下,中间人通过一套操作,掉包了服务器传来的公钥,进来得到密钥X,更笨原因是浏览器无法确认收到的公钥是不是网站自己的,因为公钥本身是明文传输的,难道还得对公钥传输进行加密,这似乎变成鸡生蛋,蛋生鸡的问题了,解法是什么?

# 如何证明浏览器收到的公钥一定是该网站的公钥?

其实所有证明的源头都是一条或者多条不证自明的"公理"(可以回想一下数学上公理),由它推导出一切。比如现实生活中,若想证明某身份证号一定是小明的,可以看他身份证,而身份证是由政府做证的,这里的公理就是政府机构可信,这也算社会正常运作的前提。

那能不能类似有个机构充当互联网世界的公理呢?让他作为一切证明的源头,给网站办法一个身份证?

它就是*CA机构**,它是如今互联网世界正常运作的前提,而CA机构办法的身份证就是数字证书。

# 数字证书

网站在使用HTTS钱,需要想CA机构申领一份数字证书,数字证书里含有证书持有者信息、公钥信息等。服务器把证书传输给浏览器,浏览器从证书里获取公钥就行了,证书就如身份证,证明"改公钥对应该网站"。而这里又是一个显而易见的问题,"证书本人的传输过程中",如果防止篡改?即如果证明证书本身的真实性?身份证运用了一些防伪技术,而数字证书呢怎么防伪呢?解决这个问题就接近生了

# 如果防止数字证书被篡改

我们把证书原本的内容生成一份签名,比对证书内容和签名是否一致就能判断是否被篡改,这就是数字证明的防伪技术,这里的签名就叫数字签名

# 数字签名

这部分内容建议看下图并结合后面的文字理解,图中左侧是数字签名的制作过程,右侧是验证过程:

数字签名

数字签名过程

  1. CA机构拥有非对称加密的私钥和公钥。
  2. CA机构对证书明文数据T进行hash
  3. 对hash后的值用私钥加密,得到数字签名S

明文和数字签名共同组成了数字证书,这样一份数字证书就可以颁发给网站了

那浏览器拿到这个服务器传来的数字证书后,如果验证它是不是真的?(有没有被篡改,掉包)

浏览器验证过程

  1. 拿到证书,得到明文T,签名S
  2. 用CA机构的公钥对S解密(由于是浏览器的信任机构,所以浏览器保有它的公钥,详情见下文),得到S'
  3. 用证书里指明的hash算法对明文T进行hash得到T'
  4. 显然通过以上步骤T'应该等于S',除非明文或签名被篡改,所以此时S'是否等于T',等于则证明证书可信,

为何这样保证证书可信呢,我们仔细想一下

# 中间人有可能篡改该证书吗?

假设中间人篡改了证书的原文,由于他没有CA机构的私钥,所以我发得到此时加密后的签名,无法相应的篡改签名。浏览器收到该证书后会发现原文和签名解密后的值不一致,则说明证书已被篡改了,证书不可以,从而终止向服务器传输信息,防止信息泄露给中间人。

既然不能篡改,那整个证书掉包呢?

# 中间人有可能把证书掉包吗?

假设有另一个网站B也拿到了CA机构认证的证书,它想劫持网站A的信息。于是它成为中间人拦截到了A传给浏览器的证书,然后替换成自己的证书,传给浏览器,之后浏览器就会错误地拿到B的证书里的公钥了,这确实会导致上文“中间人攻击”那里提到的漏洞?

其实这并不会发生,因为证书里包含了网站A的信息,包括域名,浏览器把证书里的域名与自己请求的域名比对一下就知道有没有被掉包了。

# 为什么制作数字签名时需要hash一次

我初识HTTPS的时候就有这个疑问,因为似乎那里的hash有点多余,把hash过程去掉也能保证证书没有被篡改。

最显然的是性能问题,前面我们已经说了非对称加密效率较差,证书信息一般较长,比较耗时。而hash后得到的是固定长度的信息(比如用md5算法hash后可以得到固定的128位的值),这样加解密就快很多。

当然也有安全上的原因,这部分内容相对深一些,感兴趣的可以看这篇解答:crypto.stackexchange.com/a/12780

# 怎么证明CA机构的公钥是可信的?

你们可能会发现上文中说到CA机构的公钥,我几乎一笔带过,“浏览器保有它的公钥”,这是个什么保有法?怎么证明这个公钥是否可信?

让我们回想一下数字证书到底是干啥的?没错,为了证明某公钥是可信的,即“该公钥是否对应该网站”,那CA机构的公钥是否也可以用数字证书来证明?没错,操作系统、浏览器本身会预装一些它们信任的根证书,如果其中会有CA机构的根证书,这样就可以拿到它对应的可信公钥了。

实际上证书之间的认证也可以不止一层,可以A信任B,B信任C,以此类推,我们把它叫做信任链或数字证书链。也就是一连串的数字证书,由根证书为起点,透过层层信任,使终端实体证书的持有者可以获得转授的信任,以证明身份。

另外,不知你们是否遇到过网站访问不了、提示需安装证书的情况?这里安装的就是根证书。说明浏览器不认给这个网站颁发证书的机构,那么你就得手动下载安装该机构的根证书(风险自己承担XD)。安装后,你就有了它的公钥,就可以用它验证服务器发来的证书是否可信了。

# 每次进行HTTPS请求时都必须在SSL/TLS层进行握手传输密钥吗?

这也是我当时的困惑之一,显然每次请求都经历一次密钥传输过程非常耗时,那怎么达到只传输一次呢?

服务器会为每个浏览器(或客户端软件)维护一个session ID,在TLS握手阶段传给浏览器,浏览器生成好密钥传给服务器后,服务器会把该密钥存到相应的session ID下,之后浏览器每次请求都会携带session ID,服务器会根据session ID找到相应的密钥并进行解密加密操作,这样就不必要每次重新制作、传输密钥了!

# TLS协议

TLS协议位于应用层和传输层之间,首次进行TLS协议传输需要两个RTT,接下来可以通过Session Resumption减少到一个RTT

建立了TCP虚拟通道,那么这三次握手需要几个RTT(Round Trip Time的缩写,通俗地说,就是通信一来一回的时间)时间呢

# 资料

HTTPS请求流程 (opens new window)

HTTPS请求流程2 (opens new window)

http2 over tls1.2 tls1.3 quic RTT对比 (opens new window)

RTT是什么 (opens new window)