网络-16 HTTPS 与 TLS
1. 为什么要有https
- http是明文,容易被监听拦截。
- 加密呗,用对称加密,秘钥传输的时候明文传输,会被监听拦截。
- 可以使用非对称加密,但是非对称加密过程慢,可以采用非对称+对称加密组合的方式,即用非对称加密方式来协商对称加密秘钥。 具体是这样子的: 服务器用明文的方式给客户端发送自己的公钥,客户端收到公钥之后,会生成一把密钥(对称加密用的),然后用服务器的公钥对这把密钥进行加密,之后再把密钥传输给服务器,服务器收到之后进行解密,最后服务器就可以安全着得到这把密钥了,而客户端也有同样一把密钥,他们就可以进行对称加密了。 最后中间人再对这把密钥用刚才服务器的公钥进行加密,再发给服务器。如图:

毫无疑问,在这个过程中,中间人获取了对称加密中的密钥,在之后服务器和客户端的对称加密传输中,这些加密的数据对中间人来说,和明文没啥区别。 4. 问题不在于非对称加密算法本身不安全,而在于缺少身份认证——客户端拿到一把公钥,却无法确定它到底来自真正的服务器还是中间人。解决了"这把公钥属于谁"的问题就好办了。数字证书登场。 5. 我们需要找到一个拥有公信力、大家都认可的认证中心(CA)。 服务器在给客户端传输公钥的过程中,会把公钥以及服务器的个人信息通过Hash算法生成信息摘要。如图

并且,最后还会把原来没Hash算法之前的个人信息以及公钥 和 数字签名合并在一起,形成数字证书。如图

- 当客户端拿到这份数字证书之后,就会用CA提供的公钥来对数字证书里面的数字签名进行解密来得到信息摘要,然后对数字证书里服务器的公钥以及个人信息进行Hash得到另外一份信息摘要。最后把两份信息摘要进行对比,如果一样,则证明这个人是服务器,否则就不是。如图:

2. 什么是https
https简单来说就是https = http + ssl(tls)

这样,http在到达tcp层之前就加密一次 简单一句话就是:https先用非对称加密体系协商出一把对称秘钥,后续就用对称加密算法(AES等)来加密传输。
这里要区分两种协商方式,本文后面"SSL握手流程"一节讲的是前者,但它已经被淘汰了:
- RSA 密钥交换(已淘汰):客户端生成 Premaster secret,用证书里的服务器公钥加密后发给服务器。此时非对称加密同时承担了身份认证和密钥传输两个职责。问题是:整个会话密钥完全由这一次 RSA 加密保护,一旦服务器私钥日后泄露,攻击者就能解开之前录下来的所有历史流量。
- (EC)DHE 密钥协商(现代做法):双方通过 Diffie-Hellman 交换各自的临时公钥,各自独立算出同一个共享秘密——这个秘密从未在网络上传输过。服务器的证书私钥此时只用来签名(证明"我确实是这个域名的持有者"),不参与密钥传输。
第二种做法带来的关键性质叫前向保密(Forward Secrecy,也叫完美前向保密 PFS):DH 用的密钥对是一次性的(这就是 E = Ephemeral 临时的含义),会话结束即丢弃。所以即使服务器私钥将来泄露,也无法用它解密任何历史会话——攻击者顶多能伪造未来的身份,动不了过去的数据。
TLS 1.2 中 ECDHE 是可选的(也仍允许 RSA 密钥交换),TLS 1.3 则直接删除了 RSA 静态密钥交换,完整的首次握手使用临时 (EC)DHE,因此具备前向保密。会话恢复还支持 PSK 与 PSK+(EC)DHE 两种模式,后者才继续提供前向保密。详见 TLS 1.3。
3. 数字证书
服务端如果直接把公钥明文传输给客户端,容易被中间人接收。客户端必须知道接收的公钥是合法的。为了解决这个问题,把公钥告诉权威机构,来生成数字证书以便客户端验证。 验证证书的过程如下:

