阅读笔记 | 《深入浅出 HTTPS:从原理到实战》

790 阅读17分钟

一、HTTP 为什么不安全?

我们常说 OSI 七层协议

osi.png 这是理想化的协议分层,让我们明白每一层都是干什么的。在实际应用中,我们更常提及的是 TCP/IP 分层模型,TCP/IP 对网络进行了抽象,将其划分为四层:

image.png

当我们在浏览器中输入一个 url,我们会看到一个对应的页面,在这个过程中,HTTP 的作用是什么呢?—— 负责数据请求和响应。那它为什么不安全呢?

  • 数据没有加密:HTTP 是基于 TCP/IP 的,TCP/IP 的特点也决定了 HTTP 数据很容易被截获,网络传输过程中,路由策略决定 HTTP 数据会通过很多节点设备,节点很轻松就能截获明文数据,由于数据没有加密,很容易理解其含义。或许我们会想到,我们在编程语言层面将数据加密不行吗?比如将 HTML 加密再发送,是一种思路,但这样 HTTP 头部仍然是明文,而且加密后的数据浏览器收到之后又该如何解密呢?
  • 无法验证身份:客户端和服务器并不能确认对方的身份,因为在 HTTP 标准中,没有校验对端身份的标准。
  • 数据易篡改:HTTP 数据在传输过程中,会经过很多节点,这些节点都可以修改原始数据,而对于客户端和服务器来说,没有任何技术来确保接收的数据就是发送者发送的原始数据。

知道这三点,我们后面可以对应看看 HTTPS 是如何解决的。

二、密码学

HTTPS 本质上就是密码学算法的组合应用,所以在了解 HTTPS 之前,可以简单了解下密码学。所谓加密,就是将一段数据处理成无规则的数据,如果没有密钥就无法知道其真正含义。

1、随机数

suijishu.png

不管是真正的随机数生成器 TRNG(True Random Number Generator),伪随机数生成器 PRNG(Preudo Random Number Generator),还是密码学伪随机数生成器 CPRNG(Cryptography secure Preudo Random Number Generator),其内部工作原理是一样的,CPRNG 是 PRNG 随机数生成器中的一种。随机数生成器内部会维护一个状态,对于 TRNG 来说,内部状态的数值来自外部设备,称为熵(entrory),比如动态的时间、变化的温度、声音的变化、鼠标位置。

而对于 PRNG 来说,内部状态的数值来自于模拟的数值,称为种子(seed)。随机数生成器每次生成随机数的时候,内部状态的值都会变化,这样才能产生不一样的随机数,如果每次熵和种子是一样的,生成的随机数也是相同的,所以熵和种子对于随机数生成器非常重要。一个优秀的随机数生成器就在于寻找尽可能多的熵和种子,一旦熵和种子不够,随机数生成器就会停止运行。(PS:我们在日常开发中常用到 PRNG)

随机数在密码学中有哪些使用呢?

use.png

2、密码学 Hash 算法

随机数生成器算法和密码学 Hash 算法都是密码学中的基础算法,很多密码学算法都是基于这两种基础算法构建的。

hash.png 密码学 Hash 算法和我们平常所说的 Hash 算法有所不同,它拥有 Hash 算法的全部特性,但从安全的角度考虑,它还有一些别的特性,比如单向性(无法逆向解密)。

密码学 Hash 算法的使用非常简单,可以用下列的公式描述:

摘要/散列值/指纹 = hash(消息)

该公式由三部分组成,hash 表示特定的 Hash 算法,消息就是输入值。由于 Hash 算法有很多功能,所以 Hash 算法有多种称呼,比如摘要算法(Message Digest Algorithms)、单向散列函数(Cryptographic One-way Hash Functions)。输出值也有多种称呼,比如摘要值、散列、指纹。

简单举两个应用的例子:

  • 文件比较:比如我们从网上下载了两个文件,我们可以通过计算两个文件的摘要值来确定它们是不是同一个文件。
  • 身份校验:数据库存储用户密码时,为了防止数据库泄漏导致的安全问题,一般会对密码做 Hash 加密处理。

密码学 Hash 算法有很多,比如 MD5 算法、SHA 族类算法,MD5 早已被证明是不安全的 Hash 算法了,目前使用最广泛的 Hash 算法是 SHA 族类算法。

SHA 族类算法包括SHA-1、SHA-2、SHA-3。

hashAlg.png

2、对称加密算法

在密码学中,加密算法主要分为对称加密和非对称加密两种。

什么是对称加密呢?一般是通过一个算法和一个密钥对明文进行处理,得到的不规则字符就是密文。

duichen.png

对称加密算法可以用下列公式简单表述:

