https、双向https简述

506 阅读15分钟

1. SSL和TLS的区别

SSL(Secure Sockets Layer)和 TLS(Transport Layer Security)都是用于在网络上加密通信的协议。它们的目标是确保数据在客户端和服务器之间安全地传输,防止窃听和篡改。尽管它们的功能相似,但两者之间有一些关键区别:

1. 历史发展

  • SSL:最早由 Netscape 在 1990 年代开发。SSL 共有 3 个主要版本:SSL 1.0(未公开发布)、SSL 2.0 和 SSL 3.0。由于存在安全漏洞,SSL 3.0 最终被废弃。
  • TLS:是 SSL 的继任者,最早于 1999 年由 IETF(互联网工程任务组)作为一个更安全的替代方案发布。TLS 的设计基于 SSL 3.0,但进行了重要的改进。最新的版本是 TLS 1.3(发布于 2018 年)。

2. 安全性

  • SSL:SSL 2.0 和 SSL 3.0 存在多个已知的安全漏洞,如 POODLE 攻击,使得它们被认为不再安全,并且现代浏览器和系统基本上都已停止支持 SSL。
  • TLS:相比 SSL,TLS 在加密算法和协议设计上做了诸多改进。TLS 1.2 和 TLS 1.3 提供了更强的加密方法和更高的安全性,并解决了 SSL 以及早期 TLS 版本的漏洞。

3. 协议差异

  • 加密算法:TLS 相比 SSL 提供了更多加密算法的支持,并且引入了更安全的密码套件(cipher suites)。
  • 握手过程:TLS 的握手过程更为安全,并提供了消息认证代码(MAC)来防止消息在传输过程中被篡改。
  • 记录层协议:TLS 对于记录层协议进行了改进,使得加密和消息认证的顺序更加安全。

4. 协议的应用

  • SSL:由于安全性问题,现代的系统、应用和浏览器基本上都已停止支持 SSL。
  • TLS:如今,所有支持 HTTPS 的网站和安全通信基本都基于 TLS,尤其是 TLS 1.2 和 TLS 1.3。

5. 兼容性

  • 向后兼容:TLS 设计时保留了一些与 SSL 的兼容性,但在 TLS 1.3 中,去掉了许多过时且不安全的功能。

总结

  • SSL 是早期的安全协议,现已被废弃,存在诸多安全漏洞。
  • TLS 是 SSL 的改进版本,当前广泛使用,具有更高的安全性和性能。

因此,现今提到的 SSL/TLS 实际上指的都是 TLS,但有时人们依然习惯将其统称为“SSL”。

2. Https

  1. 客户端发起建立HTTPS连接请求,将SSL协议版本的信息发送给服务器端;
  2. 服务器端将本机的公钥证书(server.crt)发送给客户端;
  3. 客户端读取公钥证书(server.crt),取出了服务端公钥;
  4. 客户端生成一个随机数(密钥R),用刚才得到的服务器公钥去加密这个随机数形成密文,发送给服务端;
  5. 服务端用自己的私钥(server.key)去解密这个密文,得到了密钥R
  6. 服务端和客户端在后续通讯过程中就使用这个密钥R进行通信了。

3. 双向https

  1. 客户端发起建立HTTPS连接请求,将SSL协议版本的信息发送给服务端;
  2. 服务器端将本机的公钥证书(server.crt)发送给客户端;
  3. 客户端读取公钥证书(server.crt),取出了服务端公钥;
  4. 客户端将客户端公钥证书(client.crt)发送给服务器端;
  5. 服务器端使用根证书(root.crt)解密客户端公钥证书,拿到客户端公钥;
  6. 客户端发送自己支持的加密方案给服务器端;
  7. 服务器端根据自己和客户端的能力,选择一个双方都能接受的加密方案,使用客户端的公钥加密8. 后发送给客户端;
  8. 客户端使用自己的私钥解密加密方案,生成一个随机数R,使用服务器公钥加密后传给服务器端;
  9. 服务端用自己的私钥去解密这个密文,得到了密钥R
  10. 服务端和客户端在后续通讯过程中就使用这个密钥R进行通信了。

