要确认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 版本,它依赖底层 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()(conn是ssl.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 通常设为 TLSv1 或 TLSv1_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())ssl_context 参数,但 v2.x 已移除旧接口,务必核对版本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 文件,经常不是同一个。