密文 = E(明文,算法,密钥) 
明文 = D(密文,算法,密钥) 
// 用的密钥是同一个

对称加密算法分为块密码算法(每次对固定长度的数据块进行处理)和流密码(数据比特流和密钥流进行异或运算)算法:

miama.png

3、消息验证码(Message Authentication Code,MAC)算法

消息验证码一般结合加密算法一起使用。MAC 算法有两个特点:

  • 证明消息没有被篡改
  • 证明消息是正确的发送者发送的,即消息是经过验证的

怎么确保消息是正确的发送者发送的呢?通信双方可以维护同一个密钥,只有拥有密钥的双方才能生成和验证 MAC。

MAC 值 = mac(消息,密钥)

mac.png

应用比较多的 MAC 算法是 HMAC,这里的 H 表示 Hash,HMAC 基于 Hash 加密算法多次运算得到最终的 MAC 值。

由于加密算法不能提供完整性【什么叫完整性?就是消息没有被篡改;】,加密的同时必须引入 MAC 避免消息被篡改,这样就能保证数据的机密性和完整性。

4、非对称加密算法

非对称加密算法也称为公开密钥算法,它和对称加密算法有啥异同呢?

  • 功能不一样:对称加密一般用于加密和解密,非对称加密的功能更多一些,可以进行加密解密、密钥协商、数字签名等。
  • 密钥是一对:对称加密中,密钥是一串数字,加密解密使用同一个密钥,而非对称加密算法的加密和解密的密钥是不同的,分为公钥和私钥【一般以文件的形式提供】,私钥由密钥对的生成方持有,避免泄露,而公钥可以公开,任何人都可以持有。
  • 运算速度慢:非对称加密算法比如 RSA 算法运算非常慢。

RSA 是使用比较广泛的非对称加密算法,密钥文件是怎么生成的呢?【依赖于数学知识】

  • 选取两个很大的质数 p 和 q;
  • 求这两个数的乘积 n;
  • 取一个公开指数 e,e < (p-1)(q-1);
  • e 和 n 组合起来就相当于公钥;
  • 通过 e、p、q 就能计算出私钥 d,d 和 n 组合起来就是私钥,一旦计算出私钥, p 和 q 这两个数就可以丢弃,这样更安全。

RSA 用于加密时,一般是公钥加密,私钥解密;加密:

C = M^e (mod n) // M:明文,C:密文

解密:

M = C^d (mod n) // M:明文,C:密文

怎么破解私钥呢?公钥持有人知道 e 和 n,通过 n 要求出 p 和 q 是一个因式分解问题,很难暴力破解;所以它更安全~

5、密钥

密钥是加密算法的关键,一般情况下密钥一旦被截获,密文就能被解密。为了避免暴力破解,密钥应该保证一定长度,但密钥长也不代表绝对安全,密钥应该是随机且无法预测的。

密钥到底是什么呢?

  • 对称加密算法、MAC 算法使用的密钥就是一串数字
  • 非对称加密中的密钥是一对文件,但究其本质还是一串数字;

密钥怎么生成?

  • 伪随机数;——不可预测
  • 基于密码的加密;

密钥如何存储和传输?

  • 静态密钥(静态不变的)可以存储在数据库、文件、某些设备中,可以通过口头、邮件等方式告诉信息接收方;
  • 动态密钥,比如网络通信中不适合用静态密钥,该怎么传输呢?一个服务端可能需要和上千个客户端通信,需要给每一个客户端配一个密钥吗?这样也太浪费存储空间了;都用一样的密钥?不安全啊;那怎么去传输动态密钥呢?——密钥协商算法

密钥协商算法用于解决密钥的分配、存储、传输等问题;在网络通信中用到的动态密钥,也称作会话密钥,它有一些特点:

  • 作用:加解密通信数据;
  • 在加解密通信数据之前,客户端和服务端需要协商出会话密钥,这个会话密钥只有服务端和特定的客户端知道;
  • 该密钥无需储存,客户端和服务端的会话终止后,这个密钥也会消失;

RSA 算法除了可以加解密,也可以用于密钥协商,看一下它是如何协商密钥的:

  • 客户端初始化连接服务器端,服务器发送 RSA 密钥对的公钥给客户端;
  • 客户端生成一个随机值,这个值必须是随机的,不能被攻击者猜出,这个值就是会话密钥;
  • 客户端使用服务器 RSA 密钥对的公钥加密会话密钥 ,并发送给服务器端,由于攻击者没有服务器的私钥,所以无法解密会话密钥;
  • 服务器端用它的私钥解密出会话密钥;
  • 双方完成连接,接下来服务器端和客户端可以使用对称加密算法和会话密钥加密解密数据;

