错误信息
[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] ssl/tls alert handshake failure
也可能输出为
- [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure
原因代码始终是 SSLV3_ALERT_HANDSHAKE_FAILURE,但文本取决于 OpenSSL:OpenSSL 3.3 及更高版本为“ssl/tls alert handshake failure”,更早的版本为“sslv3 alert handshake failure”。后面跟着源代码位置,例如“(_ssl.c:1082)”。
Python ssl module (OpenSSL) — ssl.SSLError, TLS alert 40 (handshake_failure) from the server
含义
服务器以 TLS 告警 40(handshake_failure)中止了握手。它在 Python 的 ClientHello 中找不到可接受的参数,或者要求提供客户端证书而客户端没有出示。告警本身不携带任何详细信息,因此只能通过比较双方各自支持的内容,或查看服务器日志来找出原因。
原因
没有共同的密码套件
常见从 Python 3.10 开始,默认上下文使用 OpenSSL 安全级别 2,既不提供不具备前向保密的套件,也不提供使用 SHA-1 MAC 的套件。只支持 RSA 密钥交换套件、CBC-SHA 套件或 3DES 套件的老旧服务器,与它没有任何共同的套件。
服务器要求客户端证书
常见在 TLS 1.2 下,双向 TLS 端点(银行和 B2B API、内部服务)遇到不带客户端证书的握手时,会以这个告警响应。
SNI 缺失,或服务器上没有配置该名称
偶见通过 IP 连接且没有设置 server_hostname,或者连接到 CDN 或代理不提供服务的名称时,一些服务器会以 handshake_failure 中止握手。
修复方法
- 1.
比较密码套件列表
打印这个 Python 提供的套件,并与服务器的列表(例如通过 nmap --script ssl-enum-ciphers 获取)比较。如果没有交集,说明服务器需要更新。
python import ssl ctx = ssl.create_default_context() print(sorted(c["name"] for c in ctx.get_ciphers())) # what this Python offers (TLS 1.2 names) - 2.
出示客户端证书
对于 mTLS API,用 load_cert_chain() 把客户端证书和密钥加载到上下文中;requests 则使用 cert=("client.pem", "client.key")。
python import ssl, urllib.request ctx = ssl.create_default_context() ctx.load_cert_chain(certfile="client.pem", keyfile="client.key") # mutual TLS urllib.request.urlopen("https://mtls.example.com/", context=ctx) - 3.
服务器所有者:提供现行的密码套件
所有仍在维护的客户端都接受 TLS 1.3,以及采用 ECDHE 搭配 AES-GCM 或 ChaCha20-Poly1305 的 TLS 1.2。
text # Server side: offer TLS 1.3 and TLS 1.2 ECDHE + AEAD suites ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;