SSL: CERTIFICATE_VERIFY_FAILED 问题
使用python的过程中,在发送网络请求时有时候会遇到如下问题:
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
这个问题产生原因是python发送请求的网站或地址是https,这时需要验证对方网站的证书有效性。 但python使用的证书存储位置里找不到该网站的证书。
在任何发送http请求的地方都可能遇到,包括python内置库: ssl, urllib3, 以及一些第三方库:requests, httpx, aiohttp, 甚至包管理工具: pip, poetry 等。
certifi: 一个第三方模块,提供了Mozilla trust store
里的根证书集合。使用pip install --upgrade certifi
命令对这个模块更新时,就等同于更新了最新的证书集合。很多其他模块都依赖于certifi模块。这个模块一般也会随其他模块安装时被一起安装,比如requests模块。
certifi.where()
调用这个方法将返回certifi保存的根证书的存储路径。
pip命令行工具:这是安装python就自带的,甚至创建虚拟环境也会有独立的pip工具。每个pip工具都内置了一个certifi,注意,pip内置的certifi虽然与单独安装的certifi模块功能一样,但其只被pip命令工具自己用。其内置的证书存储也只能在pip工具自已更新时才更新 python -m pip install --upgrade pip
。 pip命令本身可能通过--cert <path>
参数来指定一个证书存储,也可以通过设置PIP_CERT环境变量来设置证书存储。在v22.2到24.2之前的这些版本,可以使用--use-feature=truststore
参数来指定使用系统证书存储。从v24.2版本开始,pip自动同时使用certifi及操作系统的证书存储。此外,当确实无法验证对方证书有效性时,可以使用--trusted-host <hostname>
参数来信息对方,从而略过证书验证的步骤。
truststore: 第三方模块,用于将操作系统的证书存储位置暴露出来。 pip工具从v24.2版本开始内置了这个模块,使用pip命令可以同时使用内置的certifi以及操作系统的证书存储。通过其truststore.inject_into_ssl()
方法,可以让那些原本使用certifi证书存储的第三方库转而使用操作系统的证书存储。
可以使用truststore.SSLContext()
方法达到更细粒度的控制,比如对单个请求。truststore.inject_into_ssl()
方法并不适合用于构建package,因为一旦这样的package被import到其他项目时,项目运行的整个环境里原本该依赖certifi证书存储的程序部分就都会转而使用操作系统的证书存储了。 所以,应该更多考``虑细粒度的控制。全局的修改适合单个项目或独立运行的脚本,而不是用于共享的模块。
import ssl
import urllib3
import truststore
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
http = urllib3.PoolManager(ssl_context=ctx)
resp = http.request("GET", "https://example.com")
poetry命令行工具: 也是包管理工具,比pip更好,一般应该独立安装在一个虚拟环境中,或直接安装在python主环境里。poetry依赖requests,所以证书验证机制应该与requests库相同,即默认使用certifi库的证书存储。受环境变量 REQUESTS_CA_BUNDLE
和 SSL_CERT_FILE
的影响。
ssl: python内置模块,偏底层,提供了对 SSL/TLS 加密协议的支持,验证对方的证书以确保安全通信。直接使用该模块的方法时,可以手动设置证书存储,也可以使其查找操作系统的证书存储。urllib3模块依赖ssl模块。ssl模块又依赖于openSSL库。使用环境变量 SSL_CERT_FILE
和 SSL_CERT_DIR
。 这里要注意的一点是,windows上的python自带openSSL库,一般在python主环境的DLLS目录下,有 libcrypto-1_1.dll 和 libssl-1_1.dll 两个动态链接库。 而在linux系统上,python使用linux系统安装的openSSL。windows系统并不默认自带openSSL库。再扩展一点,windows系统本身虽不内置OpenSSL,但其有内置的SSL/TLS加密及证书验证的实现,像IE浏览器及其他windows自带的需要网络功能的程序都使用windows内置的这些ssl功能。 而像一些跨平台应用,比如chrome,firefox浏览器,他们也都自已集成了各自的ssl实现,相当于自己维护的OpenSSL库。 另一些程序,他们要么选择使用操作系统的ssl实现,要么使用第三方的OpenSSL库。
urllib: python内置模块,不如urllib3灵活和高级。内部依赖python的内置模块ssl。
urllib3: 是一个功能丰富的 HTTP 客户端库,是第三方模块,支持多种高级网络功能,如连接池、重试、代理、SSL证书验证和文件流处理。 依赖ssl模块。requests模块则依赖它。使用环境变量 CURL_CA_BUNDLE
。
requests: 是一个功能强大且易于使用的 HTTP 客户端库, 主要是对urllib3又进行了封装,使用起来更简洁。 依赖urllib3、ssl、certifi模块。证书存储默认使用certifi模块的存储。使用环境变量 REQUESTS_CA_BUNDLE
,如果该变量没配置,将会受到 CURL_CA_BUNDLE
变量的影响。在v2.16之前,requests内部集成 Mozilla trust store提供的证书集合,但只有随着requests版本更新时,该证书集合才有机会更新。在v2.16及之后的版本,requests默认使用certifi提供的证书存储。
aiohttp: 是一个流行的异步 HTTP 客户端/服务器框架。 其内部并不依赖certifi。默认行为与ssl模块一致,使用操作系统证书存储,所以也受环境变量 SSL_CERT_FILE
和 SSL_CERT_DIR
的影响。
httpx: 是一个比较新HTTP客户端库,即支持同步调用,又支持异步调用。依赖certifi模块。 默认使用certifi的证书存储。
再举个数据库连接的例子:
pymysql: 用于连接mysql数据库,可以启用ssl,但没有默认使用的证书存储,必须用参数手动指定。
sqlalchemy: ODM模块(objet-data-mapping),也支持ssl, 也没有默认使用的证书存储,也是通过参数指定证书存储,最终也是传递给像pymsql这种模块来最终连接数据库。
其他补充:
pip从v24.2开始集成truststore来获取操作系统证书存储,前面说python内置的ssl模块就有能力获取操作系统证书存储,为什么又搞出一个truststore呢? 原因如下:
- 跨平台一致性:
ssl 模块在不同操作系统上的行为可能略有不同。
truststore 提供了一个更一致的跨平台接口来访问系统证书。 - 更细粒度的控制:
truststore 允许更精细的控制over如何访问和使用系统证书。 - 性能优化:
truststore 可能提供了更高效的方式来访问系统证书,特别是在 Windows 上。 - 更新和维护:
作为一个独立的库,truststore 可以独立于 Python 核心更快地更新和改进。
两种证书存储的选择:
操作系统证书存储:
优点:
- 自动更新:随操作系统更新而更新。
- 本地化:可能包含特定于组织或地区的证书。
缺点:
- 跨平台不一致:不同操作系统的证书存储可能不同。
- 可能被本地策略修改:可能受到组织 IT 策略的影响。
certifi 证书存储:
优点:
- 跨平台一致性:在所有平台上提供相同的证书集。
- 独立更新:可以独立于操作系统更新。
缺点:
- 可能不包含某些本地或特定组织的证书。
- 需要定期更新 certifi 包。
如何选择:
- 跨平台应用:优先使用 certifi,确保一致性。
- 本地化应用:可能更适合使用操作系统证书存储。
- 高安全性要求:考虑同时使用两者,获得更广泛的证书覆盖。
- 企业环境:可能需要使用操作系统存储来包含内部 CA 证书。
同样的python环境,都是windows系统,一台公司电脑,一台家用电脑。 两台电脑都连接到家里wifi,且公司电脑并没有连接vpn。 在这种情况下,使用pip或poetry命令安装第三方包时,无论是使用官方源(pypi),还是换成国内镜像(清华源),公司电脑都提示 SSL: CERTIFICATE_VERIFY_FAILED 问题,而家用电脑都能正常安装。 经过对比,两边pip命令工具以及poetry工具都使用的是相同的版本。 除此之外,也尝试将公司电脑的certifi模块更新到了最新,但问题仍然存在。 最后,未尝试修改公司电脑环境变量,比如 REQUESTS_CA_BUNDLE
和 SSL_CERT_FILE
。 后续会修改后再试。 AI提醒可能是公司电脑防火墙问题,或者是使用了SSL代理。
以前版本的pip还有一种现像,就是在FQ时会下载包失败,关闭代理才能正常下载。之前知乎上有一贴子调查的挺深入的,现在没找到。