ssl.SSLError: tlsv1 alert protocol version

45

我正在为思科CMX设备使用REST API,尝试编写Python代码以通过API进行信息的GET请求。以下是代码,与文件中的内容相同,只是必要的信息已更改。

from http.client import HTTPSConnection
from base64 import b64encode


# Create HTTPS connection
c = HTTPSConnection("0.0.0.0")

# encode as Base64
# decode to ascii (python3 stores as byte string, need to pass as ascii 
value for auth)
username_password = b64encode(b"admin:password").decode("ascii")
headers = {'Authorization': 'Basic {0}'.format(username_password)}

# connect and ask for resource
c.request('GET', '/api/config/v1/aaa/users', headers=headers)

# response
res = c.getresponse()

data = res.read()

然而,我不断地遇到以下错误:

Traceback (most recent call last):
  File "/Users/finaris/PycharmProjects/test/test/test.py", line 14, in <module>
    c.request('GET', '/api/config/v1/aaa/users', headers=headers)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/http/client.py", line 1106, in request
    self._send_request(method, url, body, headers)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/http/client.py", line 1151, in _send_request
    self.endheaders(body)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/http/client.py", line 1102, in endheaders
    self._send_output(message_body)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/http/client.py", line 934, in _send_output
    self.send(msg)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/http/client.py", line 877, in send
    self.connect()
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/http/client.py", line 1260, in connect
    server_hostname=server_hostname)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/ssl.py", line 377, in wrap_socket
    _context=self)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/ssl.py", line 752, in __init__
    self.do_handshake()
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/ssl.py", line 988, in do_handshake
    self._sslobj.do_handshake()
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/ssl.py", line 633, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:645)

我也尝试更新OpenSSL,但没有任何效果。


1
请查看 https://github.com/CiscoDevNet/devnet-express-dna-issues/issues/16。看起来您的 Python 使用了旧版本的 OpenSSL。如果您使用的是 Mac,那么通常情况下它会附带一个非常老旧且过时的 OpenSSL 版本。 - Steffen Ullrich
我在Windows上为Wemos微控制器获取二进制工具时遇到了这个问题。原因是谷歌带我去了Python 2.7.3(旧版)的下载页面,我以为它是最新的......目前最新的版本是2.7.14,它不再有这个问题,而且可以自动工作。 - Feri
1
对于那些在所有 pip install 中遇到 [SSL: TLSV1_ALERT_PROTOCOL_VERSION] 错误的人来说,这是由于 Python.org 网站最近进行的 TLS 废弃所致。请参见 此答案 - Anupam
10个回答

71

我遇到了相同的错误,谷歌把我带到了这个问题,所以我分享一下我的做法,希望能帮助其他处于类似情况的人。

以下适用于OS X系统。

在终端中检查我安装的OpenSSL版本:

$ python3 -c "import ssl; print(ssl.OPENSSL_VERSION)"
>> OpenSSL 0.9.8zh 14 Jan 2016

由于我的OpenSSL版本过旧,所以接受的答案不起作用。

因此我必须更新OpenSSL。为了实现这一目的,我使用Homebrew将Python更新到最新版本(从3.5版本更新到3.6版本),并按照这里建议的步骤进行操作。

$ brew update
$ brew install openssl
$ brew install python3

接着,我遇到了PATH和Python版本的问题,因此我创建了一个新的virtualenv,确保使用最新版本的Python:

$ virtualenv webapp --python=python3.6

问题已解决。


3
接受的回答本身并没有完全涵盖此问题,但在评论中我间接提到需要更新OpenSSL。很抱歉这不是很清楚,感谢您的补充! - finaris
2
如果还有人遇到问题,这个链接对我非常有帮助:https://dev59.com/kGAf5IYBdhLWcg3wu0xD#46308535 - Naomi See
2
请参考下面的答案。我通过安装requests[security]解决了我的问题,而不使用Python3或使用brew安装openssl。 - James Lim
升级到3.6版本也解决了在Windows 10上的问题。谢谢。 - Amit Bhosle
你好,我无法升级到Python3,因为脚本需要Python2.7(来自cocos2dx的download-deps.py),有什么建议吗? - Zennichimaro
这是我找到的唯一方法,抱歉。也许你想开一个新问题来解释这个问题。 - J0ANMM

38
唯一需要做的事情就是在你的虚拟环境中安装requests[security]。你不必使用Python 3(它应该可以在Python 2.7中运行)。此外,如果你正在使用最新版本的macOS,则不必使用homebrew单独安装OpenSSL。
$ virtualenv --python=/usr/bin/python tempenv  # uses system python
$ . tempenv/bin/activate
$ pip install requests
$ python
>>> import ssl
>>> ssl.OPENSSL_VERSION
'OpenSSL 0.9.8zh 14 Jan 2016'  # this is the built-in openssl
>>> import requests
>>> requests.get('https://api.github.com/users/octocat/orgs')
requests.exceptions.SSLError: HTTPSConnectionPool(host='api.github.com', port=443): Max retries exceeded with url: /users/octocat/orgs (Caused by SSLError(SSLError(1, u'[SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:590)'),))
$ pip install 'requests[security]'
$ python  # install requests[security] and try again
>>> import requests
>>> requests.get('https://api.github.com/users/octocat/orgs')
<Response [200]>