通过 HTTPS 建立了一个安全 Web 事务之后,现代的浏览器都会自动获取所连接服 务器的数字证书。如果服务器没有证书,安全连接就会失败。
浏览器收到证书时会对签名颁发机构进行检查。如果这个机构是个很有权威的公共签名机构,浏览器可能已经知道其公开密钥了(浏览器会预先安装很多签名颁发机构的证书)。
如果对签名颁发机构一无所知,浏览器就无法确定是否应该信任这个签名颁发机构, 它通常会向用户显示一个对话框,看看他是否相信这个签名发布者。签名发布者可 能是本地的 IT 部门或软件厂商。
数字证书校验一般是校验公钥是否正确,域名、有效期和是否被吊销等。 具体的证书生成及验证流程:
- CA 对证书主体、公钥、域名、有效期等信息计算摘要,再使用 CA 私钥签名,签名和这些信息共同组成数字证书。
- 客户端沿证书链找到受信任的根证书,用签发者公钥验证签名,并重新计算摘要。验签通过说明证书内容未被篡改且确由对应 CA 签发,但还必须继续校验域名、有效期、用途和吊销状态。
这里统一使用“签名/验签”,不要说“私钥加密、公钥解密”。RSA 签名在数学形式上与 RSA 加解密相似,但协议填充和安全目标不同;ECDSA、Ed25519 等签名算法更不存在“解密签名”的过程。
3.1 客户端实际校验证书的完整清单
“CA 签名正确”只是第一关。一个 HTTPS 客户端通常至少检查:
- 证书链:服务端叶子证书是否能通过中间 CA 连到本地信任的根;
- 有效期:当前时间是否处于
Not Before和Not After之间; - 域名:访问的主机名必须出现在证书的 SAN(Subject Alternative Name) 中,现代客户端不再依赖 CN;
- 用途:
Extended Key Usage是否允许 Server Authentication; - 签名算法和密钥强度:拒绝 MD5、SHA-1 等弱算法和过短密钥;
- 吊销信息:根据 CRL、OCSP 或浏览器自身机制判断证书是否已被撤销。
服务端必须发送叶子证书 + 必要的中间证书,通常不必发送根证书。漏发中间证书是“某些浏览器正常、某些客户端报 unknown authority”的常见原因,因为部分客户端碰巧缓存过该中间证书。
吊销检查并不完美:实时 OCSP 查询会增加延迟,查询失败时很多客户端选择 soft-fail。OCSP Stapling 允许服务端定期向 CA 获取带签名的 OCSP 响应,并在 TLS 握手时一起发给客户端,减少额外查询和隐私泄露。
实战检查:
openssl s_client -connect example.com:443 -servername example.com -showcerts
openssl s_client -connect example.com:443 -servername example.com -status
-servername 很重要,它发送 SNI。一个 IP 托管多个 HTTPS 站点时,不带 SNI 可能拿到默认证书,导致域名不匹配。
4. 数字签名
数字签名结合了密码学哈希和非对称签名算法:发送者用私钥对消息摘要及相关上下文生成签名,接收者用公钥验签。如果验签通过,说明消息由持有对应私钥的一方签发,且签名覆盖的内容没有被修改。 数字签名的过程如下: 明文 → 哈希/签名算法 → 私钥签名 → 数字签名;接收方使用公钥验签。
数字签名有两种功效: 一、能确定消息确实是由发送方签名并发出来的,因为别人假冒不了发送方的签名。 二、数字签名能确定消息的完整性。
注意: 数字签名只能验证数据的完整性,数据本身是否加密不属于数字签名的控制范围
4.1 ca数字证书
我们知道了服务端端的数字证书是来自ca的。验证服务端证书时,需要使用签发 CA 的公钥验签。 怎么知道ca的公钥是安全的,不是伪造的?
世界上的CA认证中心不止一家,

CA认证中心之间是一个树状结构,根CA认证中心可以授权多个二级的CA认证中心,同理二级CA认证中心也可以授权多个3级的CA认证中心…如果你是数字证书申请人(比如说:交通银行),你可以向根CA认证中心,或者二级,三级的CA认证中心申请数字证书,这是没有限制的,当你成功申请后,你就称为了数字证书所有人。值得注意的是,根CA认证中心是有多个的,也就是说会有多棵这样的结构树。
实际上每个CA认证中心/数字证书所有人,他们都有一个数字证书,和属于自己的RSA公钥和密钥,这些是他们的父CA认证中心给他们颁发的。
CA的数字证书和服务端的数字证书类似:上一级 CA 用私钥为下一级 CA 的证书签名,客户端逐级验签,直到找到操作系统或应用信任库内置的根证书。根证书通常是自签名的,它不是靠继续向上验签获得信任,而是作为本地预置的信任锚。
5. TLS握手流程(TLS 1.2 的 RSA 密钥交换)
说明:SSL 是 TLS 的前身,SSLv3 早在 2015 年就被 RFC 7568 明令禁用,现在规范的叫法应该统一用 TLS。下面这个流程是 TLS 1.2 的 RSA 密钥交换版本,它不具备前向保密,现代 TLS 已不再使用;保留在这里是因为它最直观,适合理解握手要解决的问题。TLS 1.3 的实际握手流程见 TLS 1.3。

