Python TLS 1.3 的强制升级检查

3次阅读

要确认python进程实际启用tls 1.3,需依次检查:ssl.openssl_version≥1.1.1、python动态链接openssl、连接后调用conn.version()获取真实协商版本;set_ciphers()若含tls 1.2套件会隐式禁用tls 1.3,须显式包含tls 1.3套件名。

Python TLS 1.3 的强制升级检查

如何确认 Python 进程实际启用的是 TLS 1.3

Python 本身不主动“强制升级”TLS 版本,它依赖底层 OpenSSL。你看到的 ssl.TLSVersion.TLSv1_3 只是常量,不代表运行时一定用上。真正起作用的是 OpenSSL 版本、Python 编译时链接的 OpenSSL、以及你是否显式配置了协议版本。

常见错误现象:ssl.SSLError: [SSL: UNSUPPORTED_PROTOCOL] 或明明代码写了 context.minimum_version = ssl.TLSVersion.TLSv1_3,但抓包发现还是 TLS 1.2。

  • 检查 OpenSSL 实际版本:python -c "import ssl; print(ssl.OPENSSL_VERSION)" —— 必须 ≥ 1.1.1(推荐 1.1.1w+ 或 3.0.0+)
  • 确认 Python 是动态链接 OpenSSL(非静态编译或自带旧版):ldd $(python -c "import sys; print(sys.executable)") | grep ssl
  • 运行时验证:在建立连接后,打印 conn.version()connssl.SSLSocket 实例),这才是真实协商结果

为什么 set_ciphers() 有时会禁用 TLS 1.3

TLS 1.3 的密码套件(cipher suites)和 TLS 1.2 完全不兼容,且 OpenSSL 默认只启用一组预定义的 1.3 套件(如 TLS_AES_256_GCM_SHA384)。一旦你调用 context.set_ciphers("...") 并传入含 TLS 1.2 套件的字符串(比如 "ECDHE+AESGCM"),OpenSSL 会自动关闭 TLS 1.3 支持——这是它的隐式行为,文档里藏得很深。

  • 安全场景下若需自定义套件,必须显式包含 TLS 1.3 套件名,例如:"TLS_AES_256_GCM_SHA384:ECDHE+AESGCM"
  • 更稳妥的做法是分两步:先用 context.set_ciphers("default:@SECLEVEL=2"),再用 context.minimum_version = ssl.TLSVersion.TLSv1_3 强制最低版本
  • 注意:Python 3.10+ 中 set_ciphers() 对 TLS 1.3 的支持更稳定;3.7–3.9 需要 OpenSSL ≥ 1.1.1d 才能正确解析混合套件字符串

requests / urllib3 默认不启用 TLS 1.3?

不是默认“不启用”,而是它们复用系统默认 SSL 上下文,而该上下文的 minimum_version 通常设为 TLSv1TLSv1_2(取决于 Python 版本和 OpenSSL)。所以即使底层支持,也不会自动升到 1.3。

立即学习Python免费学习笔记(深入)”;

  • requests 无法全局设置 TLS 版本,必须通过自定义 HTTPAdapter 注入 context:
  • import requests from requests.adapters import HTTPAdapter from urllib3.util.ssl_ import create_urllib3_context <p>class TLS13Adapter(HTTPAdapter): def init_poolmanager(self, *args, *<em>kwargs): context = create_urllib3_context() context.minimum_version = ssl.TLSVersion.TLSv1_3 kwargs['ssl_context'] = context return super().init_poolmanager(</em>args, **kwargs)</p><p>s = requests.session() s.mount("https://", TLS13Adapter())
  • urllib3 从 v1.26 开始支持 ssl_context 参数,但 v2.x 已移除旧接口,务必核对版本
  • 容易被忽略的点:DNS over HTTPS(DoH)客户端如 dnspython 也走 urllib3,同样受此限制

测试服务端是否真支持 TLS 1.3 的最简方式

别信文档,也别只看 curl 输出——curl 默认可能 fallback 到 1.2。用 Python 写个最小探测脚本,绕过所有高级封装,直连验证。

  • 关键点:必须禁用重协商、禁用 session 复用、指定 server_hostname,并捕获真实 version()
  • 示例(仅需标准库):
  • import socket, ssl <p>context = ssl.create_default_context() context.minimum_version = ssl.TLSVersion.TLSv1_3 context.check_hostname = False context.verify_mode = ssl.CERT_NONE</p><p>with socket.create_connection(("example.com", 443)) as sock: with context.wrap_socket(sock, server_hostname="example.com") as ssock: print(ssock.version())  # 输出 TLSv1.3 或报错
  • 如果报 ssl.SSLError: [SSL: NO_CIPHERS_AVAILABLE],大概率是 OpenSSL 版本太低,或服务端只支持极少数 1.3 套件(如仅 TLS_CHACHA20_POLY1305_SHA256),此时需手动 set_ciphers

真正的难点不在代码怎么写,而在你根本不知道当前 Python 进程链接的是哪个 OpenSSL —— 容器里、conda 环境里、系统 PATH 里的 openssl 命令,和 Python 实际加载的 .so 文件,经常不是同一个。

text=ZqhQzanResources