• 文章比较长,赠于有缘人

HTTPS简介

  • HTTPS,是一种网络安全传输协议,在HTTP的基础上利用SSL/TLS来对数据包进行加密,以提供对网络服务器的身份认证,保护交换数据的隐私与完整性。
    • TLS(Transport Layer Security)1.0是SSL(Secure Sockets Layer)3.0的升级版,安全套接字层协议,承担的角色都是一样的,是HTTPS方式握手以及传输数据的一个协议。只是改了名字,其中的八卦,感兴趣的朋友可以自己去搜索。
  • HTTP(S)协议是在TCP/IP协议基础上建造的。
  • TCP/IP协议的分层管理,按层次分为:应用层、传输层、网络层、数据链路层。(我们常说的四层协议、四层模型就是指的这个啦)
  • 没有经过加密层时,数据传输的路径是:应用层->传输层->网络层->数据链路层
  • 经过加密层之后,数据传输的路径是:应用层->SSL/TLS层->传输层->网络层->数据链路层

  • 每层常见的协议:
    • 应用层协议有:FTP、Telnet、SMTP、HTTP、RIP、NFS、DNS。
    • 传输层协议有:TCP协议、UDP协议。
    • 网络层协议有:IP协议、ICMP协议、ARP协议、RARP协议。

HTTPS用途

  • 防窃听:HTTPS协议对传输数据的内容加密,保障数据在传输过程的安全(加密传播)
  • 防冒充:确认网站的真实性(身份证书)
  • 防篡改:防止内容被第三方篡改(校验机制)

HTTPS协议安全性

  • HTTPS协议本身是安全的,并且能够保障数据传输安全、两端身份真实、以及检查数据是否被篡改。
  • 但近几年有https相关的漏洞频发,如心血漏洞、中间人攻击、DROWN溺水攻击、FREAK漏洞、降维攻击、POODLE等(近期会将每个漏洞原理进行分析)。不由让人为https的安全性担忧,其实这个协议的逻辑一般是设计的非常安全了,即使出现问题,也会有大版本的升级(如TLS 1.0升级为TLS 1.1)。而且频发漏洞的并不是https协议本身,而是各个开源或商业服务在具体实现https时,出现了安全问题。

https如何进行数据传输的?

  • 大致流程是:进行握手流程建立https连接(此时是明文传输),然后再进行真正的数据传输(此时使用对称加密进行密文传输)
  • 首先需要了解TLS/SSL协议握手的过程

握手过程

  • 整个过程,如访问www.baidu.com
    • 先进行DNS解析,再建立TCP连接,然后进行https握手,最后传输加密数据。


握手消息 动作描述 消息内容
1. Client —> ClientHello —> Server 客户端(浏览器)发送一个hello消息给服务端,发起建立SSL会话的请求。并告诉服务端,自己支持哪些加密算法(Cipher Suite List)。除此之外,还需要产生一个随机数(第一个随机数,用于以后生成对称密钥),发送给服务端。 1)支持的协议版本,如TLS 1.0版
2)由客户端生成的随机数,用于生成后面的“对称密钥”
3)支持的加密方法,比如RSA公钥加密
4)支持的压缩方法
5)请求的域名
2. Server —> ServerHello —> Client 服务端的首次响应,会确定加密协议版本,以及加密的算法,也会生成一个随机数(第二个随机数)给客户端。 1)协议的版本
2)加密的算法
3)服务端生成的随机数
3. Server —> Certificate —> Client 还会把自己的证书发送给客户端,让客户端进行校验。服务端证书中的公钥也可被用于加密后面握手过程中生成的对称密钥。 1)服务端证书
* 证书颁发机构的名称
* 证书本身的数字签名
* 证书持有者公钥
* 证书签名用到的Hash算法
4. Server --> ServerKeyExchange —> Client 指定使用哪种密钥协商协议。服务端可以在ServerKeyExchange之后立即发送CertificateRequest消息,要求校验客户端的证书。 1)使用哪种密钥协商方式
2)密钥协商时客户端需要的信息
5. Server —> ServerHelloDone —> Client 服务器发送ServerHelloDone消息,告知客户端服务器这边握手相关的消息发送完毕。 &nbps;
6. Client —> ClientKeyExchange —> Server 消息中包含客户端这边的EC Diffie-Hellman算法相关参数,然后服务器和客户端都可根据接收到的对方参数和自身参数运算出对称密钥。 1)密钥协商时服务端需要的信息
7. Client —> ChangeCipherSpec —> Server ChangeCipherSpec消息,通知服务器此消息以后客户端会以加密方式发送数据。 准备好了做加密传输的通知
8. Client —> Finished —> Server 客户端计算生成对称密钥,然后使用该对称密钥加密之前所有收发握手消息的Hash值,发送给服务器,服务器将相同的会话密钥(使用相同方法生成)解密此消息,校验其中的Hash值。 &nbps;
9. Server —> ChangeCipherSpec —> Client ChangeCipherSpec消息,通知客户端此消息以后服务器会以加密方式发送数据。 准备好了做加密传输的通知
10. Server — > Finished —> Client 服务器使用对称密钥加密(生成方式与客户端相同)之前所发送的所有握手消息的hash值,发送给客户端去校验。 &nbps;
11. Application Data 真正的数据传输(使用对称加密) &nbps;

