SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED 错误解决方法
问题描述
当使用 Python 代码通过 HTTPS 连接到特定服务器时(如 ssd.jpl.nasa.gov:443
),可能会出现以下 SSL 错误:
python
requests.exceptions.SSLError: HTTPSConnectionPool(host='ssd.jpl.nasa.gov', port=443):
Max retries exceeded with url: /api/horizons.api?format=text&EPHEM_TYPE=OBSERVER&QUANTITIES_[...]_
(Caused by SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED]
unsafe legacy renegotiation disabled (_ssl.c:997)')))
该错误通常发生在使用 OpenSSL 3.x 版本连接不支持安全重协商的旧服务器时。
安全警告
启用传统不安全重协商会使 SSL 连接容易受到 CVE-2009-3555 中描述的中间人前缀攻击。建议仅在必要时临时使用这些解决方案。
解决方案
方法一:使用自定义 SSL 上下文(推荐)
这是最灵活的解决方法,可以在代码级别控制 SSL 配置:
python
import ssl
import requests
import urllib3
class CustomHttpAdapter(requests.adapters.HTTPAdapter):
"""传输适配器,允许使用自定义 ssl_context"""
def __init__(self, ssl_context=None, **kwargs):
self.ssl_context = ssl_context
super().__init__(**kwargs)
def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = urllib3.poolmanager.PoolManager(
num_pools=connections,
maxsize=maxsize,
block=block,
ssl_context=self.ssl_context
)
def get_legacy_session():
"""创建支持传统重协商的会话"""
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT 标志
session = requests.session()
session.mount('https://', CustomHttpAdapter(ctx))
return session
# 使用示例
session = get_legacy_session()
response = session.get("https://target-website.com/api/data")
方法二:使用 urllib.request
如果不想使用 requests 库,可以使用标准库的解决方案:
python
import urllib.request
import ssl
# 创建自定义 SSL 上下文
ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ssl_context.options |= 0x4 # OP_LEGACY_SERVER_CONNECT 标志
url = "https://target-website.com/api/data"
response = urllib.request.urlopen(url, context=ssl_context)
data = response.read().decode("utf-8")
方法三:aiohttp 异步请求
对于异步应用,可以使用以下方法:
python
import aiohttp
import ssl
async def secure_request(url):
custom_ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
custom_ssl_context.options |= 0x00040000 # SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
connector = aiohttp.TCPConnector(ssl=custom_ssl_context)
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get(url) as response:
return await response.text()
# 使用示例(在异步函数中)
# result = await secure_request("https://target-website.com/api/data")
开发环境警告
以下设置会降低安全性,仅推荐在开发环境中使用:
python
# 禁用主机名验证和证书验证(仅用于测试)
custom_ssl_context.check_hostname = False
custom_ssl_context.verify_mode = ssl.CERT_NONE
方法四:环境变量配置(系统级)
可以通过设置 OpenSSL 配置文件来解决此问题:
- 创建自定义 OpenSSL 配置文件
custom_openssl.cnf
:
ini
openssl_conf = openssl_init
[openssl_init]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
Options = UnsafeLegacyRenegotiation
- 设置环境变量指向该配置文件:
bash
# 临时设置
export OPENSSL_CONF=/path/to/custom_openssl.cnf
python your_script.py
# 或在运行命令时直接设置
OPENSSL_CONF=/path/to/custom_openssl.cnf python your_script.py
注意事项
这种方法会影响所有使用系统 OpenSSL 库的应用,且可能在 OpenSSL 更新时被覆盖。
其他语言的解决方案
Ruby
ruby
# 设置 OP_LEGACY_SERVER_CONNECT 选项
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_LEGACY_SERVER_CONNECT
# 发起请求
uri = URI('https://example.com')
res = Net::HTTP.post(uri, {}.to_json)
# 请求完成后取消设置
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] &= ~OpenSSL::SSL::OP_LEGACY_SERVER_CONNECT
Node.js
某些情况下,从 Node.js 18 降级到 16 可以解决此问题,但不推荐作为长期解决方案。
替代方案
降级 OpenSSL(不推荐)
对于使用 conda 的环境:
bash
conda install -n your-env-name openssl=1.0
降级 cryptography 包
bash
pip install cryptography==36.0.2
最佳实践
- 优先使用方法一或方法二,它们只影响当前应用而非整个系统
- 仅对确实需要连接的不支持安全重协商的服务器启用此选项
- 长期解决方案应该是更新服务器端以支持安全重协商
- 定期检查服务器是否已更新,以便及时移除这些临时解决方案
总结
UNSAFE_LEGACY_RENEGOTIATION_DISABLED
错误是由于客户端使用 OpenSSL 3.x 的安全标准与旧服务器不兼容导致的。通过创建自定义 SSL 上下文并设置适当的选项,可以临时解决此问题,但应注意相关的安全风险。
建议服务器管理员尽快更新服务器以支持安全重协商,这是最根本的解决方案。