4

浅谈 HTTPS

相信大家都会注意到很多网站的地址栏上会出现一个绿色的小锁,比如:

如果地址栏中出现了这样子的绿色小锁,则表示当前页面是通过 HTTPS 传递的,只要证书是正确的,那么目前来说可以保证网页内容没有被篡改以及即使第三者截取到了通信内容也无法从密文获得明文。

什么是 HTTPS?

HTTPS 类似于 HTTP 协议的“安全版”,其全称为超文本传输安全协议(英文:HyperText Transfer Protocol Secure,缩写 HTTPS),以下引用自维基百科:

超文本传输安全协议是一种透过计算机网络进行安全通讯的传输协议。HTTPS 经由 HTTP 进行通讯,但利用 SSL/TLS 来加密封包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换资料的隐私与完整性。这个协议由网景公司(Netscape)在1994年首次提出,随后扩展到互联网上。

HTTPS 的用途就是在不安全的网络上建立安全的信道,用于传输敏感数据(比如信用卡号、密码等)或者用于保护互联网账户不被盗取、保证网页数据的正确性等。

HTTPS 有什么用?

通过 HTTP 协议传输的数据就像 Tom 给 Jerry 写信,Tom 把写好的信直接交给了邮递员,信封没有密封甚至直接没有信封,邮递员可以随意查看 Tom 给 Jerry 的信件、修改信件里的内容甚至冒充 Tom 与 Jerry 进行通信。

这明显是不安全的。

Tom 为了不让邮递员偷看他给 Jerry 的信件并确保自己的信件不会被替换,决定在信的最后附上自己的签名,并在信封上加上火漆印章(一旦信件被私自拆阅则印章就会损毁),这时,如果 Tom 再给 Jerry 写信,Jerry 只需要在收到信件的时候检查信封上的火漆印章和信件最后的签名,就可以确保收到的信件没有被邮递员拆阅或者替换过。

在这个例子中,Tom 和 Jerry 分别代表客户端和服务器(或者服务器与服务器、客户端与客户端),邮递员相当于不安全的网络信道(比如包含 ISP、代理服务器等),而信件就相当于 HTTP 请求中的内容。Tom 签名及盖火漆印章的过程相当于加密,验证签名和火漆印章完整性的时候就相当于身份验证和解密。

在实际中,不使用 HTTPS 的风险不仅是数据被随时监视,无良的 ISP 可能还会给网页上加上广告,甚至可以随意增删通过 HTTP 传输的网站代码,这非常危险。而 HTTPS 可以避免这些风险。

HTTPS 连接的握手过程

我们可以使用 curl 命令来简略查看建立 HTTPS 时的握手过程,在命令行中执行:curl -v -I -L https://ymfe.org

能得到如下的输出:

curl_https_ymfe.org

简单说明一下连接的建立过程:

# 表示建立了和 ymfe.org 服务器 443 端口的连接。
Connected to ymfe.org (123.56.155.201) port 443 (#0) 

# 客户端发出 client_hello 消息。
TLSv1.2 (OUT), TLS handshake, Client hello (1): 

# 服务器发出 server_hello 消息。
TLSv1.2 (IN), TLS handshake, Server hello (2): 

# 服务器发出 certificate 消息。
TLSv1.2 (IN), TLS handshake, Certificate (11): 

# 服务器发出 server_key_exchange 消息。
TLSv1.2 (IN), TLS handshake, Server key exchange (12): 

# 服务器发出 server_done 消息。
TLSv1.2 (IN), TLS handshake, Server finished (14): 

# 客户端发出 client_key_exchange 消息。
TLSv1.2 (OUT), TLS handshake, Client key exchange (16): 

# 客户端发出加密后的 client_hello 消息。
TLSv1.2 (OUT), TLS change cipher, Client hello (1): 

# 客户端发出 hello_done 消息。
TLSv1.2 (OUT), TLS handshake, Finished (20): 

# 服务器将加密后的 client_hello 消息发回。
TLSv1.2 (IN), TLS change cipher, Client hello (1): 

# 握手结束。
TLSv1.2 (IN), TLS handshake, Finished (20): 

# SSL 连接采用 ECDHE-RSA-AES256-GCM-SHA384 密码套件。
# ECDHE 表示密钥交换方法采用椭圆曲线迪菲-赫尔曼交换方法
# RSA 表示密钥交换中使用的签名方式
# AES-256-GCM 表示的是对称加密算法
# SHA-384 表示的是内容完整性校验使用的哈希算法
SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384 

# 之后的几行包含了证书的内容,包括有效时间、常用名、证书签发机构等。
Server certificate: 
      # Common Name 为 ymfe.org
    subject: CN=ymfe.org
      # 在此时间之前无效
    start date: Aug 31 05:50:00 2017 GMT
      # 在此时间之后无效
    expire date: Nov 29 05:50:00 2017 GMT
      # 域名和证书的域名匹配
    subjectAltName: host "ymfe.org" matched cert's "ymfe.org"
      # 签发者是 Let's Encrypt
    issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3

握手的详细步骤

client_hello

这一步中,浏览器会向服务器发出建立 HTTPS 的请求,在请求中,浏览器会带上一些建立连接的必要信息(注意:这一步的信息全都是明文的),包括:

  1. 版本。客户端支持的最高的 TLS 协议版本从高到低依次为:TLS v1.2, TLS v1.1, TLS v1.0, SSL v3, SSL v2。其中低于 TLS v1.0 的版本基本不再使用,因为 SSL v3 和 SSL v2 都存在漏洞,Google 和 Mozilla 已明确禁用 SSL 协议。
  2. 密码套件。按优先级降序排列的、客户端支持的加密套件列表。每个加密套件会各包含一个认证算法(用于身份验证)、密钥交换算法(用于协商密钥)、对称加密算法(用于消息加密)和信息摘要算法(用于完整性校验)。
  3. 压缩方法。客户端支持的用于压缩消息、降低传输体积的压缩算法列表。
  4. 随机数。一个由客户端生成的随机数,使用 32 位时间戳和一个安全随机数生成器生成的 28 字节随机数组成。这个随机数用于后续 Master Key 的生成,并防止重放攻击。
  5. 会话标识。一个变长的会话标志。非 0 值意味着客户端希望更新当前已存在的连接的参数或者为此连接创建一个新的连接。0 值表示客户端想在新会话上创建一个新连接。
  6. 扩展字段。包含一些其他的相关参数(比如 SNI)。

server_hello

客户端在发出 client_hello 消息之后,会等待服务器返回 server_hello 消息,包含和 client_hello 相同的参数。一般来说,参数结构如下:

  1. 版本。包含客户端支持的最低版本和服务器支持的最高版本。
  2. 随机数。由服务器生成的不同于客户端在 client_hello 中发来的随机数的另一个独立的随机数。
  3. 会话标识。如果客户端发送的会话标识不为 0,服务器会使用与客户端发送的一致的会话标识,否则返回的是服务器生成的一个新的会话标识。
  4. 密码套件。包含了服务器从客户端发来的密码套件列表中选择出的将要使用的密码套件。
  5. 压缩方法。包含了服务器从客户端发来的压缩方法列表中选择出的将要使用的压缩方法。

(certificate) (+ server_key_exchange) (+ certificate_request) + server_hello _done

通常来说,服务器会在 certificate 消息中发送其自身的证书供客户端进行验证,这个消息包含一个或一组 X. 509 证书。如果采用的是固定 Diffie-Hellman 方法(更多关于 Diffie-Hellman 的内容请查看维基百科上的词条 Diffie-Hellman),此 certificate 消息将使用服务器 Diffie-Hellman 公钥参数作为服务器的密钥交换消息。有一种密钥交换方法——匿名 Diffie-Hellman 方法,不需要 certificate 消息。该算法使用基本的 Diffie-Hellman 算法,在向对方发送其 Diffie-Hellman 公钥参数时,不进行认证(因为没有认证,所以这种方法容易受到中间人攻击)。

接着,除了两种情况:(1) 服务器发送了带有固定 Diffie-Hellman 参数的证书;(2) 使用 RSA 密钥交换;不需要发送 server_key_exchange 消息,其余情况服务器均会发送 server_key_exchange 消息,用以向客户端发送之后用以生成密钥的各项参数。该消息的内容根据密钥交换算法不同而不同,具体内容不在本文讨论之列。该项参数作为 server_key_exchange 消息的第一项值。

然后,服务器将对消息使用散列函数(根据不同签名算法选择不同的散列函数,例如 MD5 或者 SHA-1 等)对客户端发来的随机数、服务器生成的随机数以及 server_key_exchange 消息中的各项参数进行求值并使用服务器的私钥进行签名。该值作为 server_key_exchange 消息中的第二项值。

如果服务器需要验证客户端的身份(即双向认证),则会发送 certificate_request 消息,请求客户端发送其自身的证书。

最后,服务器发送 server_hello_done 消息,表明服务器的 hello 相关的消息结束。在发送此消息之后,服务器会等待客户端应答,该消息没有参数。

(certificate) + client_key_change (+ certificate_verify)

客户端在收到服务器发来的 server_hello_done 消息之后,会验证服务器提供的证书是否合法,并检查 server_hello 的各项参数。如果验证通过,则客户端会向服务器发送一条或多条消息。

如果服务器发送了 certificate_request 消息,且客户端有合适的证书,则客户端会发送一条 certificate 消息,否则会发送一个“无证书警报”。

然后客户端会发送 client_key_exchange 消息,其内容取决于密钥交换的类型:

  1. RSA:客户端生成 48 字节的 PreMaster Key,并使用服务器证书中的公钥或者服务器密钥交换消息中的临时 RSA 密钥加密。这个密钥会被用于之后的 Master Key 的计算。
  2. 瞬时或匿名 Diffie-Hellman: 发送客户端的 Diffie-Hellman 公钥参数。
  3. 固定 Diffie-Hellman: 由于证书消息中包括 Diffie-Hellman 公钥参数,所以该消息为空。

最后,如果客户端具有证书且证书具备签名能力(即除了带固定 Diffie-Hellman 参数外的所有证书),可以发送一个 certificate_verify 消息来提供对客户端证书的精确认证。

change_cipher_spec + finished

经过以上步骤,客户端和服务器已经可以通过得到的消息计算出 Master Key 了。从现在开始,客户端和服务器都将开始使用协商好的加密算法、密钥进行通信,在正式传递消息之前会计算 Master Key 及 Master Key 和之前握手过程中收到的所有信息的 hash,并通过协商好的加密算法使用 Master Key 加密,作为 change_cipher_spec 消息的内容,接着发送 finished 消息。服务器在收到客户端发来的 change_cipher_specfinished 消息之后,也会计算 Master Key 并使用协商好的加密算法和 Master Key 计算 Master Key 和之前握手过程中收到的所有信息的 hash,发回给客户端用以验证。至此,握手阶段结束,之后就可以交换应用层的内容了。

如何加密应用层内容

细心的读者可能发现,在建立连接的过程中,交换的仅仅是密钥而不是内容,为什么这么复杂的过程仅仅交换了密钥呢?

对称加密和非对称加密

常用的加密方式分为两种:

  • 对称加密:加密和解密使用的是相同的密钥。
  • 非对称加密:加密和解密使用的不是相同的密钥,而是一对密钥对,分别称为公钥私钥

对称加密最大的特点就是通信双方都需要知道加密密钥,这对于互联网上的服务器和客户端来说,在事先没有建立安全信道的情况下安全地传送密钥几乎是不可能的。不过相较于非对称加密来说,对称加密的优点就是快。

非对称加密最大的特点就是通信双方只需要知道密钥对中的一个,而且使用公钥加密的内容只能通过私钥解密,使用私钥加密的内容只能通过公钥解密。对于一个实体来说,公钥可以公开给任何人,只需要保证自身的私钥的安全,就可以保证其与另一个实体的通信内容不会被第三者窃取。

HTTPS 用了哪种加密方法?

在 HTTPS 中,对称加密和非对称加密都用到了。非对称加密可以在不安全的信道上传递秘密内容,但是由于通常使用的非对称加密方法相较于对称加密算法慢很多,因此在 HTTPS 中仅使用非对称加密算法交换对称密钥,交换密钥之后的通信内容均使用对称加密算法加密和解密,这样既可以保证密钥的安全也可以保证内容的加解密速度,这对于移动端设备来说至关重要。

扩展阅读

中间人攻击

中间人攻击(Man In The Middle Attack,简称 MITM),是指攻击者与原本通信的两个实体分别建立通信,并交换攻击者所收到的数据,让原本通信的两个实体误以为自己在一条加密的信道中和对方直接对话,但其实整个对话过程都直接受到攻击者的控制,攻击者可以随意增加、删除、修改通信的内容。一般来说,加密协议都会加入一些特殊的方法来实现通信双方的互相认证,以避免中间人攻击。比如 HTTPS 中就使用证书机制来防止中间人攻击。

之前提到匿名 Diffie-Hellman 方法易受中间人攻击,就是因为其不对秘密内容(即 Diffie-Hellman 公钥参数)进行签名认证导致的。在这个过程中,服务器向客户端发送的公钥参数如果被攻击者截获并替换为自己的公钥参数,那么攻击者就可以伪装成服务器和客户端分别和客户端和服务器进行对话,而且可以完全控制通信的内容。

SNI

当进行 SSL/TLS 握手时,服务器会提供自身的证书给客户端以供客户端验证。但是如果一台服务器上托管了多个 HTTPS 站点,那么服务器提供的证书可能就是错误的。而对于 SSL/TLS 握手连接来说,服务器名称不匹配会导致客户端断开连接,因为服务器名称不匹配表明可能有人正在进行中间人攻击。

一张证书中包含多个主机名是可以的,在之前提到的 curl -v -I -L https://ymfe.org 命令中出现了一个叫做 subjectAltName 的字段,这个字段就是用于指定多个域名的,在 common namesubjectAltName 中还可以使用通配符。

一台服务器托管多个站点是非常常见的。在被托管的站点中,它们可能是同一个域名下的子域名,也可能完全不是同一个域名。如果多个站点都用同一张证书的话,那么服务器需要知道所有被托管的站点的域名,维护一个域名列表,这在很多情况下是不切实际的。而如果为每个 HTTPS 服务器都分配一个独立的 IP 的话,不但会增加成本,还会恶化 IPv4 地址的枯竭情况。

基于名称的虚拟主机可以让多个 DNS 主机名指向同一个服务器。在客户端发起请求的时候,会在 HTTP 头部添加正在请求的主机名称,服务器根据这个主机名称来提供不同的服务。而 HTTPS 中,在建立握手之前是不会发送任何 HTTP 头部的,也就无法获取客户端正在请求的主机名称了。因此,无法使用 HTTP 头部来决定给客户端提供哪张证书。

SNI(Server Name Indication,服务器名称指示)是一个扩展的 TLS 协议。这个协议允许客户端在建立 HTTPS 握手时告知服务器它正在连接的服务器的名称。这就允许服务器根据客户端在握手消息发来的服务器名称来提供正确的 HTTPS 证书,避免因为证书错误导致握手失败。


roland_reed
78 声望4 粉丝