1. Client Hello

  • 客户端发起TLS握手请求
      struct {
          ProtocolVersion client_version;
          Random random;
          SessionID session_id;     
          CipherSuite cipher_suites<2..2^16-2>;
          CompressionMethod compression_methods<1..2^8-1>;
          select (extensions_present) {
              case false:
                  struct {};
              case true:
                  Extension extensions<0..2^16-1>;
          };
      } ClientHello;
  • 数据包括内容:
    • ProtocolVersion/协议版本(客户端期望支持的握手协议版本
    • Random/安全随机数(MasterSecret生成用到,协议文档里面说是28个字节,但是实际抓包看到是32个字节,这里怀疑是各个协议文档版本不同,还有使用加密套件的不同,导致的差异,具体博主就没有在继续深究了,如果有朋友知道可以留言给我)
    • SessionID/会话ID
      • 这个值是被服务端设置的,如果这个值为空,表示客户端与服务端没有存活的https会话,需要与服务端进行完整的握手。
      • 如果这个值存在,则表明客户端期望恢复上一次的https会话,这时候客户端与服务端只需要进行快速的握手过程。(这里我们只会分析完整的握手过程进行学习)
    • CipherSuite/加密套件(客户端支持的加密套件列表)
      • 如果sessionid不为空,可以不传这个值,服务端可以从上一次会话中恢复这个值。
      • 每个加密组件(Cipher Suite)都包括了下面5类算法 TLS Cipher Suite Registry,图中百度使用的是就是 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 这个加密套件:
        • 1、authentication (认证算法):RSA
        • 2、encryption (加密算法 ):AEAD_AES_128_GCM
        • 3、message authentication code (消息认证码算法 简称MAC):SHA256
        • 4、key exchange (密钥交换算法):ECDHE
        • 5、key derivation function (密钥衍生算法)
    • CompressionMethod/压缩方法
      • 加密前进行数据压缩
      • 因为压缩方法被攻击,在TLS1.3协议版本上已经彻底禁止压缩了。(这里有两种攻击方式BREACH、CRIME,有时间博主会来研究)
    • Extension/扩展数据(session ticket在扩展里面,可见下图)
  • 消息内容如下图:

2. Server Hello

  • 服务端回应Client Hello请求
struct {
          ProtocolVersion server_version;
          Random random;
          SessionID session_id;
          CipherSuite cipher_suite;
          CompressionMethod compression_method;
          select (extensions_present) {
              case false:
                  struct {};
              case true:
                  Extension extensions<0..2^16-1>;
          };
      } ServerHello;
  • 主要发送数据内容:
    • ProtocolVersion/握手协议版本
      • 服务端最高支持的握手协议版本,TLS/SSL协议都是向下兼容的。
    • Random/随机数
      • 服务端生成32字节安全随机数(MasterSecret生成会用到)
    • SessionID/会话ID
      • 如果客户端hello有发送session id,服务端从内存中查找,并尝试恢复之前的会话状态。
        • 恢复成功,服务端返回同样的session id。
        • 恢复不成功,服务端此字段返回空。
    • CipherSuite/加密组件
      • 服务端从客户端hello的cipher suite列表中选择一个加密套件,如果是恢复上一次的会话,则从会话状态中恢复上一次相同的加密套件。
    • CompressionMethod/压缩方法
      • 服务端从客户端hello的compression_methods列表中选择一个压缩方法,如果是恢复上一次的会话,则从会话状态中恢复上一次相同的压缩方法。
    • Extension/扩展(如下图)
  • 消息如下面所示:

3. Server Certificate

  • 服务端发送的是一个证书链,可能包含多个证书
    • 第一个证书为网站的证书。
    • 第二个证书为颁发证书给网站的机构的证书。
    • 在这个例子中第三个证书是CA机构的根证书,可以忽略不用发送,因为这个CA的根证书是CA自己给自己颁发的。

这里构成了一个证书信任链,也就是 GlobalSign Root CA信任GlobalSign Organization Validation CA,而他又信任baidu.com的证书。
如下图所示:

  • CA证书的类型有3类:DV ( domain validation),OV ( organization validation),EV ( extended validation),证书申请难度从前往后递增。
  • 证书中都包含了哪些信息?
1. 证书版本号(Version)
2. 证书序列号(Serial Number)
3. 签名算法标识符(Signature Algorithm)
签名算法标识用来指定由CA签发证书时所使用的"签名算法"。算法标识符用来指定CA签发证书时所使用的:
    1) 公开密钥算法
    2) hash算法