HTTPS 本身也是借鉴了这个方案~

miyao.png

6、数字签名

想想现实生活中的合同签名【签字或者按指纹】,我们可以以同样的思路理解数字签名 —— 为了防止抵赖,进行身份验证

数字签名是怎么回事呢?如果一个消息包含有一个唯一的指纹,那它是不是就无法抵赖呢?像前面的 RSA 密钥对,只有密钥对的生成者持有那个私钥,在不考虑私钥泄露的情况下,私钥拥有者使用密钥签署一条消息,然后发给任意接收方,接收方只要拥有公钥就能成功解密消息,因为只有密钥对的生成者知道私钥并用其“签署”消息,那它就无法抵赖说这条消息不是它发送的。这就是数字签名技术防篡改、防抵赖、防伪造

rsa.png

为什么不直接对消息进行签名,而是对消息的摘要值进行签名?因为签名值除了比较之外并没有其他用 ,那么基于消息生成签名和基于消息摘要值生成签名并没有什么区别,考虑到非对称加密算法运行是相对缓慢的,所以建议对消息摘要值进行签名,运算速度更快一点儿。

三、宏观理解 TLS

解决 HTTP 不安全的方案就是 TLS/SSL 协议。SSL 最初应用于 HTTP,TLS 是 SSL 协议的升级版,应用于其他更多协议;在理解上我们可以认为这两者是一样的,后续就用 TLS 来表示 TLS/SSL。

tls.png

TLS/SSL 协议是设计规范,落地到具体实现,有很多版本,比如 OpenSSL、LibreSSL 等;OpenSSL 比较通用,它是一个底层密码库,封装了所有的密码学算法、证书管理和 TLS/SSL 实现。在开发层面,它包括 crypto 库(密码学算法库,如 MD5、RSA)和 EVP 接口(基于 crypto 库 的进一步抽象,比如通过一个接口操作各种对称加密算法)。

HTTPS = HTTP + TLS;HTTPS 拥有 HTTP 的所有特征,并且基于 TLS 协议进行安全保护。TLS 协议核心就三大步骤:认证、密钥协商、数据加密

PS:数据加密:加密算法➕MAC 算法,由于 HTTP 传输的数据比较大,所以很少用非对称加密算法来加密数据;

数据传输需要加密,加密数据需要密钥,那怎么安全地传输密钥呢?难道还要对密钥进行加密?这不是走进了死循环?在 HTTPS 中,客户端和服务端互不认识,客户端可以是任何一台机器,必须采用动态密钥,密钥协商算法的用武之地有了。

1、密钥协商算法

前面说过了,这里不再赘述;

2、PKI (公钥基础设施)技术

密钥协商过程中也有安全隐患 —— 中间人攻击,就是服务器传递给客户端的公钥被攻击者替换,因为客户端无法确定服务器端的真实身份,它接收到一个密钥,但它无法确定这是不是真正的服务器端返回的密钥,那怎么去确定密钥的真正主人呢?—— PKI 技术,其核心是【证书】。

pki.png

PKI 技术不是 TLS 协议的一部分,但是在 HTTPS 中必须引入 PKI 技术才能保证安全。

就好比我们去银行办理业务,银行需要确认我们的身份,我们无法自证,所以国家给我们每个人发了一张身份证,有了身份证,银行就能确认我们是本人了。为什么银行一定会信任身份证呢?因为身份证是国家签发的,银行作为国家的一部分,必须信任国家,这是基础。即所有的信任都有一个基本前提。

PKI 同理,它由多个组织组成,组织之间基于一定的信任基础,主要有这几部分:

  • 服务器实体:HTTPS 网站的提供者
  • 客户端
  • CA 机构:像上面例子的国家角色

CA 会签发一张证书,证书中包含了一些关键信息(比如服务器的域名、公钥等等),客户端基于对 CA 机构的信任,就可以基于证书验证服务器的身份。证书,本质就是一个普通的文件,客户端怎么去校验这个文件呢?解决方案就是数字签名技术。

CA 也拥有一个密钥对,它用私钥对证书进行数字签名,将签名的证书发送给服务器,浏览器访问服务器时,服务器发送证书给浏览器,浏览器拥有 CA 机构的公钥(内嵌在浏览器中),然后校验证书的签名,一旦校验成功,就代表这个证书是可信的 CA 机构签发的。证明这个证书是可信的 CA 机构签发的,并不代表这个服务器就可以信任。需要进一步验证:比如用户访问了 www.baidu.com ,浏览器验证证书中的域名,发现是一样的,说明身份验证成功。最后浏览器从证书中拿到服务器的公钥,用来进行密钥协商。

