详解SSL/TLS
本文最后更新于:2024年9月27日 下午
详细分析SSL/TLS建立安全连接的流程,以及部分加密算法的数学原理,以及SSL是如何工作在CA以及证书上的。
项目内容
采用Python语言,基于OpenSSL库实现证书颁发机构(CA)程序,并基于证书签发机制和公钥密码体制(RSA算法)实现了SSL握手流程,完成客户端与服务端之间共享密钥的交换,并且在消息传输的过程中采用对称加密算法(AES)对明文消息进行加密。
CA以及CA的工作原理
CA是证书颁发机构的简称,证书颁发机构是一个可信任的实体,负责颁发、管理和撤销数字证书。数字证书用于在公钥基础设施 (PKI) 中验证身份,并确保在网络通信中数据的安全性和完整性。
CA的作用是作为公证人,在一次SSL握手过程中,假设我们没有CA颁发的证书作为验证,那么考虑此时客户端被中间人劫持,在双方交换密钥的过程中,中间人可以自己的公钥发送给客户端,此时客户端没有别的信息来鉴别这个公钥,因此中间人会维持两个对话,一个是中间人-客户端,一个是中间人-服务端。所以此时需要一个中间人来证明这个公钥是可信安全的,CA就出现了,服务端会将自己的公钥(在证书请求文件CSR里)发给CA,CA使用签名算法和CA自己的私钥给这个公钥签名,也就是生成证书,那么客户端就可以使用CA的公钥来对服务端的证书进行验证
工作过程:
- 根证书的基础是一个公钥和私钥对,这对密钥是 CA 用于签署其他证书的核心。这个过程包括:
- 选择加密算法:常用的算法包括
RSA
和ECC
。RSA
通常使用 2048 位或更高的密钥长度,而ECC
使用更短但安全性相当的密钥长度(如 256 位)。我们选择的是RSA加密算法。 - 生成密钥对:使用安全的硬件安全模块 (HSM) 或可信的软件工具生成公钥和私钥对。私钥必须严格保密,通常存储在 HSM 中,以防泄露。
- 选择加密算法:常用的算法包括
- CA自身生成证书请求文件,然后对其进行自签名,这个证书称为根证书,作为认证链的基础
- 客户端生成证书请求文件.csr,发送给CA进行签名
- 签名后返回客户端证书.crt
公钥基础设施PKI和信任链
简单来说,PKI就是一套用于管理数字证书的体系结构。
关于信任链的话,我们知道在建立SSL链接的时候需要使用证书进行身份验证,那么证书颁发机构的安全性又如何保证呢,其实很简单,让更高一级的CA对低一级的CA证书进行签名,这样一层一层的向下保证,只需要保证根CA的安全性即可。
RSA加密算法
RSA(Rivest-Shamir-Adleman)
算法是一种非对称加密算法,广泛应用于数字证书的颁发和验证过程中。RSA
算法在证书颁发中的主要作用包括生成密钥对、加密和解密数据、以及数字签名和验证。RSA是公钥密码体制中的一个重要算法。
公钥:公开发布并包含在数字证书中,用于加密数据和验证数字签名。通过公钥我们就可以判断这个证书是否是受信任的CA颁发的。
私钥:保密并由持有者妥善保管。在证书颁发过程中,CA 使用其私钥对证书进行数字签名。
数学推导:
首先选取两个大素数
。这很重要,RSA的基础就是大素数分解困难问题 计算它们的乘积:
计算欧拉函数:
选择一个与
互质的整数 (通常选择较小的素数,如65537) 计算
使得 是 的模 逆元,即满足:
这个方程可以通过扩展欧几里得算法求解。公钥是
,私钥是 。 私钥签名:给定消息
(其中 是一个整数,且 ),签名过程为: 公钥解密:给定密文
,解密过程为:
值得注意的是,RSA算法并不具有前向安全,因为它的公私钥是静态的,当黑客长期收集同一个私钥加密的数据时,一旦私钥泄露,那么之前被收集的加密数据均会被破解。
DH算法
DH 算法又称“Diffie–Hellman 算法”,像往常的算法名字一样,这是用俩个数学牛人的名字来命名的算法,实现安全的密钥交换,通讯双方在完全没有对方任何预先信息的条件下通过不安全信道创建起一个密钥。
下面讲一讲算法流程:
- Alice与Bob协定使用一个大素数
和一个 的整数模 乘法群及其原根 , 都公开 - Alice选择一个随机数
(私钥)并计算 ,并将 发送给Bob - Bob选择一个随机数
(私钥)并计算 ,并将 发送给Alice - Alice计算
- Bob计算
- Alice和Bob就同时协商出群元素
, 和 因为群是乘法交换的。
SSL协议的流程
SSL(Secure Sockets Layer)协议是为确保在计算机网络中进行安全通信而设计的加密协议。SSL协议在传输层之上,应用层之下
- 第一步:client向server发出hello,并生成一个随机数random_A,将使用的SSL协议版本、加密算法、压缩方法以及random_A发送给服务端。
- 第二步:
- server向client回应hello,并生成一个随机数random_B,和客户端一样,在报文中包含SSL版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。并将random_B发送给client
- server向client发送自己的证书用于身份验证
- server向client发送hellodone表示密钥协商阶段结束
- 第三步:
- client验证服务器证书的有效性。通常,客户端会验证证书是否由受信任的CA签发,证书是否过期,证书链是否完整等。
- client如果发送了证书,也会通过签名证明其证书的有效性。
- client生成一个随机数random_C,client会使用证书中取出来的公钥对random_C进行加密,生成预主密钥,客户端以Client Key Exchange报文作为回应,其中包含预主密钥。
- client继续发送Change Cipher Spec报文。用于告知server,client已经切换到之前协商好的加密套件(Cipher Suite)的状态,准备使用之前协商好的加密套件加密数据并传输了。
- client发送Finished报文。该报文包含连接至今全部报文的整体校验值(也就是HASH值),用来供server校验。
- 第四步:
- server接收到客户端的请求之后,使用私钥解密报文,把Pre-master secret取出来。接着,同样发送Change Cipher Spec报文。
- server同样发送Finished报文,用来供client校验。
- 此时双方建立起SSL连接,开始使用HTTP通讯
TLS协议
网景公司针对传输层协议(TCP/UDP)并没有对传输的数据进行加密保护的缺陷,为自家浏览器设计了SSL协议,于1995年公开协议的2.0版本。1999年,IETF小组基于SSL 3.0设计了与SSL协议独立的TLS 1.0协议,正式成为互联网传输层加密的标准。
SSL/TLS提供了两种功能:
- 建立一个安全的连接:对其中传输的数据提供加密保护,防止被中间人嗅探到可见明文;对数据提供完整性校验,防止传输的数据被中间人修改。
- 建立一个可信的连接:对连接双方的实体提供身份认证。
TLS1.2
其实就是修复了一些安全漏洞,然后将SSL(security socket layer)换了个名字TLS(transport layer security),具体流程和SSL类似。
这里讲一下基于DH算法的握手流程的改动
- 在证书中,不再是服务端的RSA公钥,而是DH中的p,g以及服务端的公钥
。 - 客户端在进行密钥协商时不再返回加密后的预主密钥,而是返回
。
TLS1.3
TLS1.3总共有两层,分别是握手协议(handshake protocol)和记录协议(record protocol),握手协议在记录协议的上层,记录协议是一个分层协议。其中握手协议中还包括了警告协议(alert protocol)。
往返时延上的改进
我们先来简单回忆一下 Client 的密钥在 1.3 前的 DH(E) 算法下是怎么进行协商的:
- Client -> Server:请求建立连接
- Server -> Client:生成 𝑏 并计算出 𝐵 发向对方
- Client -> Server:生成 𝑎 并计算出 𝐴 发向对方
- 双方根据 𝑎⋅𝐵 或 𝑏⋅𝐴 计算出密钥 𝐾
在客户端和服务器之间进行了两次往返(2-RTT)以完成握手。 平均而言,这需要0.25秒到0.5秒之间的时间。
在这里我们可以看出 Client 向 Server 发送密钥是在收到 Server 的响应后的,这是因为密钥计算需要 𝑝 和 𝑔 ,而他们又在 Server 的证书里或被证书所签名,所以需要等到收到 Server 证书后才能计算。也就是说,只要我们能够提前得知 𝑝、𝑔 ,就能在第一个握手时就发送 𝐴。
TLS1.3 在握手上做了优化,只需要一次时延往返就可以建立连接(1-RTT)
那么它是怎么做的呢?
其实具体的做法还是利用了扩展。客户端在 Client Hello
消息里直接用 supported_groups 带上支持的曲线,比如 P-256、x25519,用 key_share 带上曲线对应的客户端公钥参数,用 signature_algorithms 带上签名算法。
服务器收到后在这些扩展里选定一个曲线和参数,再用 key_share
扩展返回服务器这边的公钥参数,就实现了双方的密钥交换。
握手流程
Client 生成 𝑘𝑒𝑦𝑠ℎ𝑎𝑟𝑒向 Server 发送𝑘𝑒𝑦𝑠ℎ𝑎𝑟𝑒 的结构为一个列表,其中具有多个 <密钥组名,通过
和对应组使用的𝑝,𝑔生成的 > 的数据项。 - 为了安全起见,每一个数据项中的 𝑎 (Client 私钥) 都是不一样的
- 以上包含的多个组中并不只有 DHE 类型的组,还有 ECDHE 类型,其值中的加密组件所需参数不同
Server 从多个密钥组中选择自己能够接受的密钥组;如果没有,则会直接响应自己能够接受的密钥组的组名,让 Client 重新生成对应的 𝐴 并发送(此时 1⋅𝑅𝑇𝑇 会退化为 2⋅𝑅𝑇𝑇 )。
在选定密钥组后,Server发送决定使用的密钥组的名称,同时使用该组的 𝑝,𝑔 和自己的𝑏计算出𝐵,紧接着根据密钥交换原理通过收到的𝐴计算出𝐾。
这里有个被忽略的过程,这篇文章讲了,非常感谢。在将”服务器端随机数”和 B𝐵 发送出去后,接下来,将通过 K𝐾 导出以下三个密钥:
- 𝑚𝑎𝑠𝑡𝑒𝑟 𝑠𝑒𝑐𝑟𝑒𝑡 :主密钥
- 客户端握手密钥:用来加密 Client -> Server 的握手信息
- 服务器端握手密钥:用来加密 Server -> Client 的握手信息
导出密钥后,接下的所有要发送的信息都将被对应的密钥进行加密。对于每个被导出的密钥,所参与的参数与过程都存在不同,且该过程中会使用到 [客户端随机数] 和 [服务器端随机数],这两参数的交换过程也被省略。
导出的操作可理解为使用单向散列函数,以上导出的每个密钥中,参与导出的参数并不完全相同,所以导出的密钥的值也并不同的。
Client 收到来自 Server 的服务器端随机数和公钥 𝐵 后,也进行以上的计算过程,并根据得出的”服务器端握手密钥”解密接下来收到的握手信息。
在解密并验证完被加密的证书后,就能确保服务器的身份。同时仍会验证发过来的握手阶段的报文的摘要,确认握手过程未被篡改。
在保证通信安全后,接下来会和 TLS 1.3 前一样发送握手阶段报文的摘要(当然,这会被”客户端握手密钥”加密)。
在两边做完自己的工作后,双方将从𝑚𝑎𝑠𝑡𝑒𝑟 𝑠𝑒𝑐𝑟𝑒𝑡中导出以下四个密钥:
客户端通信密钥:用来加密 Client -> Server 的通信信息
(上文的两个用于握手报文加密的密钥将会被丢弃)
服务器端通信密钥:用来加密 Server -> Client 的通信信息
恢复密钥:用来参与 PSK 的计算
导出密钥:用来参与默认的密钥导出计算
为什么TLS1.3会使用这么多的密钥
在 TLS 1.3 中,其密钥的导出函数叫做 HKDF,为一个基于 HMAC 的密钥导出函数(KDF),它执行的主要方法是 “extract-then-expand”,也就是说,先从输入密钥与参数中提取一个固定长度的密钥,然后拓展为多个额外的密钥。重点在于,额外导出的密钥在密码学上是安全的,并且即使其中一个密钥被泄露,也不会导致其他由相同的密钥材料导出的密钥存在风险。
简单来讲就是,”不要把鸡蛋放在同一个篮子里”。
MITM
MITM(Man-in-the-MiddleAttack) ,是指攻击者与通讯的两端分别创建独立的联系,并交换其所有收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个对话都被攻击者完全控制,在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。一个中间人攻击能成功的前提条件是攻击者能够将自己伪装成每个参与会话的终端,并且不被其他终端识破。