requests[security] 允许请求在协商连接时使用最新版本的TLS。macOS内置的openssl支持TLS v1.2。

在安装自己的OpenSSL之前,请考虑这个问题:Google Chrome是如何加载https://github.com的?


James,你的MacOS版本是什么(sw_vers)?xmlrpclib -> httplib -> ssl对你是否有效,例如在searching-pypi-by-topic中使用pypi = ... https: ...而不是http - denis
@denis,操作系统版本并不像OpenSSL版本那样重要。你可以使用ssl.OPENSSL_VERSION获取OpenSSL版本信息。 - James Lim
pip install 'requests[security]' 完成了任务,谢谢。 - naccode
谢谢,这解决了我在本地使用多个Python版本进行测试时遇到的所有问题。 - Fabio Caccamo

16

我相信TLSV1_ALERT_PROTOCOL_VERSION是在警告您,服务器不想和您使用TLS v1.0进行通信。尝试通过添加以下代码来指定仅使用TLS v1.2:

import ssl
from http.client import HTTPSConnection
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)

# Create HTTPS connection
c = HTTPSConnection("0.0.0.0", context=context)

请注意,您可能需要足够新的Python版本(例如2.7.9+)以及可能需要OpenSSL(我使用的是“OpenSSL 1.0.2k 26 Jan 2017”并且以上方法似乎可行,但结果可能因人而异)


1
你有OpenSSL 0.9.8,支持TLS 1.2吗?我非常怀疑:因为只有从OpenSSL 1.0.1开始才支持TLS 1.2。 - Steffen Ullrich
TLS 1.2在OpenSSL 0.9.8上无法工作,但我将解释器更改为具有OpenSSL 1.0.2k的解释器,TLS 1.2可以正常工作(因此API调用成功)。 - finaris
1
@SteffenUllrich 啊,是的,执行 python -c 'import ssl; print(ssl.OPENSSL_VERSION)' 确认了我的(Homebrew 安装的)Python 已与“OpenSSL 1.0.2k 26 Jan 2017”链接。我展示的版本字符串“0.9.8zh”是运行“openssl version”得到的,这必须是我较旧的系统范围安装版本。 - Josh Kupershmidt

11

没有一个被接受的答案能指引我朝正确的方向前进,而且这个问题还是搜索该主题时出现的,所以这是我(部分地)成功的传奇。

背景:我在Beaglebone Black上运行一个Python脚本,使用python-poloniex库轮询加密货币交易所Poloniex。 它突然出现了TLSV1_ALERT_PROTOCOL_VERSION错误而停止工作。

结果发现OpenSSL没问题,并试图强制进行v1.2连接是一次巨大的无意义的任务 - 该库将根据需要使用最新版本。 链中的薄弱环节实际上是Python,它仅定义了ssl.PROTOCOL_TLSv1_2,因此自版本3.4以来开始支持TLS v1.2。

与此同时,Beaglebone上的Debian版本认为Python 3.3是最新版本。 我使用的解决方法是从源代码安装Python 3.5(3.4也可能最终奏效,但经过数小时的尝试和错误后,我已经做完了):

sudo apt-get install build-essential checkinstall
sudo apt-get install libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev
wget https://www.python.org/ftp/python/3.5.4/Python-3.5.4.tgz
sudo tar xzf Python-3.5.4.tgz
cd Python-3.5.4
./configure
sudo make altinstall

也许并非所有这些软件包都是必要的,但一次性安装它们可以节省大量重试。使用altinstall可以防止安装覆盖现有的Python二进制文件,而以python3.5的形式进行安装,不过这意味着您需要重新安装附加库。使用./configure耗费了好几分钟时间。使用make操作需要几个小时。

即使如此,直到我最终执行以下命令之前,它仍然无法正常工作:

sudo -H pip3.5 install requests[security]

这也会安装 pyOpenSSL, cryptographyidna. 我怀疑 pyOpenSSL 是关键,所以也许只需要运行 pip3.5 install -U pyopenssl 就足够了,但我已经花费了太长时间去确认了。

总之,如果在Python中遇到 TLSV1_ALERT_PROTOCOL_VERSION 错误,很可能是因为你不支持TLS v1.2。要添加支持,你至少需要以下内容:

  • OpenSSL 1.0.1
  • Python 3.4
  • requests[security]

这使我摆脱了 TLSV1_ALERT_PROTOCOL_VERSION,但现在我又要面对 SSL23_GET_SERVER_HELLO 了。

