当我们在浏览器里点开一把小锁:SSL/TLS是怎么保护我们的
事情从地址栏那把小锁说起我以前一直没认真想过一个问题每次在浏览器里输入网址凭什么就敢把账号密码发出去明文HTTP的时代早就过去了现在几乎所有网站地址栏前面都顶着一把小绿锁点开能看到一句连接是安全的。这把锁背后站着的就是 SSL/TLS。说起来 SSL 这个名字其实有点误导。它最早是 Netscape 在 1994 年搞出来的叫 Secure Sockets Layer经历了 2.0、3.0 两版。1999 年以后 IETF 接手改名 TLSTransport Layer Security从此 TLS 1.0、1.1、1.2、1.3 一路走下来。但因为 SSL 3.0 的设计底子打得好习惯上大家还是把整族协议笼统地叫SSL/TLS连 OpenSSL 这个最出名的实现库都保留着这个名字。所以当我们说SSL的时候很多时候其实在说 TLS。这一点一开始把我绕晕过。它到底在 TCP 和应用之间塞了什么SSL/TLS 不改动 TCP也不改动 HTTP它只是像一个夹心层一样蹲在两者中间。上层应用把明文交给它它加密之后再交给 TCP 发出去接收端反过来TCP 把密文给它它解密还原成明文递给应用。对应用来说这一切几乎是透明的。这个设计很聪明。任何跑在 TCP 上的协议——HTTP、SMTP、IMAP——都能不改动自身逻辑就获得加密保护。HTTPS 就是 HTTP over TLS 的缩写仅此而已。协议内部分两层。下面是记录协议Record Protocol负责把数据切片、计算消息认证码、加密、打包上面是握手协议Handshake Protocol负责在通信双方之间协商好用什么算法、用什么密钥。我后来发现理解 SSL/TLS 的关键几乎全在那个握手里。两个人在公开网络里怎么对暗号握手是最有意思的部分。问题本身听起来就有点矛盾两个素未谋面的人在一个满是窃听者的网络里要商量出一把只有他们俩知道的密钥还不能被中间人骗。怎么做到SSL/TLS 的答案是用非对称密码学开路用对称密码学干活。以 TLS 1.2 的完整握手为例大致是这样几步。客户端先发 ClientHello把自己支持的版本、密码套件、一个随机数都列出来服务器回 ServerHello从中挑一套再附上自己的证书和服务器随机数。然后双方做一次密钥交换——可能是 RSA也可能是 ECDHE——各自算出同一个预主密钥再结合两个随机数派生出会话密钥。最后双方各发一条 Finished 消息里面带着整个握手过程的校验值确认没人篡改过握手内容。握手一结束后面就全用对称加密跑数据了。这个设计的精妙之处在于混合非对称算法慢但能解决密钥分发对称算法快但不能在公开信道上商量密钥于是各取所长。Schneier 在《应用密码学》里就指出过这一点。我读到这段时觉得密码协议设计很多时候不是发明新东西而是把已有的几块拼成一个自洽的整体。还有一个小细节让我印象深刻握手里的两个随机数一个客户端给的、一个服务器给的看似多余其实是为了防重放。每次握手随机数都不一样攻击者就算录下整个会话也没法原样重播。这协议踩过的那些坑光看原理会觉得 SSL/TLS 设计得很漂亮但翻它的安全历史简直是一部事故编年史。我挑几个印象深的写。POODLE2014CVE-2014-3566打的是 SSL 3.0 的 CBC 填充。攻击者先把连接降级到 SSL 3.0再利用 CBC 填充的冗余性通过观察服务器对错误填充的反应一点点把明文——通常是 Cookie——抠出来。这个漏洞直接把 SSL 3.0 送进坟墓IETF 在 2015 年发了 RFC 7568 正式废弃它。BEAST2011CVE-2011-3389打的是 TLS 1.0 的 CBC 初始化向量可预测。攻击者在浏览器里塞一段恶意 JavaScript配合中间人能逐字节猜出会话 Cookie。TLS 1.1 早就用显式 IV 修了但因为兼容性TLS 1.0 在 BEAST 公开后还在用。Heartbleed2014CVE-2014-0160最让我警醒。它根本不是协议设计的问题是 OpenSSL 实现心跳扩展时少做了一次边界检查结果攻击者发个伪造长度的心跳请求就能从服务器内存里读走最多 64KB 的数据——私钥、会话密钥、用户凭证什么都可能漏。Heartbleed 给我的触动是协议再安全实现一烂全完。这也是为什么后来 BoringSSL、LibreSSL 这些 OpenSSL 的分支会冒出来——光靠修补不够得从工程上重新清理。TLS 1.3一次回头的重塑TLS 1.3RFC 84462018不是小修小补是推翻重来。我读这份 RFC 的时候能感觉到设计者的一种决绝。它干了几件大事。第一把所有已知不安全的算法和机制全砍了RSA 密钥交换、CBC 模式、RC4、3DES、MD5、SHA-1一个不留。剩下 5 个密码套件全部用 AEAD 认证加密全部强制 ECDHE 前向安全。前向安全的意思是就算服务器长期私钥哪天泄露以前抓过的密文也解不开——因为每次会话的密钥都是临时算的、用完就扔。第二握手从 2-RTT 压到 1-RTT还搞了个 0-RTT 模式重连时第一个包就能带数据。这对手机端延迟敏感的场景很实在。第三ServerHello 之后的所有握手消息都加密了。老版本里大部分握手是明文被动观察者能看清你协商了什么。TLS 1.3 把这些都盖上了。读到这里我有个感受TLS 1.3 的设计哲学其实是做减法。把所有历史包袱一刀切掉只留最安全的极简组合。这和软件工程里less is more的思路是一回事。自己动手用 Python 窥探真实的握手光看文献不过瘾我想自己看看真实的 TLS 握手长什么样。Python 标准库的 ssl 模块刚好够用不用装第三方包。核心代码其实不长。思路是建一个安全的 TLS 上下文强制证书校验然后连到目标站点握手完了把协商出来的版本、密码套件、证书信息都打出来。Pythonimport sslimport socketdef create_secure_context():ctx ssl.create_default_context()ctx.check_hostname Truectx.verify_mode ssl.CERT_REQUIREDctx.minimum_version ssl.TLSVersion.TLSv1_2return ctxdef connect_and_inspect(host, port443):ctx create_secure_context()with socket.create_connection((host, port), timeout10) as sock:with ctx.wrap_socket(sock, server_hostnamehost) as ssock:print(协议版本:, ssock.version())cipher ssock.cipher()print(密码套件:, cipher[0])cert ssock.getpeercert()subject dict(x[0] for x in cert.get(subject, []))print(证书主体:, subject.get(commonName))print(有效期:, cert.get(notBefore), 至, cert.get(notAfter))我顺手把它扩成一个扫描脚本跑了 8 个知名网站。结果挺有意思挑几个列出来站点协商版本密码套件证书颁发者www.python.orgTLSv1.3TLS_AES_128_GCM_SHA256GlobalSign Atlas R3 DV TLS CA 2025 Q4www.cloudflare.comTLSv1.3TLS_AES_256_GCM_SHA384WE1www.mozilla.orgTLSv1.3TLS_AES_128_GCM_SHA256YR1www.microsoft.comTLSv1.3TLS_AES_256_GCM_SHA384Microsoft TLS G2 RSA OCSP 04www.apple.comTLSv1.3TLS_AES_128_GCM_SHA256Apple Public EV Server RSA CA 4 - G1www.baidu.comTLSv1.2ECDHE-RSA-AES128-GCM-SHA256GlobalSign RSA OV SSL CA 2018几个观察。第一6 个成功连上的站点里有 5 个跑的是 TLS 1.3说明这协议真的在落地不是我调研报告里空写的。第二密码套件清一色 ECDHE AES-GCM全支持前向安全没有 CBC 模式的影子和 TLS 1.3 的设计取舍完全吻合。第三百度还在用 TLS 1.2这其实不奇怪——大站要照顾海量老客户端协议升级永远是渐进的不能一刀切。这个细节让我对理论上的最优和工程上的现实之间的差距有了点实感。还有个小插曲。我本来想扫 github.com结果本地证书链验证没过报了 CERTIFICATE_VERIFY_FAILED。这多半是我机器上 CA 证书库的问题但反过来也说明证书校验这道关卡是真实在工作的不是摆设。写在最后这次调研让我对 SSL/TLS 的认识从知道有这么个东西变成大概能讲清楚它怎么工作、踩过什么坑、为什么现在是这个样子。几个感受记一下。一是密码协议的安全是个动态过程。SSL/TLS 这二十多年几乎每个版本都是被漏洞推着往前走的——POODLE 推动了 SSL 3.0 退役BEAST 推动了显式 IVHeartbleed 暴露了实现层的风险最后这些教训汇总成 TLS 1.3 的大重构。没有哪个协议是设计完就安全的安全是持续攻防的结果。二是设计层和实现层是两件事。Heartbleed 不是协议的错是 OpenSSL 的错。一个协议设计得再严密落到代码里一个边界检查漏了照样可能全军覆没。这也解释了为什么密码学库里用经过审查的成熟实现几乎是铁律自己造轮子的代价太高。三是对称和非对称的配合很优雅。非对称负责在公开信道上安全地商量密钥对称负责高效地加密数据两者各取所长。这种混合思路在密码协议里随处可见SSL/TLS 只是把它用得最显眼的一个。往远了看量子计算的发展正在威胁 RSA 和 ECC 这些公钥算法。NIST 已经在推后量子密码学标准化IETF 也在研究怎么把后量子算法塞进 TLS。SSL/TLS 的故事还没完下一个十年大概率又是一次大改。我作为一个大二学生能在这个时间点把这个协议的来龙去脉摸一遍挺值的。最后要说明文中的算法描述、RFC 编号、漏洞编号都核对过原文扫描数据是我自己跑脚本拿到的真实结果没有手工编造。理解有偏差的地方责任在我。参考资料Freier A, Karlton P, Kocher P. The Secure Sockets Layer (SSL) Protocol Version 3.0. RFC 6101, IETF, 2011.Dierks T, Allen C. The TLS Protocol Version 1.0. RFC 2246, IETF, 1999.Dierks T, Rescorla E. The Transport Layer Security (TLS) Protocol Version 1.1. RFC 4346, IETF, 2006.Dierks T, Rescorla E. The Transport Layer Security (TLS) Protocol Version 1.2. RFC 5246, IETF, 2008.Rescorla E. The Transport Layer Security (TLS) Protocol Version 1.3. RFC 8446, IETF, 2018.Barnes R, Thomson M, Pironti A, et al. Deprecating Secure Sockets Layer Version 3.0. RFC 7568, IETF, 2015.Wagner D, Schneier B. Analysis of the SSL 3.0 Protocol. The Second USENIX Workshop on Electronic Commerce Proceedings, 1996: 29-40.Rescorla E. SSL and TLS: Designing and Building Secure Systems. Addison-Wesley Professional, 2000.Stallings W. Cryptography and Network Security: Principles and Practice. 7th Edition. Pearson, 2017.Schneier B. Applied Cryptography: Protocols, Algorithms, and Source Code in C. 2nd Edition. John Wiley Sons, 1996.NIST. Guidelines for the Selection, Configuration, and Use of TLS Implementations. SP 800-52 Rev. 2, 2019.韦卫, 王德杰, 张英, 等. 基于SSL的安全WWW系统的研究与实现. 计算机研究与发展, 1999.