浏览器怎么拿到 CA 机构的公钥呢?浏览器集成了 CA 机构的根证书,根证书包含了验证签名的公钥,这是信任的基础。CA 机构颁发证书的过程是极其严谨的,不然它要是给攻击者颁发了证书,还有谁会信任它呢。

作为开发者,如果我们想弄一个 https 网站,我们需要给我们的服务器申请证书

  • 服务器实体生成非对称加密算法的一对密钥,比如一对 RSA 密钥;
  • 服务器实体生成一个 CSR(Certificate Signing Request)文件,其中包含网站域名、密钥对中的公钥、营业执照,将其发送给 CA 机构申请证书;
  • CA 收到 CSR 文件后,核实申请者的身份;(比如校验域名的拥有者是不是证书申请者)
  • 审核成功后,CA 机构利用自己的密钥对中的私钥签名 CSR 文件的内容得到签名值,将签名值附在 CSR 文件后面得到证书文件,证书文件中包含申请者和 CA 机构的信息;
  • CA 机构将证书颁发给服务器实体。

客户端怎么校验呢?

  • 浏览器请求 https 网址;
  • 服务器接收到请求后,将证书文件和 RSA 密钥对的公钥发送给浏览器;
  • 浏览器收到证书,从中判断是哪个 CA 机构签发的证书,并且知道证书的签名算法,由于浏览器内置了该 CA 机构的根证书,根证书包含了签名算法使用的公钥,因此可以用于验证签名;
  • 浏览器验证签名成功,表示该证书确实是合法的 CA 机构签发的;
  • 浏览器接着校验申请者的身份,从证书中取出 RSA 公钥和主机名,如果证书中的信息和实际信息一致,则表示校验成功;
  • 一旦服务器身份校验成功,接下来就可以进行密钥协商了。

3、HTTPS

HTTPS设计的很巧妙,主要由两层组成:握手层和加密层

tlswoshou.png

握手层在加密层的上层,握手层提供加密层所需的密钥,对于一个 HTTPS 请求来说,HTTP 消息没有完成握手之前,是不会传递给加密层的,一旦握手层处理完毕,消息会交由加密层进行加密。

1)握手层

客户端和服务端交换一些信息,比如协议版本号、随机数、一些密码算法【称作密码套件,包括身份验证算法、密码协商算法、加密算法等】等,经过协商,服务器确定本次连接使用的密码学算法;客户端通过证书验证服务器端的身份后,双方开始密钥协商,协商出主密钥、预备主密钥、密钥块,接下来由加密层处理;

关键步骤:认证、密码套件协商、密钥协商

【为什么要协商密码套件呢?直接确定一些加密算法不行吗?】客户端的运行环境无法预知,有可能是各种操作系统、各种版本,导致一些算法不能被支持,所以必须协商出双方都支持的算法。

【握手完整性校验】握手消息会经过 TLS 协议加密层保护,能够确保机密性和完整性,如果攻击者篡改握手消息,整个握手过程会失败。

2)加密层

加密层拥有握手层提供的密钥块,在握手层完成之后才能进行加密,这也是 TLS 缓慢的原因。加密层逻辑比较简单,这里不再赘述。

tlsjiami.png

4、构建 HTTPS 网站

证书获取途径:

  • 向免费的 CA 机构申请;比如 Lets Encrypt
  • 向收费的 CA 机构申请;
  • 生成自签名的证书;

配置的话,两个关键点:

  • 证书、密钥对配置;
  • 密码套件、TLS 协议版本等配置;

PS: 前端配置 Nginx 示例

// 80 端口
server {
  listen 80;
  server_name  xxx.com;
  return 301 https://$server_name$request_uri;  // 重定向到 https 网址
}

// 443 端口
server { 
  listen 443;
  server_name xxxx.com; 
  ssl on; 
  root html; 
  index index.html index.htm; 
  ssl_certificate cert/214897021540762.pem; // 将 SSL 证书放在 cert 目录下    
  ssl_certificate_key cert/214897021540762.key; // 对应密钥 
  ssl_session_timeout 5m; 
  ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; 
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 
  ssl_prefer_server_ciphers on; 
  location { 
    root html; 
    index index.html index.htm; 
  }
}

HTTPS 性能问题】导致性能消耗的原因主要有两个:握手阶段和密码学方面的计算。握手阶段服务端和客户端来回传递消息,消息量虽然不大,带来了延迟,可以使用 HTTP/2 能提升性能;密码学运算方面,加解密会消耗 CPU 的计算能力,客户端和服务端都需要计算,请求多的时候服务端的消耗更大。但是 CPU 消耗比例小于 1%,综合安全和性能来看,是利大于弊的。