事实证明,这又回到了 Python 选择错误的 SSL 版本的问题。可以通过使用这个技巧挂载一个带有 ssl_version=ssl.PROTOCOL_TLSv1_2 的请求会话来确认。如果不这样做,则会使用 SSLv23 并出现 SSL23_GET_SERVER_HELLO 错误。但是,如果这样做,请求将成功。

最后的战斗是在第三方库内部进行请求时强制选择 TLSv1_2。 这种方法这种方法都应该可以解决问题,但都没有任何效果。我的最终解决方案很可怕,但非常有效。我编辑了/usr/local/lib/python3.5/site-packages/urllib3/util/ssl_.py 并更改了:

def resolve_ssl_version(candidate):
    """
    like resolve_cert_reqs
    """
    if candidate is None:
        return PROTOCOL_SSLv23

    if isinstance(candidate, str):
        res = getattr(ssl, candidate, None)
        if res is None:
            res = getattr(ssl, 'PROTOCOL_' + candidate)
        return res

    return candidate

def resolve_ssl_version(candidate):
    """
    like resolve_cert_reqs
    """
    if candidate is None:
        return ssl.PROTOCOL_TLSv1_2

    if isinstance(candidate, str):
        res = getattr(ssl, candidate, None)
        if res is None:
            res = getattr(ssl, 'PROTOCOL_' + candidate)
        return res

    return candidate

那么,我的脚本现在终于可以再次联系服务器了。


2
谢谢@Heath!我确认我在运行Python2.7并尝试连接Poloniex API时遇到了同样的问题,只需运行pip install requests[security]即可解决问题。无需升级到Python3 :) 非常感谢! - Victor Grau Serrat

7

截至2018年7月,Pypi现在要求连接到其上的客户端使用TLS 1.2。如果你正在使用随MacOS(2.7.10)一起发送的python版本,则会出现问题,因为它仅支持TLS 1.0。您可以更改Python使用的ssl版本以解决此问题,或升级到更高版本的Python。使用Homebrew在默认库位置之外安装新版本的Python。

brew install python@2

你的回答真的救了我的一天。我能请你喝一杯吗?请私信我你的BTC地址。老实说,我现在有一个Python脚本在El Capitan下运行(我知道,我不应该再使用它,但在这种情况下我必须这样做),这让我取得了巨大的进步。谢谢! - Michael

6

针对 Mac OS X

1) 从官方 Python 语言网站https://www.python.org/downloads/下载本地应用程序安装程序,将 Python 更新至 3.6.5 版本。

我发现安装程序比 homebrew 更好地处理了新 Python 的链接和符号链接的更新。

2) 在刷新后的 Python 3.6 目录中使用 "./Install Certificates.command" 安装新证书。

> cd "/Applications/Python 3.6/"
> sudo "./Install Certificates.command"

2
起初,这对我没有用(在其他方法也没有用之后,所以我对使用Python 3的前景感到非常失望),但是sudo命令的初始失败结果之一建议我使用sudo的-H标志。这样做可以使更新顺利进行。 - zerowords

2

这个问题的另一个来源是:我发现在Debian 9中,Python httplib2 硬编码为坚持使用TLS v1.0。因此,任何使用 httplib2 连接到要求更高安全性的服务器的应用程序都会失败并显示 TLSV1_ALERT_PROTOCOL_VERSION

我通过更改以下内容来解决它:

context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)

to

context = ssl.SSLContext()

在 /usr/lib/python3/dist-packages/httplib2/__init__.py 中。

Debian 10没有这个问题。


这同样适用于Ubuntu 18.04 LTS。这里描述的相同修复方法有效。 - father_goose

0

我也遇到了这个问题。

  • 步骤1:使用brew重新安装python。现在你有了Python3.7而不是旧的Python。

  • 步骤2:基于Python3.7构建新的环境。我的路径是/usr/local/Cellar/python/3.7.2/bin/python3.7

现在,你将不会被这个问题打扰了。


0

我在使用Flask和flask_mqtt扩展时遇到了这个问题。解决方案是将以下内容添加到Python文件中:

app.config['MQTT_TLS_VERSION'] = ssl.PROTOCOL_TLSv1_2 

0

当我尝试运行gem install bundler时,我遇到了这个确切的问题,并且被所有Python的响应所困惑(因为我正在使用Ruby)。这是我的确切错误:

ERROR:  Could not find a valid gem 'bundler' (>= 0), here is why:
          Unable to download data from https://rubygems.org/ - SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: tlsv1 alert protocol version (https://rubygems.org/latest_specs.4.8.gz)

我的解决方案是:我将 Ruby 更新到最新版本(2.6.5)。问题得到解决。

我认为这可能是问题的更好答案:https://dev59.com/P2Ik5IYBdhLWcg3wl_Ip - entpnerd

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