a. 双向https证书逻辑

从上一章内容中,我们可以总结出来,如果要把整个双向认证的流程跑通,最终需要六个证书文件:

  • 服务器端公钥证书:server.crt
  • 服务器端私钥文件:server.key
  • 根证书:root.crt
  • 客户端公钥证书:client.crt
  • 客户端私钥文件:client.key
  • 客户端集成证书(包括公钥和私钥,用于浏览器访问场景):client.p12

生成这一些列证书之前,我们需要先生成一个CA根证书,然后由这个CA根证书颁发服务器公钥证书和客户端公钥证书。为了验证根证书颁发与验证客户端证书这个逻辑,我们使用根证书办法两套不同的客户端证书,然后同时用两个客户端证书来发送请求,看服务器端是否都能识别。下面是证书生成的内在逻辑示意图:

我们可以全程使用openssl来生成一些列的自签名证书,自签名证书没有听过证书机构的认证,很多浏览器会认为不安全,但我们用来实验是足够的。需要在本机安装了openssl后才能继续本章的实验。

b. 环境搭建

HTTPS双向认证原理与测试环境搭建

nginx配置

server {
        listen       442 ssl;
        server_name  localhost;
        ssl_certificate      /Users/chy/Desktop/temp/ssl/server.crt;  #server公钥证书
        ssl_certificate_key  /Users/chy/Desktop/temp/ssl/server.key;  #server私钥
        ssl_client_certificate /Users/chy/Desktop/temp/ssl/ca.crt;  #根证书,可以验证所有它颁发的客户端证书
        ssl_verify_client on;  #开启客户端证书验证  
        ssl_protocols SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; #按照这个套件配置

        location / {
            proxy_pass http://localhost:8080;
        }
    }

c. 使用curl作为客户端调用验证

使用curl加上证书路径,可以直接测试Nginx的HTTPS双向认证是否配置成功。下面我们测试两个个用例:

  • 使用client.crt/client.key这一套客户端证书来调用服务器端
  • 不使用证书来调用服务器端

下面是两个用例的测试结果:

  • 带证书的成功调用:
#--cert指定客户端公钥证书的路径
#--key指定客户端私钥文件的路径
#-k不校验证书的合法性,因为我们用的是自签名证书,所以需要加这个参数
#可以使用-v来观察具体的SSL握手过程

curl --cert ./client.crt --key ./client.key https://localhost:442/hello -v -k

*   Trying 127.0.0.1:442...
* Connected to localhost (127.0.0.1) port 442 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS handshake, CERT verify (15):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=cn; ST=hz; L=hz; O=have; OU=test; CN=server; emailAddress=test@test.com
*  start date: Sep 10 05:55:12 2024 GMT
*  expire date: Sep 10 05:55:12 2025 GMT
*  issuer: C=cn; ST=hz; L=hz; O=have; OU=test; CN=have; emailAddress=test@test.com
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
> GET /hello HTTP/1.1
> Host: localhost:442
> User-Agent: curl/7.77.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 
< Server: nginx/1.26.2
< Date: Tue, 10 Sep 2024 07:19:41 GMT
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 17
< Connection: keep-alive
< 
* Connection #0 to host localhost left intact
hello world, 8080
  • 不带证书的调用
curl  https://localhost:442/hello -v -k
*   Trying 127.0.0.1:442...
* Connected to localhost (127.0.0.1) port 442 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=cn; ST=hz; L=hz; O=have; OU=test; CN=server; emailAddress=test@test.com
*  start date: Sep 10 05:55:12 2024 GMT
*  expire date: Sep 10 05:55:12 2025 GMT
*  issuer: C=cn; ST=hz; L=hz; O=have; OU=test; CN=have; emailAddress=test@test.com
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
> GET /hello HTTP/1.1
> Host: localhost:442
> User-Agent: curl/7.77.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 400 Bad Request
< Server: nginx/1.26.2
< Date: Tue, 10 Sep 2024 07:21:17 GMT
< Content-Type: text/html
< Content-Length: 237
< Connection: close
< 
<html>
<head><title>400 No required SSL certificate was sent</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<center>No required SSL certificate was sent</center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
* Closing connection 0
* TLSv1.2 (OUT), TLS alert, close notify (256):