握手阶段分成五步。
第一步,爱丽丝给出协议版本号、一个客户端生成的随机数(Client random),以及客户端支持的加密方法。
第二步,鲍勃确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random)。
第三步,爱丽丝确认数字证书有效,然后生成一个新的随机数(Premaster secret),并使用数字证书中的公钥,加密这个随机数,发给鲍勃。
第四步,鲍勃使用自己的私钥,获取爱丽丝发来的随机数(即Premaster secret)。
第五步,爱丽丝和鲍勃根据约定的加密方法,使用前面的三个随机数,生成"对话密钥"(session key),用来加密接下来的整个对话过程。
注意: 整个握手阶段都不加密(也没法加密),都是明文的。因此,如果有人窃听通信,他可以知道双方选择的加密方法,以及三个随机数中的两个。整个通话的安全,只取决于第三个随机数(Premaster secret)能不能被破解。
5.1 服务端对客户端验证
对于非常重要的保密数据,服务端还需要对客户端进行验证,以保证数据传送给了安全的合法的客户端。服务端可以向客户端发出 Cerficate Request 消息,要求客户端发送证书对客户端的合法性进行验证。比如,金融机构往往只允许认证客户连入自己的网络,就会向正式客户提供USB密钥,里面就包含了一张客户端证书。
5.2 现代 TLS 1.2:ECDHE 握手
现代 TLS 1.2 不再使用上面的 RSA 密钥交换,而使用 ECDHE:
- 客户端发送 ClientHello:支持的版本、密码套件、随机数、SNI、ALPN,以及支持的椭圆曲线等;
- 服务端返回 ServerHello、证书和临时 ECDHE 公钥;
- 服务端使用证书对应的私钥为握手参数签名,证明临时公钥确实来自证书持有者;
- 客户端验证证书和签名,发送自己的临时 ECDHE 公钥;
- 双方分别计算出相同的共享秘密,再用 KDF 派生出各方向的对称密钥;
- 双方发送 Finished,验证此前所有握手消息未被篡改。
关键区别是:共享秘密从未在网络中传输,证书私钥只负责身份签名,不负责解密会话密钥。 临时 ECDHE 私钥在会话后销毁,因此服务器长期私钥未来泄露,也无法解密以前录下的流量,这就是前向保密。
TLS 1.3 进一步简化了握手和密码套件。完整的首次握手使用临时 (EC)DHE;会话恢复还可以使用 PSK,通常配合 (EC)DHE 保留前向保密。详见 网络-17 TLS 1.3。
5.3 SNI、ALPN 与会话恢复
三个扩展和 Go 服务部署直接相关:
- SNI:客户端在 ClientHello 中告诉服务端要访问的域名,使同一 IP 可以选择不同证书。传统 SNI 是明文的,ECH 才能加密 ClientHello 中的敏感信息;
- ALPN:在 TLS 握手中协商上层协议,例如
h2或http/1.1,避免握手结束后再探测。Go 的net/http会据此启用 HTTP/2; - 会话恢复:TLS 1.2 的 Session ID/Ticket、TLS 1.3 的 PSK 可以复用此前建立的状态,减少完整握手的 CPU 和 RTT 开销。
多副本 Go 服务部署在负载均衡器后时,如果每个实例使用不同的 Session Ticket Key,客户端打到另一实例就无法恢复会话。可以让入口 LB 统一终止 TLS,或安全地协调轮换 ticket key;不要为了命中率长期使用永不轮换的固定密钥。
6. 抓包原理
HTTPS即使安全,也是能够被抓包的,常见的抓包工具有:Charles、fildder等。 常用的HTTPS抓包方式是作为中间人,对客户端伪装成服务端,对服务端伪装成客户端。简单来说:
截获客户端的HTTPS请求,伪装成中间人客户端去向服务端发送HTTPS请求 接受服务端返回,用自己的证书伪装成中间人服务端向客户端发送数据内容。
具体过程如下图所示:

