urllib.request.urlopen:SSL:CERTIFICATE_VERIFY_FAILED Error on Windows>=Vista(7/8/10/Server 2008)(Python>=3.4上)

2024-03-28 15:13:49 发布

您现在位置:Python中文网/ 问答频道 /正文

尝试在最近的(gt;=Vista)Windows机器上的许多HTTPS站点上使用python3urlopen时,我在许多站点上尝试执行urllib.request.urlopen时出现“SSL:CERTIFICATE_VERIFY_FAILED”错误(在某些构建计算机上,甚至是https://www.google.com/,但奇怪的是,从未在https://www.microsoft.com/上)。在

>>> import urllib.request
>>> urllib.request.urlopen("https://www.google.com/")
Traceback (most recent call last):
  File "C:\Python35\lib\urllib\request.py", line 1254, in do_open
    h.request(req.get_method(), req.selector, req.data, headers)
  File "C:\Python35\lib\http\client.py", line 1106, in request
    self._send_request(method, url, body, headers)
  File "C:\Python35\lib\http\client.py", line 1151, in _send_request
    self.endheaders(body)
  File "C:\Python35\lib\http\client.py", line 1102, in endheaders
    self._send_output(message_body)
  File "C:\Python35\lib\http\client.py", line 934, in _send_output
    self.send(msg)
  File "C:\Python35\lib\http\client.py", line 877, in send
    self.connect()
  File "C:\Python35\lib\http\client.py", line 1260, in connect
    server_hostname=server_hostname)
  File "C:\Python35\lib\ssl.py", line 377, in wrap_socket
    _context=self)
  File "C:\Python35\lib\ssl.py", line 752, in __init__
    self.do_handshake()
  File "C:\Python35\lib\ssl.py", line 988, in do_handshake
    self._sslobj.do_handshake()
  File "C:\Python35\lib\ssl.py", line 633, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c
:645)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python35\lib\urllib\request.py", line 163, in urlopen
    return opener.open(url, data, timeout)
  File "C:\Python35\lib\urllib\request.py", line 466, in open
    response = self._open(req, data)
  File "C:\Python35\lib\urllib\request.py", line 484, in _open
    '_open', req)
  File "C:\Python35\lib\urllib\request.py", line 444, in _call_chain
    result = func(*args)
  File "C:\Python35\lib\urllib\request.py", line 1297, in https_open
    context=self._context, check_hostname=self._check_hostname)
  File "C:\Python35\lib\urllib\request.py", line 1256, in do_open
    raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certifica
te verify failed (_ssl.c:645)>

最令人恼火的是,这种情况几乎只发生在构建/CI服务器上,而且这些错误通常在尝试调查问题后消失(例如,检查与给定站点的连接,当通过浏览器尝试时,该站点会正确响应):

^{pr2}$

我听到了很多关于通过破坏SSL上下文来禁用证书验证的建议,但是我想避免这种情况——我想保持我的HTTPS安全性不变!在

这个问题的原因是什么?我怎样才能修好它?在


Tags: inpyselfclientsendhttpsslrequest
1条回答
网友
1楼 · 发布于 2024-03-28 15:13:49

不幸的是,这是一个没有幸福结局的悲伤故事,在https://bugs.python.org/issue20916中有详细说明。在

Python3.3将cadefault参数添加到urllib.request.urlopen,默认为Truehttps://bugs.python.org/issue14780),这使得HTTPS请求在默认情况下使用系统证书存储来验证服务器证书。在

python3.4使SSLContext.set_default_verify_paths可以在Windows(https://bugs.python.org/issue19292)上工作,使Python能够使用Windows证书存储。在

以前,Microsoft通过Windows Update推送根证书更新,这确保系统根证书存储始终更新(只要用户安装了更新)。到目前为止,还不错。在

但是,由于Windows Vista,Windows在存储区中只捆绑了几个“核心”证书(少于20个,IIRC),每当CryptoAPI被要求验证在本地存储中找不到可信根的证书时,就会联系Microsoft服务器以检查它们是否有可信的根。如果是,则提供根证书并自动安装到系统证书存储中。在

不幸的是,Python不使用Windows CryptoAPI,因此它无法从这种自动机制中获益;相反,它要求系统证书存储中的所有证书并尝试使用它们-但这意味着它得到的只是Windows附带的少数证书,即手动安装的证书,plus发生的已自动安装的所有证书,通常在使用Internet Explorer或Edge浏览Internet时安装。在

这使得这个问题变得特别隐秘,因为不同机器之间会出现不同的问题(主要取决于其浏览历史记录),如果检查是否可以使用Windows CryptoAPI通过浏览器连接到该站点,则通常会消失(对于该站点,以及来自同一根证书的所有站点)。由于这个原因,新的Windows安装、构建的机器和服务器(它们看不到多少交互式的互联网浏览)特别容易受到这个问题的影响,而开发人员在他们的“普通”桌面计算机上可能永远不会遇到这个问题。在


怎么解决这个问题?不幸的是,没有简单的解决办法。在

  • 对于一些简单的情况,例如CI服务器,其中一些测试需要访问一些几乎永远不会更改的特定域,一个简单的解决方法是打开internetexplorer并在这些域上打开一个页面。这将使它将所需的根证书获取到本地证书存储,Python在过期之前不会有问题(注意:我们这里讨论的是root证书,它的持续时间通常为多年)
  • 您可以禁用证书验证;这已经在许多不同的答案中讨论过,such as this。然而,这通常是不可取的,因为您正在放弃SSL提供的MITM保护
  • 您可以手动将所有当前受信任的根证书安装到Windows证书存储;here is a site that explains how(免责声明:解释的过程看起来很合理,但我从未尝试过);不幸的是,这是一个手动过程,您需要定期重复以确保获得新的根证书
  • 您可以安装certifi包,它提供了自己的证书存储(IIRC它是Mozilla证书存储的副本);然后可以这样使用它:

    import certifi
    import urllib.request
    r = urllib.request.urlopen(url_website, cafile=certifi.where())
    

    这就是流行的requests模块所走的路,它确实可以“开箱即用”地工作;不幸的是,这是另一个证书存储,必须保持更新,因此您必须确保通过pip定期更新certifi包,或者以何种方式安装它。


感谢this blog article一书的作者,这是我第一次找到e正确地解释了这个问题。

相关问题 更多 >