两个个用例都符合预期,从第一个测试日志中,我们可以看到,整个通信过程较长,客户端验证服务器端的证书,客户端也将自己的证书上传到服务器端进行验证。使用根证书颁发的两个客户端证书都可以正常发起双向HTTPS认证的调用。没有带客户端证书的调用会被服务器端拒绝服务。

d. 总结

HTTPS双向认证方式通信在一些安全级别较高的场景非常有用,拥有合法证书的客户端才能正常访问业务。实现这个场景需要以下几步:

  • 生成根公钥证书和私钥文件(root.crt/root.key);
  • 使用根证书和根证书私钥(root.crt/root.key)配合服务器端私钥颁发服务器端证书(server.crt);
  • 使用根证书和根证书私钥(root.crt/root.key)配合客户端私钥颁发客户端证书(server.crt);
  • 将根证书(root.crt)、服务器端证书(server.crt)、服务器端秘钥(server.key)配置到Nginx的Server配置中;
  • 客户端使用客户端私钥和根证书颁发的客户端证书(client.crt)正常访问业务。

根证书可以任意颁发客户端证书并给业务方使用,为了安全起见,需要注意颁发的证书的有效期。

魔鬼藏在细节,有两个细节再次重点强调下:

  • 根证书的Common Name填写root就可以,所有客户端和服务器端的证书这个字段需要填写域名,一定要注意的是,根证书的这个字段和客户端证书、服务器端证书不能一样;
  • Nginx的ssl_client_certificate需要配置根证书root.crt,而不是客户端证书;

4. 双向Https对比Https

双向 HTTPS(双向 SSL/TLS)相比于普通的 HTTPS 有以下几个优势:

a. 增强的身份验证:

  • 单向 HTTPS:在普通的 HTTPS 中,只有服务器会提供一个 SSL 证书,客户端通过验证服务器证书来确认连接的安全性,但客户端的身份不需要验证。
  • 双向 HTTPS:在双向 HTTPS 中,除了服务器提供 SSL 证书外,客户端也需要提供一个客户端证书,服务器会验证客户端的证书。这样不仅服务器身份得到了验证,客户端的身份也得到了验证。

b. 更高的安全性:

  • 双向 HTTPS 的双向认证机制可以防止未经授权的客户端连接服务器,这对于需要严格控制访问权限的应用场景(例如金融服务、企业内部系统)尤其重要。即使攻击者能够伪造服务器,服务器也能拒绝没有正确客户端证书的请求。

c. 防止中间人攻击:

  • 在普通的 HTTPS 中,中间人攻击依然可能通过欺骗客户端和服务器之间的认证来伪造服务器身份。
  • 在双向 HTTPS 中,双向认证可以有效防止中间人攻击,因为攻击者必须同时伪造客户端和服务器的证书,这大大增加了攻击的难度。

d. 访问控制:

  • 双向 HTTPS 允许服务器基于客户端证书的身份信息(例如证书中的组织名称、用户 ID 等)来进行更精细的访问控制。这为身份验证提供了更安全的机制,无需依赖用户名/密码的方式。

e. 适用场景:

  • 需要严格身份验证的场景,如企业内部应用、银行系统、政府系统等。
  • 敏感信息传输,特别是当客户端需要确保数据只能被授权的服务器接收时。

总的来说,双向 HTTPS 提供了更强的安全性和身份验证能力,但实现和维护相对复杂,适用于对安全性要求极高的场景。

5. Https中间人攻击

