SSL错误,不安全的传统重新协商已禁用。

67
我正在运行一个Python代码,其中我需要从HTTPSConnectionPool(host='ssd.jpl.nasa.gov', port=443)获取一些数据。但是每次我尝试运行代码时都会出现以下错误。我使用的是MAC OS 12.1。
raise SSLError(e, request=request)
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)')))

我真的不知道如何绕过这个问题。
12个回答

115

警告:启用传统不安全重协商时,SSL连接将容易受到中间人前缀攻击的威胁,如CVE-2009-3555所述。

https://bugs.launchpad.net/bugs/1963834https://bugs.launchpad.net/ubuntu/+source/gnutls28/+bug/1856428的帮助下

请注意,不建议编辑系统的openssl.conf文件,因为一旦openssl更新,您可能会失去更改。

在任何目录中创建一个自定义的openssl.cnf文件,并包含以下内容:

openssl_conf = openssl_init

[openssl_init]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
Options = UnsafeLegacyRenegotiation

在运行程序之前,请确保您的 OPENSSL_CONF 环境变量已经设置为您自定义的 openssl.cnf 完整路径,像这样运行爬虫:
OPENSSL_CONF=/path/to/custom/openssl.cnf python your_scraper.py

或者像这样:

export OPENSSL_CONF=/path/to/custom/openssl.cnf
python your_scraper.py

或者,如果您正在使用pipenv、systemd或docker,请将此内容放入您的.env文件中

OPENSSL_CONF=/path/to/custom/openssl.cnf

2
这对我也起作用,尽管我不完全理解我在做什么。(只是尝试获取TD Ameritrade股票。在升级到22.04之前运行良好)。要编辑的文件是修改现有的openssl配置文件,路径:/usr/lib/ssl/openssl.cnf - Chad
@Chad 不建议更改系统默认设置,因为它们可能会被软件包更改覆盖。这种情况发生在每个人身上。有一种更清晰的方法,我正在编辑答案以发布一个可行的示例。 - nurettin
对我来说起作用了...我在使用Ubuntu 22.04(openssl 3.0.2)时遇到了问题,它默认使用Python 3.10.6。我无法使用nltk.download()下载语料库,因为源代码似乎使用了旧的函数...我得到了这个错误[nltk_data] Error loading stopwords: <urlopen error [SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] - Rafael

37

完整的代码段落,基于Harry Mallon提供的答案:

定义一个可重复使用的方法:

import requests
import urllib3
import ssl


class CustomHttpAdapter (requests.adapters.HTTPAdapter):
    # "Transport adapter" that allows us to use custom 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
然后将它用于代替requests调用:
get_legacy_session().get("some-url")

2
不错!我现在只需使用 with (get_legacy_session() as s, s.get("some-url") as response) 就可以了。这对于在 Google Cloud 上部署非常有用(因为我无法在此处降级 SSL)。 - Jeroen Vermunt
1
无法工作 - Dean Chen

33

我在Linux上遇到了同样的错误(当服务器不支持"RFC 5746安全重新协商",而客户端使用OpenSSL 3时会发生这种情况,默认情况下它强制执行该标准)。

这里有一个解决方案(你可能需要稍微调整一下)。

  1. 在Python代码中导入sslurllib3
  2. 创建一个自定义的HttpAdapter,它使用自定义的sslContext