7. 反抓包策略
为了防止中间人攻击,可以使用SSL-Pinning的技术来反抓包。 可以发现中间人攻击的要点的伪造了一个假的服务端证书给了客户端,客户端误以为真。解决思路就是,客户端也预置一份服务端的证书,比较一下就知道真假了。 SSL-pinning有两种方式: 证书锁定(Certificate Pinning) 和公钥锁定( Public Key Pinning)。
- 证书锁定 需要在客户端代码内置仅接受指定域名的证书,而不接受操作系统或浏览器内置的CA根证书对应的任何证书。它能缩小信任范围,但不能保证“绝对安全”:客户端仍可能被逆向、Hook,服务端私钥也可能泄露。证书续期后通常还需要重新发布客户端,因此必须预留新旧 pin 并存的轮换窗口。
- 公钥锁定 提取证书中的公钥哈希并内置到客户端中,通过与服务器证书里的公钥对比来验证连接。续期时若沿用密钥对则不需要改 pin,但长期复用同一私钥也会增加密钥泄露风险。无论采用哪种方式,都应至少内置一个备用 pin,否则密钥紧急轮换可能让所有旧客户端永久无法连接。
没有绝对的安全,这不能挡住逆向反编译。
8. Go 服务端的 TLS 配置
Go 标准库已经提供安全的默认值,最重要的原则是:少配密码学细节,明确协议下限,自动化证书续期,并给 HTTP Server 配超时。
srv := &http.Server{
Addr: ":443",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
IdleTimeout: 90 * time.Second,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
},
}
log.Fatal(srv.ListenAndServeTLS("fullchain.pem", "privkey.pem"))
fullchain.pem 应包含叶子证书和中间证书。一般不要手工配置 CipherSuites:TLS 1.3 套件本就不能通过该字段配置,Go 也会随版本淘汰不安全套件。
客户端最危险的配置是:
&tls.Config{InsecureSkipVerify: true}
它会跳过证书链和域名校验,让中间人证书也能通过。测试环境若必须信任自建 CA,应把 CA 加入 RootCAs;需要额外约束证书时使用正常校验后的 VerifyConnection,不要用 InsecureSkipVerify 绕开整套 PKI。
mTLS 场景下,服务端要求并验证客户端证书:
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: clientCAPool,
}
mTLS 证明的是“客户端持有某张证书的私钥”。业务仍应把证书里的身份映射到具体账号/服务,并设计证书吊销和轮换机制。
9. 问题
-
HTTPS和HTTP的区别 https协议需要到CA申请证书。 http是超文本传输协议,信息是明文传输;https 则是具有安全性的ssl加密传输协议。 http和https默认端口不一样,前者是80,后者是443。 HTTPS 是在 HTTP 与 TCP 之间插入了一层 TLS,提供加密传输、身份认证和完整性校验。注意 HTTP 的"无状态"和安全性是两个无关的维度——HTTPS 同样是无状态的,加了 TLS 并不会让它变得有状态。
-
抓包 Q: 使用 HTTPS 会被抓包吗? A: 会被抓包,HTTPS 只防止用户在不知情的情况下通信被监听,如果用户主动授信,是可以构建“中间人”网络,代理软件可以对传输内容进行解密。 我们使用抓包工具的第一步就是在你自己设备中信任 Charles 的 CA 证书,在自己的设备中添加了一个 CA,请求的时候,Charles 通过自己的 CA 签名了一个自己的公钥,发送给客户端,客户端就误以为是服务器了,这样之后的流程都会先走到 Charles 然后才会走到目标服务器。 Charles 扮演了一个中间人的角色,而且这个中间人是我们自己设置的。 因此要想防止抓包,应用应该自己做处理。
-
为什么一定要用三个随机数来生成”会话密钥”呢?
网上流传很广的一种解释是"一个伪随机数可能不够随机,三个伪随机数叠加就接近真随机了"——这个说法是错的。随机性不会因为把几个弱随机数拼在一起就变强,如果 PRNG 本身有缺陷,拼三个照样能被预测。
真正的原因是 Client random 和 Server random 由双方各自独立生成,这带来两个性质:
- 防重放攻击:每次握手客户端和服务端都会给出全新的随机数,即使攻击者完整录下了上一次握手的所有报文,重放时也会因为随机数不同而算出完全不同的会话密钥,握手无法复用。
- 双方都无法单独控制会话密钥:会话密钥由三个随机数共同派生,任何一方(以及任何中间人)都只能贡献其中一部分,没法把最终密钥引导到自己预设的值上。这一点在 TLS 1.3 里更关键——它的握手签名要覆盖双方的随机数,以此抵御降级攻击。
至于 Premaster secret 的机密性,靠的是非对称加密(或 DH 协商)本身,而不是"多凑几个随机数"。
10. 本章面试题
HTTPS 如何保证安全?为什么要用非对称 + 对称结合?
HTTPS = HTTP + TLS,提供三个保证:加密(防窃听)、身份认证(防冒充)、完整性校验(防篡改)。
为什么要结合两种加密:
- 纯对称加密快,但密钥怎么安全地传给对方?明文传会被截获;
- 纯非对称加密能解决密钥分发,但运算慢,不适合加密大量数据。
所以:用非对称加密体系协商出一把对称密钥,后续通信全用对称加密(AES 等)。兼顾了安全与性能。
注意现代说法的修正:TLS 1.2 的 ECDHE 和 TLS 1.3 用的是 (EC)DHE 密钥协商而非"用公钥加密密钥传过去",非对称加密只用于身份签名。这带来了前向保密,详见 网络-17。
数字证书解决什么问题?验证过程是怎样的?
解决身份认证问题:服务端直接明文发公钥的话,中间人可以替换成自己的公钥,客户端无法分辨。证书的作用是证明"这把公钥确实属于这个域名"。
签发:CA 为服务端的公钥、域名、有效期等信息生成摘要,再用 CA 私钥签名,签名 + 基础信息组成证书。
验证:
- 客户端沿证书链找到受信任的根;
- 使用签发者公钥逐级验签;
- 校验证书有效期、SAN 域名、扩展用途、算法强度和吊销状态;
- 全部通过后,才把叶子证书里的公钥绑定到当前域名。
证书链:CA 之间是树状结构,逐级验证直到操作系统/浏览器内置的根证书为止。根证书是信任的起点(自签名)。
HTTPS 能被抓包吗?原理是什么?怎么防?
能,但前提是用户主动信任了抓包工具的 CA 证书。
原理是受控的中间人:Charles/Fiddler 对客户端伪装成服务端(用自己的 CA 签发假证书),对服务端伪装成客户端。因为你已经在设备上信任了它的 CA,客户端校验证书时就不会报错。
这不算 HTTPS 的漏洞——HTTPS 防的是"用户不知情的情况下被监听",用户主动授信则不在防护范围内。
防御(SSL Pinning):客户端内置预期的证书或公钥,只接受它,不信任系统 CA 列表。
- 证书锁定:内置整张证书,缺点是证书续期后要重新发版;
- 公钥锁定:只内置公钥,续期时密钥对不变则无需改动,一般推荐这种。
但 Pinning 挡不住逆向反编译,没有绝对安全。
为什么握手要用三个随机数生成会话密钥?
流传很广的错误说法是"一个伪随机数不够随机,三个叠加就接近真随机"——这是错的,随机性不会因为拼接就变强。
真正的原因是 Client random 和 Server random 由双方各自独立生成:
- 防重放攻击——每次握手随机数都是新的,攻击者即使完整录下上次握手,重放时也会算出完全不同的密钥;
- 双方都无法单独控制会话密钥——任何一方(及中间人)只能贡献其中一部分,无法把最终密钥引导到预设值。
Premaster secret 的机密性靠非对称加密/DH 协商本身保证,而不是"多凑几个随机数"。
HTTP 和 HTTPS 的区别?
- HTTPS 在 HTTP 与 TCP 之间插入了一层 TLS,提供加密、身份认证、完整性校验;
- HTTPS 需要向 CA 申请证书(通常有成本,Let’s Encrypt 免费);
- 默认端口不同:HTTP 是 80,HTTPS 是 443;
- HTTPS 有额外的握手开销(TLS 1.2 多 2 个 RTT,1.3 多 1 个)和少量 CPU 开销。
注意一个常见的概念混淆:HTTP 的"无状态"和安全性是两个无关的维度。HTTPS 同样是无状态的,加 TLS 不会让它变得有状态。
SNI 和 ALPN 分别解决什么问题?
- SNI 在 ClientHello 中携带目标域名,让一个 IP 上的多个 HTTPS 站点选择正确证书;
- ALPN 在 TLS 握手中协商上层协议,例如
h2或http/1.1,避免握手后再探测。
排查多域名证书时,openssl s_client 必须带 -servername,否则服务端可能返回默认证书。
Go TLS 配置最常见的安全错误是什么?
客户端设置 InsecureSkipVerify: true 会跳过证书链和域名校验,使中间人证书也能通过。自建 CA 应加入 RootCAs,而不是关闭验证。
服务端应至少明确 MinVersion: tls.VersionTLS12、提供完整中间证书链并自动续期;通常不要手工固定密码套件,让 Go 安全默认值随版本演进。
参考资料: http://www.wxtlife.com/2016/03/27/%E8%AF%A6%E8%A7%A3https%E6%98%AF%E5%A6%82%E4%BD%95%E7%A1%AE%E4%BF%9D%E5%AE%89%E5%85%A8%E7%9A%84%EF%BC%9F/ https://mp.weixin.qq.com/s?__biz=Mzg2NzA4MTkxNQ==&mid=2247485216&idx=1&sn=fd119ae8e9d184a81cc1dd2984e8d4b8&scene=21#wechat_redirect
xingliuhua