ssl-bad-ecpoint-经验帖

Python requests 遇到 BAD_ECPOINT 的终极解决方案

一句话总结

SSLError(1, '[SSL: BAD_ECPOINT] bad ecpoint') 发生在 TLS 握手层 ,与证书验证无关,verify=False 无效。根因是目标服务器使用 ECDH 压缩曲线,OpenSSL 1.1+ 拒绝握手。Windows schannel 无此限制所以本地正常。最终方案:用 curl_cffi 替代 requests,绕过 Python _ssl 模块。

问题场景

湖南生态环境厅网站 sthjt.hunan.gov.cn,在 Docker 容器(python:3.12-slim-bullseye,OpenSSL 1.1)中请求时直接报错:

复制代码
requests.exceptions.SSLError: HTTPSConnectionPool(host='sthjt.hunan.gov.cn', port=443):
Max retries exceeded (Caused by SSLError(SSLError(1, '[SSL: BAD_ECPOINT] bad ecpoint (_ssl.c:1000)')))

Windows 本地运行完全正常

根因分析

为什么 verify=False 无效?

SSL/TLS 握手分为两层:

复制代码
┌─────────────────────────────────┐
│  证书验证层 (verify=True/False)   │  ← verify=False 只跳过这一层
├─────────────────────────────────┤
│  TLS 握手层 (密钥交换/加密套件)    │  ← BAD_ECPOINT 发生在这里
└─────────────────────────────────┘

BAD_ECPOINT 发生在 TLS 握手阶段的 ECDH 密钥交换环节------服务器返回了压缩格式的椭圆曲线点(compressed EC point),OpenSSL 1.1.0+ 出于安全考虑拒绝接受。这是握手层面的拒绝,比证书验证更底层。

为什么 Windows 正常,Linux 报错?

Python 的 SSL/TLS 能力来自 CPython 编译时链接的 SSL 后端:

平台 SSL 后端 对压缩 EC 点的态度
Windows schannel(系统自带) 容忍
Linux (OpenSSL 1.1+) OpenSSL 拒绝
Linux (OpenSSL 3.x) OpenSSL 拒绝
macOS Secure Transport 容忍

关键点 :Python 的 _ssl 模块是 C 扩展,在 CPython 编译时就链接好了,运行时无法替换apt install opensslpip install pyOpenSSL 都改变不了 _ssl 的行为。

尝试过的无效方案

按时间顺序,以下方案均失败

方案 结果 原因
verify_ssl = False 错误在握手层,不在证书层
_LegacySSLAdapter + SECLEVEL=1 安全级别与 EC 点压缩是正交问题
_LegacySSLAdapter + SECLEVEL=0 + OP_LEGACY_SERVER_CONNECT 同上,OpenSSL 1.1 根本不允许压缩 EC 点
降级到 python:3.12-slim-bullseye(OpenSSL 1.1) OpenSSL 1.1.0 开始就拒绝压缩 EC 点了
云端浏览器 API 代理 API 服务端自身崩溃(api_server.py 内部报错)

最终方案:curl_cffi

原理

curl_cffi 通过 CFFI 直接调用 libcurl,完全不经过 Python 的 _ssl 模块 。TLS 握手由 curl 自己的 SSL 后端处理,从根本上规避了 Python _ssl 的限制。

此外它还支持 TLS 指纹伪装(模拟 Chrome/Firefox/Safari 的 JA3 指纹),顺带解决基于 TLS 指纹的反爬。

安装

bash 复制代码
pip install curl_cffi

支持 Python 3.10+,有预编译的 manylinux/musllinux wheel,无需编译。

代码改动

改动量极小------curl_cffi 的 API 与 requests 完全兼容:

python 复制代码
# 之前
import requests
session = requests.Session()

# 之后
from curl_cffi import requests
session = requests.Session(impersonate="chrome124")  # 模拟 Chrome 124 TLS 指纹

完整代码(湖南 pipeline 实际改动):

python 复制代码
from curl_cffi import requests

class HunanPipeline(BasePipeline):
    province = "湖南"

    def _create_session(self) -> requests.Session:
        """使用 curl_cffi Session(Chrome 指纹 + libcurl TLS,绕过 BAD_ECPOINT)"""
        session = requests.Session(impersonate="chrome124")
        session.headers.update({
            "User-Agent": "Mozilla/5.0 ... Chrome/124.0.0.0 Safari/537.36",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
            "Accept-Language": "zh-CN,zh;q=0.9",
        })
        return session

    def collect(self, session, year_filter, first_page_only=False):
        # ... session.get(url) 用法完全不变
        resp = session.get(url, timeout=30)

    def process_item(self, session, item):
        # ... 同样不变
        resp = session.get(item["url"], timeout=30)

不需要 verify_ssl = False不需要 _LegacySSLAdapter不需要改 Dockerfile 基础镜像。

注意事项

  1. curl_cffi 的异常类继承自自己的 curl_cffi.requests.errors.RequestsError不是 标准库的 requests.RequestException。如果基类用 except requests.RequestException 做重试,需要改为同时捕获两者的基类,或用 except Exception 兜底。
  2. impersonate 参数可选的浏览器版本:chrome110chrome120chrome124firefoxsafari 等。国内政府网站建议用 chrome124
  3. 详情页的超时时间建议 > 30s(政府网站响应可能较慢)。

推广适用场景

这个方案不仅适用于 BAD_ECPOINT,同样的思路可以解决其他 Python _ssl 层面的问题:

  • SSLError: unsupported protocol --- 服务器仅支持老旧 TLS 版本
  • SSLError: dh key too small --- DH 密钥长度不足
  • SSLError: no shared cipher --- 加密套件不兼容
  • TLS 指纹反爬(JA3 检测)

只要问题根因在 Python _ssl 模块层面,且无法通过升级/配置 OpenSSL 解决,就可以考虑用 curl_cffi 绕开。

参考

相关推荐
REDcker5 小时前
用 eBPF 观测 SSL/TLS 明文:原理、钩点与边界
网络·网络协议·ssl
数据知道5 小时前
VLAN 与安全隔离:从配置错误到 VLAN Hopping 攻击
网络·安全·网络安全·智能路由器
Tsuki_tl6 小时前
UDP传输协议
网络·网络协议·udp·传输层·udp校验和·tcp与udp区别·面向数据报
Sagittarius_A*6 小时前
Open HTTP Redirect 开放重定向漏洞:URL 跳转校验缺陷与绕过技术
网络·安全·web安全·http·dvwa·burp suite
信仰8746 小时前
HCIA-华为数通基础理论与实践08
网络·笔记·华为
代码的小搬运工9 小时前
网络请求(NSURL、NSURLRequest、NSURLSessionDataTask、协议回调与 JSON 数据的基本流向)
网络·数据库·json
蓝胖的四次元口袋14 小时前
服务器网络与系统基础-面试题
服务器·网络
程序员JerrySUN15 小时前
Jetson 刷机深度解析:flash.sh vs l4t_initrd_flash.sh(含安全与磁盘加密对比)
linux·网络·arm开发·安全·系统安全
视觉AI16 小时前
VS Code Remote-SSH 连接Jetson踩坑完整解决记录(网段不通+主机密钥变更双重故障)
运维·网络·人工智能·windows·ssh·边缘计算
明志数科16 小时前
人形机器人格斗场景下的技术挑战:动力学控制、感知融合与实时决策
网络·数据库