Go 1.19+ 默认启用 TLS 1.3 且禁用弱密码套件(如 TLS_RSA_WITH_AES_256_CBC_SHA),导致仅支持 TLS 1.0–1.2 或旧套件的老客户端(如嵌入式设备、Java 7)因协议或套件无交集而握手失败。
为什么 TLSConfig 默认值会让老客户端握手失败
Go 1.19+ 的 http.Server 和 tls.Listen 默认启用 TLS 1.3,并禁用不安全的旧密码套件(如 TLS_RSA_WITH_AES_256_CBC_SHA)。但某些嵌入式设备、旧版 Java(read: connection reset by peer 或空 tls: client didn't provide a certificate(实际是协商失败前就中断了)。
关键不是“要不要降级”,而是“降级到哪一级还能守住底线”:tls 1.2 是必须保留的底线,rsa 密钥交换可接受,但要剔除已知被攻破的套件(如 sslv3、rc4、export 等)。
如何配置 TLSConfig 兼容老客户端又不放行高危套件
核心是显式控制三个字段:MinVersion、CipherSuites、CurvePreferences。不要依赖 nil 或默认值。
-
MinVersion设为tls.VersionTLS12—— 不设VersionTLS10,因为 WinXP SP3 起已支持 TLS 1.2,且 TLS 1.0/1.1 已被 RFC 8996 废弃 -
CipherSuites显式列出兼容且尚可接受的套件,例如:[]uint16{ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_256_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, }注意:保留TLS_RSA_*套件是关键,否则老客户端无法完成密钥交换;但务必排除_RC4_、_3DES_、_MD5_、_SHA_(非 SHA256/SHA384)等 -
CurvePreferences建议显式设为[]tls.CurveID{tls.CurveP256}—— 部分旧 OpenSSL(CurveP384 或CurveP521支持不稳,P256 兼容性最好
验证是否真生效:别只信 curl -v
curl -v --tlsv1.2 --ciphers 'AES256-SHA' https://yoursite.com 只能测单点,容易漏掉真实客户端行为。更可靠的方式:
- 用
openssl s_client -connect yoursite.com:443 -tls1_2 -cipher 'AES256-SHA'观察Server certificate和Protocol行是否出现,以及是否报ssl handshake failure - 抓包看 ClientHello:Wireshark 过滤
tls.handshake.type == 1,确认服务端返回的ServerHello.cipher_suite是你允许列表里的值,而非0x0000(表示协商失败) - 测试真实设备:比如某款海康 IPC 固件只认
TLS_RSA_WITH_AES_128_CBC_SHA,若你没显式加入该套件,它连 TCP 握手后立即 RST
额外要注意的两个坑
一是 GetCertificate 回调里不能返回 nil 证书——即使你用的是单域名,也得确保回调返回有效 *tls.Certificate,否则 Go TLS 栈会静默拒绝连接(无错误日志);二是 NextProtos 若包含 "h2",某些只支持 HTTP/1.1 的老客户端会在 ALPN 协商阶段失败,建议删掉或仅留 []string{"http/1.1"}。
真正麻烦的从来不是加几行配置,而是你得知道哪个旧设备卡在哪条密码套件上,而它的文档里往往只写“支持 HTTPS”,不写 TLS 版本和 cipher list。