在普通的 HTTPS 连接中,中间人攻击(Man-in-the-Middle,MITM)通常试图在客户端和服务器之间拦截或修改通信。在单向 HTTPS 中,虽然服务器会向客户端提供一个 SSL 证书,客户端会验证该证书的合法性,但攻击者依然可能通过以下方式来欺骗客户端和服务器的身份验证,从而伪造服务器身份。

a. MITM 攻击的实现步骤:

  1. 截获通信
    • 攻击者首先需要在客户端和服务器之间拦截通信,通常通过网络钓鱼、DNS 欺骗、ARP 欺骗等技术,让客户端误以为攻击者是服务器,或者让服务器误以为攻击者是客户端。
  1. 伪造的证书
    • 攻击者向客户端发送一个伪造的服务器证书,使得客户端认为自己正在与真实的服务器通信。为了实现这一点,攻击者通常会采取以下方式:
      • 自签名证书:攻击者可以生成自己的证书,并将其发送给客户端。由于客户端的浏览器会检查证书是否由可信的证书颁发机构(CA)签发,因此这种方式会触发安全警告。用户需要手动忽略警告,继续访问,这就有可能使不注意安全警告的用户上当。
      • 伪造受信的证书:更复杂的攻击者可能会试图获得受信 CA 颁发的伪造证书,或者利用 CA 的漏洞获取合法证书。这种情况下,客户端将信任伪造的证书,认为通信是安全的。
  1. SSL/TLS 解密和再加密
    • 攻击者充当客户端和服务器之间的“中介”角色:攻击者会解密客户端发送的数据,然后将数据重新加密并发送给服务器,反之亦然。这样,攻击者能够完全拦截和修改通信内容,而客户端和服务器并未意识到通信被劫持。
    • 攻击者会使用客户端的公钥来解密客户端发送给服务器的数据,然后使用服务器的公钥重新加密发送给服务器。同样,攻击者可以解密服务器返回的数据,篡改后再加密给客户端。
  1. 最终目的
    • 在攻击过程中,攻击者可以读取、修改或删除数据,甚至在用户不知情的情况下获取敏感信息,如登录凭证、银行信息等。

b. 攻击成功的关键:

  • 不受信任的 CA:如果攻击者能够获得不受信任的证书颁发机构颁发的证书(例如通过社会工程学攻击 CA 或利用 CA 的漏洞),他可以伪造几乎任何网站的身份,客户会认为该网站是安全的。
  • 用户忽略安全警告:在很多 MITM 攻击中,攻击者利用自签名证书或过期证书,触发浏览器的安全警告。如果用户忽视这些警告并继续访问,那么攻击者可以轻易进行攻击。

c. 如何防止 MITM 攻击:

  1. 严格的证书验证
    • 客户端应该始终检查服务器证书的真实性,确保其由受信任的 CA 签发,并验证证书的域名是否与服务器匹配。
  1. 证书固定(Certificate Pinning)
    • 应用程序可以使用证书固定技术,将特定的服务器证书或 CA 证书绑定到应用程序中,这样即使攻击者使用了伪造证书,应用程序也会检测到异常。
  1. 双向 HTTPS
    • 通过双向 HTTPS,不仅服务器需要提供证书,客户端也需要提供证书,从而进一步增加了 MITM 攻击的难度,因为攻击者不仅要伪造服务器的证书,还要伪造客户端的证书。
  1. HSTS(HTTP Strict Transport Security)
    • HSTS 是一种 HTTP 头部机制,强制客户端与服务器只能通过 HTTPS 进行通信,并且避免客户端接受没有有效 SSL 证书的网站。即使攻击者试图通过降级攻击使用 HTTP,HSTS 也会阻止这种情况。

d. 结论:

在普通的 HTTPS 中,中间人可以通过截获通信、伪造证书等方式欺骗客户端和服务器,进行中间人攻击。尽管 HTTPS 提供了加密和身份验证,但中间人攻击依然可能利用证书系统的漏洞或用户的忽视。