class CustomHttpAdapter (requests.adapters.HTTPAdapter):
    '''Transport adapter" that allows us to use custom 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)
  1. 建立一个ssl上下文,启用OP_LEGACY_SERVER_CONNECT,并将其与您的自定义适配器一起使用。

ssl.OP_LEGACY_SERVER_CONNECT目前在Python中还不可用(https://bugs.python.org/issue44888)。但是事实证明,在OpenSSL中它的值为0x4。因此我们可以执行以下操作。

ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ctx.options |= 0x4
session.mount('https://', CustomHttpAdapter(ctx))

2
sessionrequests.Session() 的一个实例。我的代码片段不完整,因为它们是从一个更大的项目中提取出来的。您需要根据您的代码进行调整。 - Harry Mallon
1
这会导致:ValueError: 当启用check_hostname时,无法将verify_mode设置为CERT_NONE。只需添加以下内容,它就应该可以运行了
ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE
- laol
3
这个方法是有效的,问题是:如果我使用这个解决方案,我是否容易受到CVE-2009-3555中描述的中间人前缀攻击的影响? - undefined
1
这个方法是有效的,问题是:如果我使用这个解决方案,我是否容易受到CVE-2009-3555中描述的中间人前缀攻击的影响? - Nachengue
这是我唯一有效的解决方法。 - Daniel Vilas-Boas
显示剩余5条评论

16

当使用OpenSSL 3连接到不支持它的服务器时,会出现此错误。 解决方法是降级Python中的加密包:

在使用的环境中运行pip install cryptography==36.0.2

来源:https://github.com/scrapy/scrapy/issues/5491

编辑:请参考Hally Mallon和ahmkara的答案以获得无需降级cryptography的解决方法。


7
对我来说没有用,以及 api.searchads.apple.com - mastisa
2
我在使用Python requests 2.28.1 和 cryptography 37.0.2 的过程中遇到了相同的问题。我将 cryptography 降级到了36.0.2,问题得以解决。非常感谢:X - ali reza
9
尝试在Ubuntu 22.04上使用Python3.8、3.9、3.10,但没有成功。 - Martin Hansen
2
这个方法对我也没用。 - troymyname00
这并不完全准确 - 它不是OpenSSL 3,而是一个支持TLS 1.2但不支持RFC 5746安全重协商扩展或TLS 1.3的服务器或篡改代理。然而,这是一个危险的建议,因为使用旧版本的加密意味着您没有修补安全漏洞。 - Chris Adams

6

这并没有真正回答问题,但是一位同事从Node 18切换到16后,不再出现这个错误。


5
如果你想使用urlopen,这段代码对我很有用。
import ssl
import urllib.request

url = 'http://....'

# Set up SSL context to allow legacy TLS versions
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ctx.options |= 0x4  # OP_LEGACY_SERVER_CONNECT

# Use urllib to open the URL and read the content
response = urllib.request.urlopen(url, context=ctx)

1

要在Ruby中解决同样的问题,您可以执行以下操作:

# Set OP_LEGACY_SERVER_CONNECT option
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_LEGACY_SERVER_CONNECT

# Make a request
uri = URI('https://example.com')
res = Net::HTTP.post(uri, {}.to_json)

# Unset OP_LEGACY_SERVER_CONNECT option
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] &= ~OpenSSL::SSL::OP_LEGACY_SERVER_CONNECT

0
如果您正在使用conda,通常conda会为每个环境安装一个新的openssl可执行文件。一个简单的解决方法是通过以下命令将您的openssl降级到1.0版本(与您的环境一起运行)。
conda install -n conda-env-name openssl=1

或者找到您特定的conda环境中openssl配置文件的位置,并按照Jack Lee的答案进行操作。
您需要密切关注此网站上的SSL版本,以确保指定正确的渠道。https://anaconda.org/conda-forge/openssl/labels

0
现在我解决了这个问题 - [SSL错误:不安全的旧式重新协商已禁用] 或者(由SSLError引起(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1007)'))) 参考链接如下:但它没有被使用,只是为了理解问题
SSL错误:不安全的旧式重新协商已禁用) 使用以下代码:
import urllib.request
import ssl

# Create a secure SSL context
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS)  # Use the appropriate protocol

url = "https://example.com"  # Replace with your URL
response = urllib.request.urlopen(url, context=ssl_context)

# Read and process the response
data = response.read().decode("utf-8")

如果你需要,请申请,谢谢。

0
这个帖子上有很多答案。虽然没有一个完全满足我的需求,但我想我也可以贡献我的解决方案。希望其他人也会觉得有价值。
我的设置:
我在Ubuntu 22.04.2上运行Python 3.10。我使用aiohttp来实现异步的HTTP请求。
我正在对我内部局域网上的一台硬件进行HTTP请求,但我无法更新这台硬件以停止使用不安全的SSL重协商。
我的方法:
我创建了一个自定义的SSL上下文,然后传入了SSL OP标志“SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION”。我从这里获取了这个标志:SSL OP标志列表
custom_ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
custom_ssl_context.options |= 0x00040000 # OP flag 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()

如果您还需要禁用SSL验证(例如在开发测试中),您可以将以下两行代码添加到您的custom_ssl_context中:
custom_ssl_context.check_hostname = False
custom_ssl_context.verify_mode = ssl.CERT_NONE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接