example: sha256WithRSAEncryption
须向国际知名标准组织(如ISO)注册
4. 签发机构名(Issuer)
5. 有效期(Validity):指定证书的有效期
6. 证书用户名(Subject)
7. 证书持有者公开密钥信息(Subject Public Key Info)
证书持有者公开密钥信息域包含两个重要信息:
    1) 证书持有者的公开密钥的值
    2) 公开密钥使用的算法标识符。此标识符包含公开密钥算法和hash算法。
8. 扩展项(extension)
9. 签发者唯一标识符(Issuer Unique Identifier)
10. 证书持有者唯一标识符(Subject Unique Identifier)
11. 签名算法(Signature Algorithm)
12. 签名值(Issuer's Signature)
  • 如下图所示


如何校验服务端证书呢?

  • 签名的生成:CA先将证书信息生成hash摘要,然后再用CA机构的私钥进行加密。
  • 签名的校验:使用CA机构的公钥进行解密签名,得到hash摘要A,再计算证书信息的hash摘要B。比对是否一致。

详细解释服务端的证书是怎么生成的?

  • 服务端的证书是由CA (Certificate Authority,证书认证中心)颁发的。他是一个负责发放和管理数字证书的第三方权威机构,它负责管理PKI(Public Key Infrastructure,公开密钥基础设施)结构下的所有用户(包括各种应用程序)的证书,把用户的公钥和用户的其他信息捆绑在一起,在网上验证用户的身份。
  • 一般情况下网站方向CA申请一个证书。CA会给网站方生成一对非对称加密的公钥私钥,公钥会做到证书里面,私钥则会给到网站方。
  • CA会先做一个“数字签名”(生成过程:明文 --> hash运算 --> 摘要 --> 私钥加密 --> 数字签名)
    • 就是将网站方的信息网站方的公钥签名算法等信息(就是Wireshark Packet 20中的数据,除了“签名值”),计算一个hash值(图中hash算法是SHA256),然后CA再用自己私钥做加密(图中公开密钥算法是RSA),最后的这个密文就是“数字签名”(也就是我们在图中看到“encrypted”签名值)。
  • CA最后将“网站方信息”、“网站方公钥”、“签名算法”、“签名值”都做到证书里面(就是Wireshark Packet 20中的我们看到那些数据),证书就做好了,CA会把“证书”和“网站方的私钥”给到网站方。

CA怎么验证证书是不是自己颁发的呢?以及做证书内容校验?

  • 首先浏览器(校验网站的证书)或操作系统(校验应用的证书),会在操作系统存储的系统信任的根证书里面去查找“证书颁发机构”是否是信任的。如下图系统根证书:
    • 浏览器通常也会内置大多数主流权威CA的根证书。
    • 如果查找不到对应的可信CA,则判断这个证书是伪造的,不可信的。(浏览器则会提醒该证书不是可信任机构颁发的,并询问是否要继续访问)
  • 如果找到对应的CA机构,则取出CA机构证书里面的公钥信息,将网站方证书中的签名值(也就是数字签名)做解密,得到网站证书信息的hash摘要A。
  • 然后将网站证书中的信息,做hash得到摘要B,比对摘要A和摘要B是否一致。如果不一致,说明网站证书中的信息被修改了。(浏览器则会提醒该证书不是可信任机构颁发的,并询问是否要继续访问)
  • 如果摘要hash一致,则说明证书中的信息未被修改,这时浏览器会比对您现在正在访问的网站与证书中网站信息是否一致,比如域名是否一致、证书是否在有效期内等。(如果出现问题,浏览器将会提醒你,并询问是否要继续访问)
  • 另外大部分浏览器也会在线校验证书,是否在有效期内(将证书序列号通过在线证书状态协议“OCSP”发送给CA做校验)。
  • 证书校验成功,最后将从证书中取出网站方的公钥,用于后面的握手签名。

4. Server Key Exchange

  • 这个步骤是密钥协商的服务端部分,最终的密钥将会用于传输数据对称加密。
  • 服务端需要发送一个Diffie-Hellman算法的公钥,和指定使用哪种椭圆曲线多项式。
  • 我们到Client Key Exchange的时候,再来讲这个密钥协商过程。
  • 这里还有一个签名,校验这个流程的数据是否被篡改。如下图所示,客户端收到Server Key Exchange数据后,可以用上个流程中获得的证书公钥对签名值解密,获得摘要A。并将这次数据明文做SHA512的hash,获得摘要B,做比对。(这里对协商算法做签名校验,目的可能是防止中间人对协商算法方式做篡改,虽然DH算法不担心公钥在不安全的网络中传输,但是其他算法可能需要考虑被篡改的情况。所以猜测服务端密钥协商时做签名是这个目的,因为服务端这时已经确定是DH算法了,所以客户端协商时就不需要做签名了,DH算法不需要考虑这个安全问题)
  • 发送的数据如下图示:

5. Server Hello Done

  • 服务端发送ServerHelloDone消息表示,已经发送完了密钥协商需要的消息,并且客户端可以开始进行客户端的密钥协商处理了,也就是Client Key Exchange。
  • 收到ServerHelloDone后,客户端需要确认服务器是否提供了合法的证书,并且确认服务器的ServerHello消息里面的参数是否可以接受。

6. Client Key Exchange

  • 客户端生成自己用于密钥协商的公私钥,并发送此公钥
  • 这时客户端已经知道了服务端密钥协商的公钥以及自己的公钥
  • 我们以EC Diffie-Hellman密钥协商协议为例,来看看客户端、服务端是怎么协商出相同的密钥的(这里协商出来的是PreMasterSecret,不是最终的对称加密用到的密钥)。
  • EC Diffie-Hellman使用到一个数学难题,就是在给定的椭圆曲线上的一个点P,一个整数k,求Q=kP很容易;但是给定一个点P、Q,知道Q=kP,求整数k确实很难。
  • 服务端确定了密钥协商算法为“EC Diffie-Hellman”,发送给客户端。现在两端都知道了使用的是哪个曲线参数(椭圆曲线E、阶N、基点G)。
  • Server Key Change:服务端随机生成一个整数a,计算A=a*G,生成服务端公钥A,发送给客户端。
  • Client Key Change:客户端随机生成一个整数b,计算B=b*G,生成服务端公钥B,发送给服务端。
  • 客户端计算出PreMasterSecret:Q=b*A=b*(a*G)
  • 服务端计算出PreMasterSecret:Q'=a*B=a*(b*G),这两个计算结果是相等的,此时双方协商好对称密钥值。
  • 并且即使攻击者截获到双方公钥A、B,仍然无法计算出PreMasterSecret,因为攻击者需要知道随机整数a、b的其中任意一个,可是之前我们就提到过EC Diffie-Hellman协议中,知道A、G求a是很难的。
  • 真正对称加密使用到的密钥生成(这里使用到了client、server一开始hello中传输的随机数):
    • MasterSecret = PRF(PreMasterSecret, "master secret", Client.random || Server.random)[0..47] -- 固定取前 48 字节
    • KeyBlock = PRF(MasterSecret, "key expansion", Server.random || Client.random) -- 长度为由双方确定的密码算法套件决定
    • KeyBlock才是最终用来做对称加密的密钥块 6.3. Key Calculation

7. Client Change Cipher Spec

  • 这个过程就是告诉服务端,他已经准备好MasterSecret了,可以进行数据加密传输了。
  • 这个协议是冗余的,在TLS 1.3里面直接被删除了。

8. Client Finished

  • 这条消息是用来确定双方的MasterSecret是否正确生成,发送的是verify_data消息。
struct {
    opaque verify_data[verify_data_length];
} Finished;

verify_data
   PRF(master_secret, finished_label,Hash(handshake_messages))
      [0..verify_data_length-1];
  • verify_data = PRF(master_secret, finished_label, Hash(handshake_messages))
    • PRF是伪随机函数(pseudorandom function,PRF)
    • master_secret是密钥协商时,计算出来的
    • finished_label:对客户端发的Finished消息来说,固定是字符串 "client finished". 对服务器发的Finished消息来说,固定是字符串 "server finished".
    • handshake_messages,是各端握手过程中发送的所有消息的,类型如下:
struct {
          HandshakeType msg_type;    /* handshake type */
          uint24 length;             /* bytes in message */
          select (HandshakeType) {
              case hello_request:       HelloRequest;  //HelloRequest是服务端在任何时候都可以发出的,告诉客户端需要重新进行握手协议,客户端随即发送新的ClientHello    
              case client_hello:        ClientHello;
              case server_hello:        ServerHello;
              case certificate:         Certificate;//服务端或客户端发送自己证书给客户端。
              case server_key_exchange: ServerKeyExchange;
              case certificate_request: CertificateRequest;//服务端请求,客户端发送自己的客户端证书,给服务端做校验。这个步骤在博文中没有提到,看以后有需要再了解。
              case server_hello_done:   ServerHelloDone;
              case certificate_verify:  CertificateVerify;//客户端发出,从client hello开始,一直到CertificateVerify之前的所有消息的hash加上客户端证书对应私钥的加密结果。
              case client_key_exchange: ClientKeyExchange;
              case finished:            Finished;
          } body;
      } Handshake;
  • 但不包括ChangeCipherSpec、alerts之类的消息。并且最后一个发送Finished的一方,需要把前一个发送Finished的内容包括进去。
  • 注意这里每个端发送自己的握手消息就可以,比如Client发送内容包括ClientHello、Certificate(有发送的话)、CertificateVerify(如果有发送的话)、ClientKeyExchange、Finished(如果是最后一方需要包含)。服务端同理。

  • 因为verify_data是加密的,我就没有在截图了,上述的字段以及说明可以查看协议文档 7.4.9. Finished

8.1. Server New Session Ticket

  • 如果服务端想使用Ticket方式存储session状态,在Server Change Cipher Spec之前就需要发送New Session Ticket消息。
  • New Session Ticket方式与Session ID方式对比:
    • SessionID方式,客户端在ClientHello的时候带着上一次SessionID过来,服务端从自己内存中查找SessionID对应的session状态,并读取session状态快速恢复。
    • SessionTicket方式,则是将session状态加密后,发送给客户端存储。客户端在ClientHello时将SessionTicket带上,服务端就将其解密,读取出里面存储的session状态信息,SessionTicket存储的信息如下:
      struct {
          ProtocolVersion protocol_version; //协议版本
          CipherSuite cipher_suite; //加密套件类型
          CompressionMethod compression_method;    //压缩方法
          opaque master_secret[48]; //对称密钥
          ClientIdentity client_identity; //客户端ID
          uint32 timestamp;//ticket有效期
      } StatePlaintext;

9. Server Change Cipher Spec

  • 告诉客户端,我已经准备好进行加密传输了。

10. Server Finished

  • 与8. Client Finished的情况一样,使用对称密钥加密,最后做一次验证,确定双方是否都准备好进行数据传输了。只是这里加密的数据还不是真正的网站内容数据,而是握手过程的数据。

11. Application Data

  • 真正的网站数据传输,但是这里的数据就是经过握手时协商好的对称密钥进行加密的了。
  • 现在我们有KeyBlock(对称密钥块),也知道对称加密算法是AES-128-GCM 5.1. AEAD_AES_128_GCM

后续

参考文献