bigywater.tech
Go · 101

crypto/tls:握手过程和证书校验在代码里怎么走

TLS 握手在 Go 代码里的一些细节常常只在出错时才被注意到:证书不被信任、过期、主机名不符、没设 ServerName。这篇在本机用真实生成的 CA 和证书,把这些错误一个个触发出来,看错误原文,并量会话复用和证书密钥类型对握手耗时的影响。

TLS 1.3
默认协商出的协议版本,密码套件 TLS_AES_128_GCM_SHA256
0.89 → 0.63 ms
本机回环上完整握手与会话复用握手的中位耗时
0.69 vs 1.59 ms
服务端证书用 ECDSA P-256 与 RSA 2048 的完整握手中位耗时

握手之后的状态

实测 · 本机执行输出
  握手成功(1.44 ms):版本 TLS 1.3,密码套件 TLS_AES_128_GCM_SHA256,ALPN 协商结果 "http/1.1",服务端名 "example.test",证书链长度 1,验证后的链数 1

tls.Conn.ConnectionState() 给出协商出的版本、密码套件、ALPN 结果、服务端名和验证后的证书链。默认就是 TLS 1.3;ALPN 取的是客户端提供的列表和服务端支持的列表的交集。

校验失败的错误

实测 · 本机执行输出
  客户端没有信任这个 CA(RootCAs 为空,用系统根证书) → 失败:tls: failed to verify certificate: x509: “example.test” certificate is not trusted
  服务端证书已过期 → 失败:tls: failed to verify certificate: x509: certificate has expired or is not yet valid: current time 2026-10-10T14:42:38+08:00 is after 2026-10-09T06:42:38Z
  证书里的名字是 other.test,客户端要的是 example.test → 失败:tls: failed to verify certificate: x509: certificate is valid for other.test, not example.test
  服务端最高只到 TLS 1.2,客户端要求最低 TLS 1.3 → 失败:remote error: tls: protocol version not supported
  客户端不设 ServerName(用 IP 地址拨号) → 失败:tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
  同上,但 InsecureSkipVerify: true → 握手成功

四类典型错误的原文:不信任(certificate is not trusted,这个措辞来自 macOS 的系统验证器)、过期(has expired or is not yet valid,会带上当前时间)、主机名不符(valid for other.test, not example.test)、版本不匹配(服务端回 protocol version not supported)。另外不设 ServerName 又没设 InsecureSkipVerify,握手前就以配置错误拒绝;InsecureSkipVerify: true 会让握手成功,但文档明确说那样会遭遇中间人攻击,只用于测试。

会话复用

实测 · 本机执行输出
  本机回环上完整握手(30 次):中位 0.89 ms;配 ClientSessionCache 的复用握手(30 次中 DidResume 为真 30 次):中位 0.63 ms

配了 ClientSessionCache 并让服务端的会话票据到达后,30 次握手都是 DidResume,中位耗时 0.63 ms,对完整握手的 0.89 ms 省了约 30%。这是本机回环,没有网络往返,真实网络上节省的还有往返时延,我没有测。

证书密钥类型

实测 · 本机执行输出
  服务端证书 ECDSA P-256:50 次完整握手中位 0.69 ms;密码套件 TLS_AES_128_GCM_SHA256
  服务端证书 RSA 2048:50 次完整握手中位 1.59 ms;密码套件 TLS_AES_128_GCM_SHA256

服务端证书用 RSA 2048,完整握手 1.59 ms,比 ECDSA P-256 的 0.69 ms 慢约 2.3 倍(服务端要做一次 RSA 签名)。对大量短连接的服务,这是可以量到的成本。

文档

实测 · 本机执行输出
  InsecureSkipVerify controls whether a client verifies the server's certificate chain and host name. If InsecureSkipVerify is true, crypto/tls accepts any certificate presented by the server and any host name in that certificate. In this mode, TLS is susceptible to machine-in-the-middle attacks unless custom verification is used. This should be used only for testing or in combination with VerifyConnection or VerifyPeerCertificate.
  MinVersion contains the minimum TLS version that is acceptable. // By default, TLS 1.2 is currently used as the minimum. TLS 1.0 is the minimum supported by this package. MinVersion uint16

文档对 InsecureSkipVerify 的警告和 MinVersion 默认值(TLS 1.2)都与实验一致。

分步演示

crypto/tls
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew);用 go run / go build / go test 直接编译运行,需要第三方库时经代理从 proxy.golang.org 下载;计时用 time.Now / testing.B,数字每次运行会有小幅抖动。CA 与证书在进程内用 crypto/x509 生成(CA 与 ECDSA 叶子证书用 P-256,RSA 叶子 2048 位);握手在本机回环上进行;耗时用 time.Since 测,30 或 50 次取中位数;“证书不被信任”的措辞来自 macOS 的系统验证,Linux 上措辞可能不同;读过的文档:go doc crypto/tls.Config
    • 没有验证:真实网络往返下的握手耗时、证书吊销(OCSP/CRL)、客户端证书(mTLS)、0-RTT、Linux 上的错误措辞、tls.Config 的其它选项(CipherSuites、CurvePreferences)
    京ICP备2026067502号-1京公网安备11010502063487号