1990年互联网诞生之初,就已经开始用超文本传输协议 HTTP 传输数据,这也是为什么现在网页地址都是以 http 开头的原因。但是HTTP协议传输数据是明文传输,任意的人抓包就能看到传输的数据,这显然不安全。1994年,Netscape 公司用加密协议增加了 HTTP,开始在 HTTP 的基础上加入 SSL 即安全套接层(Secure Socket Layer)。称为 "HTTP over SSL" 或者 "HTTP Secure",也就是我们现在熟知的 HTTPS。
HTTPS 其实是一个非常简单的协议,RFC 文档很小,只有短短的 7 页,里面规定了新的协议名https,默认端口号 443,至于其他的什么请求 - 应答模式、报文结构、请求方法、URI、头字段、连接管理等等都完全沿用 HTTP,没有任何新的东西。
也就是说,除了协议名http和端口号 80 这两点不同,HTTPS 协议在语法、语义上和 HTTP 完全一样,优缺点也照单全收(当然要除去明文和不安全)。
SSL/TLS
SSL/TLS是位于TCP/IP 7层协议中的会话层,用于认证用户和服务器,加解密数据以及维护数据的完整性,确保数据在传输过程中不会被修改。
SSL 有 v2 和 v3 两个版本,而 v1 因为有严重的缺陷从未公开过。SSL 发展到 v3 时已经证明了它自身是一个非常好的安全通信协议,于是互联网工程组 IETF 在 1999 年把它改名为 TLS(传输层安全,Transport Layer Security),正式标准化,版本号从 1.0 重新算起,所以 TLS1.0 实际上就是 SSLv3.1。
到今天 TLS 已经发展出了三个版本,分别是 2006 年的 1.1、2008 年的 1.2 和去年(2018)的 1.3,每个新版本都紧跟密码学的发展和互联网的现状,持续强化安全和性能,已经成为了信息安全领域中的权威标准。
目前应用的最广泛的 TLS 是 1.2,而之前的协议(TLS1.1/1.0、SSLv3/v2)都已经被认为是不安全的,各大浏览器即将在 2020 年左右停止支持,所以接下来的讲解都针对的是 TLS1.2。
TLS 由记录协议、握手协议、警告协议、变更密码规范协议、扩展协议等几个子协议组成,综合使用了对称加密、非对称加密、身份认证等许多密码学前沿技术。浏览器和服务器在使用 TLS 建立连接时需要选择一组恰当的加密算法来实现安全通信,这些算法的组合被称为密码套件(cipher suite,也叫加密套件)。
SSL/TLS分为对称加密和非对称加密两种方式。
对称加密
对称加密是指加密和解密都用同一份密钥。如下图所示:
AES 的意思是高级加密标准(Advanced Encryption Standard),密钥长度可以是 128、192 或 256。它是 DES 算法的替代者,安全强度很高,性能也很好,而且有的硬件还会做特殊优化,所以非常流行,是应用最广泛的对称加密算法。
ChaCha20 是 Google 设计的另一种加密算法,密钥长度固定为 256 位,纯软件运行性能要超过 AES,曾经在移动客户端上比较流行,但 ARMv8 之后也加入了 AES 硬件优化,所以现在不再具有明显的优势,但仍然算得上是一个不错算法。
非对称加密
非对称加密对应于一对密钥,称为私钥和公钥,用私钥加密后需要用公钥解密,用公钥加密后需要用私钥解密。如下图所示:
对称加密看上去好像完美地实现了机密性,但其中有一个很大的问题:如何把密钥安全地传递给对方,术语叫密钥交换。
因为在对称加密算法中只要持有密钥就可以解密。如果你和网站约定的密钥在传递途中被黑客窃取,那他就可以在之后随意解密收发的数据,通信过程也就没有机密性可言了。
这个问题该怎么解决呢?
你或许会说:把密钥再加密一下发过去就好了,但传输加密密钥的密钥又成了新问题。这就像是鸡生蛋、蛋生鸡,可以无限递归下去。只用对称加密算法,是绝对无法解决密钥交换的问题的。
所以,就出现了非对称加密(也叫公钥加密算法)。
它有两个密钥,一个叫公钥(public key),一个叫私钥(private key)。两个密钥是不同的,不对称,公钥可以公开给任何人使用,而私钥必须严格保密。
公钥和私钥有个特别的单向性,虽然都可以用来加密解密,但公钥加密后只能用私钥解密,反过来,私钥加密后也只能用公钥解密。
非对称加密可以解决密钥交换的问题。网站秘密保管私钥,在网上任意分发公钥,你想要登录网站只要用公钥加密就行了,密文只能由私钥持有者才能解密。而黑客因为没有私钥,所以就无法破解密文。
非对称加密算法的设计要比对称算法难得多,在 TLS 里只有很少的几种,比如 DH、DSA、RSA、ECC 等。
RSA 可能是其中最著名的一个,几乎可以说是非对称加密的代名词,它的安全性基于整数分解的数学难题,使用两个超大素数的乘积作为生成密钥的材料,想要从公钥推算出私钥是非常困难的。10 年前 RSA 密钥的推荐长度是 1024,但随着计算机运算能力的提高,现在 1024 已经不安全,普遍认为至少要 2048 位。ECC(Elliptic Curve Cryptography)是非对称加密里的后起之秀,它基于椭圆曲线离散对数的数学难题,使用特定的曲线方程和基点生成公钥和私钥,子算法 ECDHE 用于密钥交换,ECDSA 用于数字签名。
比起 RSA,ECC 在安全强度和性能上都有明显的优势。160 位的 ECC 相当于 1024 位的 RSA,而 224 位的 ECC 则相当于 2048 位的 RSA。因为密钥短,所以相应的计算量、消耗的内存和带宽也就少,加密解密的性能就上去了,对于现在的移动互联网非常有吸引力。
对称加密的优点是运算速度快,缺点是互联网环境下无法将密钥安全的传送给对方。非对称加密的优点是可以安全的将公钥传递给对方,但是运算速度慢。
看到这里,你是不是认为可以抛弃对称加密,只用非对称加密来实现机密性呢?
这里 TLS 把对称加密和非对称加密结合起来,两者互相取长补短,即能高效地加密解密,又能安全地密钥交换。其实说穿了也很简单:
在通信刚开始的时候使用非对称算法,比如 RSA、ECDHE,首先解决密钥交换的问题。
然后用随机数产生对称算法使用的会话密钥(session key),再用公钥加密。因为会话密钥很短,通常只有 16 字节或 32 字节,所以慢一点也无所谓。
对方拿到密文后用私钥解密,取出会话密钥。这样,双方就实现了对称密钥的安全交换,后续就不再使用非对称加密,全都使用对称加密。
这样混合加密就解决了对称加密算法的密钥交换问题,而且安全和性能兼顾,完美地实现了机密性。
不过这只是万里长征的第一步,后面还有完整性、身份认证、不可否认等特性没有实现,所以现在的通信还不是绝对安全。
数字签名与证书
黑客虽然拿不到会话密钥,无法破解密文,但可以通过窃听收集到足够多的密文,再尝试着修改、重组后发给网站。因为没有完整性保证,服务器只能照单全收,然后他就可以通过服务器的响应获取进一步的线索,最终就会破解出明文。
另外,黑客也可以伪造身份发布公钥。如果你拿到了假的公钥,混合加密就完全失效了。你以为自己是在和某宝通信,实际上网线的另一端却是黑客,银行卡号、密码等敏感信息就在安全的通信过程中被窃取了。
所以,在机密性的基础上还必须加上完整性、身份认证等特性,才能实现真正的安全。
摘要算法
实现完整性的手段主要是摘要算法(Digest Algorithm),也就是常说的散列函数、哈希函数(Hash Function)。
你可以把摘要算法近似地理解成一种特殊的压缩算法,它能够把任意长度的数据压缩成固定长度、而且独一无二的摘要字符串,就好像是给这段数据生成了一个数字指纹。
换一个角度,也可以把摘要算法理解成特殊的单向加密算法,它只有算法,没有密钥,加密后的数据无法解密,不能从摘要逆推出原文。
摘要算法实际上是把数据从一个大空间映射到了小空间,所以就存在冲突(collision,也叫碰撞)的可能性,就如同现实中的指纹一样,可能会有两份不同的原文对应相同的摘要。好的摘要算法必须能够抵抗冲突,让这种可能性尽量地小。
因为摘要算法对输入具有单向性和雪崩效应,输入的微小不同会导致输出的剧烈变化,所以也被 TLS 用来生成伪随机数(PRF,pseudo random function)。
你一定在日常工作中听过、或者用过 MD5(Message-Digest 5)、SHA-1(Secure Hash Algorithm 1),它们就是最常用的两个摘要算法,能够生成 16 字节和 20 字节长度的数字摘要。但这两个算法的安全强度比较低,不够安全,在 TLS 里已经被禁止使用了。
目前 TLS 推荐使用的是 SHA-1 的后继者:SHA-2。
SHA-2 实际上是一系列摘要算法的统称,总共有 6 种,常用的有 SHA224、SHA256、SHA384,分别能够生成 28 字节、32 字节、48 字节的摘要。
完整性
摘要算法保证了数字摘要和原文是完全等价的。所以,我们只要在原文后附上它的摘要,就能够保证数据的完整性。
比如,你发了条消息:转账 1000 元,然后再加上一个 SHA-2 的摘要。网站收到后也计算一下消息的摘要,把这两份指纹做个对比,如果一致,就说明消息是完整可信的,没有被修改。
如果黑客在中间哪怕改动了一个标点符号,摘要也会完全不同,网站计算比对就会发现消息被窜改,是不可信的。
不过摘要算法不具有机密性,如果明文传输,那么黑客可以修改消息后把摘要也一起改了,网站还是鉴别不出完整性。
所以,真正的完整性必须要建立在机密性之上,在混合加密系统里用会话密钥加密消息和摘要,这样黑客无法得知明文,也就没有办法动手脚了。
这有个术语,叫哈希消息认证码(HMAC)。
数字签名
加密算法结合摘要算法,我们的通信过程可以说是比较安全了。但这里还有漏洞,就是通信的两个端点(endpoint)。
就像一开始所说的,黑客可以伪装成网站来窃取信息。而反过来,他也可以伪装成你,向网站发送支付、转账等消息,网站没有办法确认你的身份,钱可能就这么被偷走了。
现实生活中,解决身份认证的手段是签名和印章,只要在纸上写下签名或者盖个章,就能够证明这份文件确实是由本人而不是其他人发出的。
在这里,使用非对称加密里的私钥再加上摘要算法,就能够实现数字签名,同时实现身份认证和不可否认。
数字签名的原理其实很简单,就是把公钥私钥的用法反过来,之前是公钥加密、私钥解密,现在是私钥加密、公钥解密。
但又因为非对称加密效率太低,所以私钥只加密原文的摘要,这样运算量就小的多,而且得到的数字签名也很小,方便保管和传输。
签名和公钥一样完全公开,任何人都可以获取。但这个签名只有用私钥对应的公钥才能解开,拿到摘要后,再比对原文验证完整性,就可以像签署文件一样证明消息确实是你发的。
刚才的这两个行为也有专用术语,叫做签名和验签。
只要你和网站互相交换公钥,就可以用签名和验签来确认消息的真实性,因为私钥保密,黑客不能伪造签名,就能够保证通信双方的身份。
比如,你用自己的私钥签名一个消息我是小明。网站收到后用你的公钥验签,确认身份没问题,于是也用它的私钥签名消息我是某宝。你收到后再用它的公钥验一下,也没问题,这样你和网站就都知道对方不是假冒的,后面就可以用混合加密进行安全通信了。
数字证书和 CA
到现在,综合使用对称加密、非对称加密和摘要算法,是不是已经完美了呢?
不是的,这里还有一个公钥的信任问题。因为谁都可以发布公钥,我们还缺少防止黑客伪造公钥的手段,也就是说,怎么来判断这个公钥就是你或者某宝的公钥呢?
真是按下葫芦又起了瓢,安全还真是个麻烦事啊,一环套一环的。
我们可以用类似密钥交换的方法来解决公钥认证问题,用别的私钥来给公钥签名,显然,这又会陷入无穷递归。但这次实在是没招了,要终结这个死循环,就必须引入外力,找一个公认的可信第三方,让它作为信任的起点,递归的终点,构建起公钥的信任链。
这个第三方就是我们常说的 CA(Certificate Authority,证书认证机构)。它就像网络世界里的公安局、教育部、公证中心,具有极高的可信度,由它来给各个公钥签名,用自身的信誉来保证公钥无法伪造,是可信的。CA 对公钥的签名认证也是有格式的,不是简单地把公钥绑定在持有者身份上就完事了,还要包含序列号、用途、颁发者、有效时间等等,把这些打成一个包再签名,完整地证明公钥关联的各种信息,形成数字证书(Certificate)。
知名的 CA 全世界就那么几家,比如 DigiCert、VeriSign、Entrust、Let’s Encrypt 等,它们签发的证书分 DV、OV、EV 三种,区别在于可信程度。
DV 是最低的,只是域名级别的可信,背后是谁不知道。EV 是最高的,经过了法律和审计的严格核查,可以证明网站拥有者的身份(在浏览器地址栏会显示出公司的名字,例如 Apple、GitHub 的网站)。
不过,CA 怎么证明自己呢?
这还是信任链的问题。小一点的 CA 可以让大 CA 签名认证,但链条的最后,也就是Root CA,就只能自己证明自己了,这个就叫自签名证书(Self-Signed Certificate)或者根证书(Root Certificate)。你必须相信,否则整个证书信任链就走不下去了。
有了这个证书体系,操作系统和浏览器都内置了各大 CA 的根证书,上网的时候只要服务器发过来它的证书,就可以验证证书里的签名,顺着证书链(Certificate Chain)一层层地验证,直到找到根证书,就能够确定证书是可信的,从而里面的公钥也是可信的。
证书体系的弱点
证书体系(PKI,Public Key Infrastructure)虽然是目前整个网络世界的安全基础设施,但绝对的安全是不存在的,它也有弱点,还是关键的信任二字。
如果 CA 失误或者被欺骗,签发了错误的证书,虽然证书是真的,可它代表的网站却是假的。
还有一种更危险的情况,CA 被黑客攻陷,或者 CA 有恶意,因为它(即根证书)是信任的源头,整个信任链里的所有证书也就都不可信了。
这两种事情并不是耸人听闻,都曾经实际出现过。所以,需要再给证书体系打上一些补丁。
针对第一种,开发出了 CRL(证书吊销列表,Certificate revocation list)和 OCSP(在线证书状态协议,Online Certificate Status Protocol),及时废止有问题的证书。
对于第二种,因为涉及的证书太多,就只能操作系统或者浏览器从根上下狠手了,撤销对 CA 的信任,列入黑名单,这样它颁发的所有证书就都会被认为是不安全的。
HTTPS 建立连接
当你在浏览器地址栏里键入https开头的 URI,再按下回车,会发生什么呢?
浏览器首先要从 URI 里提取出协议名和域名。因为协议名是https,所以浏览器就知道了端口号是默认的 443,它再用 DNS 解析域名,得到目标的 IP 地址,然后就可以使用三次握手与网站建立 TCP 连接了。
在 HTTP 协议里,建立连接后,浏览器会立即发送请求报文。但现在是 HTTPS 协议,它需要再用另外一个握手过程,在 TCP 上建立安全连接,之后才是收发 HTTP 报文。
这个握手过程与 TCP 有些类似,是 HTTPS 和 TLS 协议里最重要、最核心的部分,懂了它,你就可以自豪地说自己掌握了 HTTPS。
TLS 协议的组成
在讲 TLS 握手之前,我先简单介绍一下 TLS 协议的组成。
TLS 包含几个子协议,你也可以理解为它是由几个不同职责的模块组成,比较常用的有记录协议、警报协议、握手协议、变更密码规范协议等。
记录协议(Record Protocol)规定了 TLS 收发数据的基本单位:记录(record)。它有点像是 TCP 里的 segment,所有的其他子协议都需要通过记录协议发出。但多个记录数据可以在一个 TCP 包里一次性发出,也并不需要像 TCP 那样返回 ACK。警报协议(Alert Protocol)的职责是向对方发出警报信息,有点像是 HTTP 协议里的状态码。比如,protocol_version 就是不支持旧版本,bad_certificate 就是证书有问题,收到警报后另一方可以选择继续,也可以立即终止连接。握手协议(Handshake Protocol)是 TLS 里最复杂的子协议,要比 TCP 的 SYN/ACK 复杂的多,浏览器和服务器会在握手过程中协商 TLS 版本号、随机数、密码套件等信息,然后交换证书和密钥参数,最终双方协商得到会话密钥,用于后续的混合加密系统。最后一个是变更密码规范协议(Change Cipher Spec Protocol),它非常简单,就是一个通知,告诉对方,后续的数据都将使用加密保护。那么反过来,在它之前,数据都是明文的。
下面的这张图简要地描述了 TLS 的握手过程,其中每一个框都是一个记录,多个记录组合成一个 TCP 包发送。所以,最多经过两次消息往返(4 个消息)就可以完成握手,然后就可以在安全的通信环境里发送 HTTP 报文,实现 HTTPS 协议。
ECDHE 握手过程
刚才你看到的是握手过程的简要图,又画了一个详细图,下面我就用这个图来仔细剖析 TLS 的握手过程。
在 TCP 建立连接之后,浏览器会首先发一个Client Hello消息,也就是跟服务器打招呼。里面有客户端的版本号、支持的密码套件,还有一个随机数(Client Random),用于后续生成会话密钥。
HandshakeProtocol:ClientHelloVersion:TLS1.2(0x0303)Random:1cbf803321fd2623408dfe…CipherSuites(17suites)CipherSuite:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xc02f)CipherSuite:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xc030)
这个的意思就是:我这边有这些这些信息,你看看哪些是能用的,关键的随机数可得留着。复制代码
作为礼尚往来,服务器收到Client Hello后,会返回一个Server Hello消息。把版本号对一下,也给出一个随机数(Server Random),然后从客户端的列表里选一个作为本次通信使用的密码套件,在这里它选择了TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384。
Handshake Protocol: Server HelloVersion:TLS1.2(0x0303)Random:0e6320f21bae50842e96…CipherSuite:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xc030)
这个的意思就是:版本号对上了,可以加密,你的密码套件挺多,我选一个最合适的吧,用椭圆曲线加 RSA、AES、SHA384。我也给你一个随机数,你也得留着。复制代码
然后,服务器为了证明自己的身份,就把证书也发给了客户端(Server Certificate)。
接下来是一个关键的操作,因为服务器选择了 ECDHE 算法,所以它会在证书后发送Server Key Exchange消息,里面是椭圆曲线的公钥(Server Params),用来实现密钥交换算法,再加上自己的私钥签名认证。
HandshakeProtocol:ServerKeyExchangeECDiffie-HellmanServerParamsCurveType:named_curve(0x03)NamedCurve:x25519(0x001d)Pubkey:3b39deaf00217894e...SignatureAlgorithm:rsa_pkcs1_sha512(0x0601)Signature:37141adac38ea4...
这相当于说:刚才我选的密码套件有点复杂,所以再给你个算法的参数,和刚才的随机数一样有用,别丢了。为了防止别人冒充,我又盖了个章。复制代码
之后是Server Hello Done消息,服务器说:我的信息就是这些,打招呼完毕。
这样第一个消息往返就结束了(两个 TCP 包),结果是客户端和服务器通过明文共享了三个信息:Client Random、Server Random 和 Server Params。
客户端这时也拿到了服务器的证书,那这个证书是不是真实有效的呢?
这就要用到第 25 讲里的知识了,开始走证书链逐级验证,确认证书的真实性,再用证书公钥验证签名,就确认了服务器的身份:刚才跟我打招呼的不是骗子,可以接着往下走。
然后,客户端按照密码套件的要求,也生成一个椭圆曲线的公钥(Client Params),用Client Key Exchange消息发给服务器。
Handshake Protocol:ClientKeyExchangeECDiffie-HellmanClientParamsPubkey:8c674d0e08dc27b5eaa…
现在客户端和服务器手里都拿到了密钥交换算法的两个参数(Client Params、Server Params),就用 ECDHE 算法一阵算,算出了一个新的东西,叫Pre-Master,其实也是一个随机数。
至于具体的计算原理和过程,因为太复杂就不细说了,但算法可以保证即使黑客截获了之前的参数,也是绝对算不出这个随机数的。
现在客户端和服务器手里有了三个随机数:Client Random、Server Random 和 Pre-Master。用这三个作为原始材料,就可以生成用于加密会 话的主密钥,叫Master Secret。而黑客因为拿不到Pre-Master,所以也就得不到主密钥。
为什么非得这么麻烦,非要三个随机数呢?
这就必须说 TLS 的设计者考虑得非常周到了,他们不信任客户端或服务器伪随机数的可靠性,为了保证真正的完全随机不可预测,把三个不可靠的随机数混合起来,那么随机的程度就非常高了,足够让黑客难以猜测。
你一定很想知道Master Secret究竟是怎么算出来的吧,贴一下 RFC 里的公式:
master_secret= PRF(pre_master_secret,"master secret",ClientHello.random + ServerHello.random)
这里的PRF就是伪随机数函数,它基于密码套件里的最后一个参数,比如这次的 SHA384,通过摘要算法来再一次强化Master Secret的随机性。复制代码
主密钥有 48 字节,但它也不是最终用于通信的会话密钥,还会再用 PRF 扩展出更多的密钥,比如客户端发送用的会话密钥(client_write_key)、服务器发送用的会话密钥(server_write_key)等等,避免只用一个密钥带来的安全隐患。
有了主密钥和派生的会话密钥,握手就快结束了。客户端发一个Change Cipher Spec,然后再发一个Finished消息,把之前所有发送的数据做个摘要,再加密一下,让服务器做个验证。
意思就是告诉服务器:后面都改用对称算法加密通信了啊,用的就是打招呼时说的 AES,加密对不对还得你测一下。
服务器也是同样的操作,发Change Cipher Spec和Finished消息,双方都验证加密解密 OK,握手正式结束,后面就收发被加密的 HTTP 请求和响应了。
RSA 握手过程
整个握手过程可真是够复杂的,但你可能会问了,好像这个过程和其他地方看到的不一样呢?
刚才说的其实是如今主流的 TLS 握手过程,这与传统的握手有两点不同。
第一个,使用 ECDHE 实现密钥交换,而不是 RSA,所以会在服务器端发出Server Key Exchange消息。
第二个,因为使用了 ECDHE,客户端可以不用等到服务器发回Finished确认握手完毕,立即就发出 HTTP 报文,省去了一个消息往返的时间浪费。这个叫TLS False Start,意思就是抢跑,和TCP Fast Open有点像,都是不等连接完全建立就提前发应用数据,提高传输的效率。
这里我也画了个图。
大体的流程没有变,只是Pre-Master不再需要用算法生成,而是客户端直接生成随机数,然后用服务器的公钥加密,通过Client Key Exchange消息发给服务器。服务器再用私钥解密,这样双方也实现了共享三个随机数,就可以生成主密钥。
双向认证
到这里 TLS 握手就基本讲完了。
不过上面说的是单向认证握手过程,只认证了服务器的身份,而没有认证客户端的身份。这是因为通常单向认证通过后已经建立了安全通信,用账号、密码等简单的手段就能够确认用户的真实身份。
但为了防止账号、密码被盗,有的时候(比如网上银行)还会使用 U 盾给用户颁发客户端证书,实现双向认证,这样会更加安全。
双向认证的流程也没有太多变化,只是在Server Hello Done之后,Client Key Exchange之前,客户端要发送Client Certificate消息,服务器收到后也把证书链走一遍,验证客户端的身份。
作者:huansky
来源:https://www.cnblogs.com/huansky/p/13977181.html