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。它可以获取操作系统配置的代理: urllib.request.getproxies()
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提供的证书存储。
requests默认使用操作系统配置的代理,具体是从urllib.request.getproxies()
获取到的,但也可以像下面这样使用指定的代理:
import requests
proxies = {
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
}
requests.get('http://example.org', proxies=proxies)
如果没有像上面那些对单个请求显示指定代理,还可以通过设置环境变量来影响。 比如:
$ export HTTP_PROXY="http://10.10.1.10:3128"
$ export HTTPS_PROXY="http://10.10.1.10:1080"
$ export ALL_PROXY="socks5://10.10.1.10:3434"
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 核心更快地更新和改进。
关于代理服务
在使用Clash for Windows这种客户端运行时,其会将windows系统的代理服务器配置为127.0.0.1,端口:7890。 当使用requests模块时就会默认使用系统代理。因为requests模块内部会使用urllib.request.getproxies
来获取代理。
Clash 的工作原理:
当您在 Clash for Windows 中选择 "[视频] 台湾 01" 时,Clash 会将发往 127.0.0.1:7890 的请求转发到这个特定的服务器。
Clash 充当了一个本地代理服务器,它接收所有的请求,然后根据规则或您的选择将流量转发到适当的远程服务器。
也就是说,在windows系统上,不管是浏览器,还是python requests模块发出的网络请求,首先都会发送给windows代理服务器设置的这个地址和端口。 由于此时设置的是本机的7890端口,Clash在本机的这个端口接收到请求后,会再将其转发给真正的外部代理服务器。最终我们的网络请求就会被这些外部代理服务器转发出去。
那么如果我们用python requests模块写的程序想在没有安装Clash for Windows这种客户端的电脑上运行时,如何使用这些外部的代理服务器转发我们的网络请求呢? 这时我们可以想到简单的在调用requests时设置代理。但现在像代理服务器的协议一般是ss(Shadow敏感词Socks)或者Trojan,而requests模块是不支ss(Shadow敏感词Socks)和Trojan协议,这时就要使用PySocks模块进行个转换,这块的学习 未完待续。。。
两种证书存储的选择:
操作系统证书存储:
优点:
- 自动更新:随操作系统更新而更新。
- 本地化:可能包含特定于组织或地区的证书。
缺点:
- 跨平台不一致:不同操作系统的证书存储可能不同。
- 可能被本地策略修改:可能受到组织 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代理。这块的研究 未完待续。。。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~