一、前言

       ***之前下载python运行环境都依赖Windows系统的根证书出现了这个错误,报错就显示说aiohttp库尝试加载 Windows 系统根证书时,发现证书存储中存在数据不完整或格式损坏的证书,导致 SSL 上下文初始化失败。 ssl.SSLError: [ASN1: NOT_ENOUGH_DATA]

        这个报错是在项目下的虚拟环境下运行的,不知道是不是虚拟环境下OpenSSL 库本身损坏。于是打算在powershell终端的base环境下验证底层SSL是否正常、aiohttp是否能正常导入。下面是powershell的命令

# 切换到之前报错的虚拟环境
conda activate llm_conda

# 1. 测试底层SSL是否正常
python -c "import ssl; ssl.create_default_context(); print('SSL正常')"

# 2. 测试aiohttp是否能正常导入
python -c "import aiohttp; print('aiohttp版本:', aiohttp.__version__)"

        执行命令以后显示结果正常,但是这不能直接排除系统证书损坏的问题。因为两个环境的 Python 版本、OpenSSL 底层库版本可能完全不同,对证书格式的校验严格程度不一样。因此 base 环境正常不代表 虚拟环境环境也正常。

        于是我在虚拟环境里面继续执行在base里的两行测试命名(检测底层SSL是否正常、测试aiohttp是否能正常导入),最后发现果然是系统证书出现了问题。

第一条测试
第二条测试

        

        上面两张图片展示了 Windows 系统下 Anaconda 环境(llm_conda)中执行 Python 命令时出现的 SSL 相关错误。核心问题是 Python 无法正确加载 SSL 默认上下文(CA 证书),导致网络请求(如 aiohttp)失败。

        其中,ssl.create_default_context() 尝试加载系统默认的 CA 证书(用于验证 HTTPS 网站身份)。 NOT_ENOUGH_DATA 错误表示 CA 证书数据不完整或缺失,导致 SSL 握手无法完成。所有依赖 SSL 的 Python 库(如 requestsaiohttpurllib)都会受影响。

        

        ***除此以外,下载docker desktop遇见了WSL错误,导致出现docker乱码和 0xffffffff 错误。

        排查一圈后发现可能是安装了某些软件、杀毒软件或企业安全代理(如 Cisco AnyConnect, Zscaler)之后它们可能会修改系统证书存储;或者说中啥病毒了,之前也没有乱删除什么系统文件。

二、报错原因

2.1 第一次原因分析  

      datasets库 → aiohttp库 → ssl模块 → 加载Windows系统证书 → 发现损坏证书/格式不正确 → 抛出ASN1解析错误。所以任何使用aiohttp/requests的Python库都会出现这个错误。

        Python 的ssl模块会批量读取所有系统根证书,只要有一个证书格式损坏、ASN.1 结构解析失败,就会整体报错,导致aiohttprequests等所有依赖 SSL 的库都无法导入。

2..2 最终原因

        根本原因就是新旧环境的 OpenSSL 版本对畸形证书的容忍度不同。

        certifi 元数据损坏:

        site-packages 下同时存在 3 个 certifi-*.dist-info 目录( 2026.4.22 / 2026.6.17 / 2026.7.22 ),其中一个 METADATA 缺失,导致 pip list 显示 certifi None

        Windows 证书存储 + OpenSSL 3.5.7 冲突:

        Windows 系统证书存储(ROOT/CA)里有 1 条损坏的证书数据 (ASN.1 结构不完整)。新建环境没问题是因为它用 OpenSSL 3.5.6 ,对损坏证书容忍度更高/跳过;而 旧环境 用的 OpenSSL 3.5.7 解析更严格,在 ssl._load_windows_store_certs() 读取这条坏证书时直接抛 [ASN1: NOT_ENOUGH_DATA] ,整个 create_default_context() 崩溃,连带着所有 urllib 、 http.client 的默认 HTTPS 请求全部失败。

三、解决方案

1.docker乱码和 0xffffffff 错误

        我遇到的是最经典的旧版 WSL 问题--version参数是WSL 2.0 + 版本才有的,你的系统里安装的是 Windows 自带的老旧 "收件箱" 版 WSL 1,这也是 Docker Desktop 报错的根本原因(新版 Docker 只支持 WSL 2)。

(1)卸载现有内核,重新安装WSL内核即可。

# 直接在管理员PowerShell中执行以下命令自动下载安装
#执行这个关键命令,直接从微软官网下载最新 WSL 2(跳过 Microsoft Store):
wsl --update --web-download

#等待下载安装完成,会提示 "已成功更新适用于 Linux 的 Windows 子系统"
#必须重启电脑,这一步绝对不能跳过!

(2)等待安装完成后,再次测试:

#在powershell中输入
wsl --version

        此时应该能正常显示版本信息,没有乱码。

        成功标志:正常显示类似以下的版本信息(没有乱码,没有 "命令行选项无效")

WSL 版本: 2.3.24.0
内核版本: 5.15.153.1-2
WSLg 版本: 1.0.65
MSRDC 版本: 1.2.5620
Direct3D 版本: 1.611.1-81528511
DXCore 版本: 10.0.26091.1-240330-1435.ge-release
Windows 版本: 10.0.22631.3880

2.ssl系统证书错误

(1)换更稳定的 requests 库来替代 aiohttp库 (一个异步 HTTP 库),这样可以完全避开对 aiohttp 的依赖,从而绕过这个 SSL 错误。这里以调用搜索工具查天气为例子。

from langgraph.prebuilt import ToolNode
from langgraph.graph import StateGraph, MessagesState, START, END
from langchain.chat_models import init_chat_model
# 1. 删除这行:from langchain_tavily import TavilySearch
import requests
import os
from dotenv import load_dotenv
from langchain.tools import tool

load_dotenv()

llm = init_chat_model(api_key=os.getenv("DASHSCOPE_API_KEY"),
                      base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
                      model_provider="openai",
                      model='MiniMax-M2.1')

# 2. 使用 @tool 自定义工具,直接请求 Tavily API
@tool
def tavily_search_tool(query: str) -> str:
    """这是一个搜索工具"""
    api_key = os.getenv("TAVILY_API_KEY")
    if not api_key:
        return "Error: TAVILY_API_KEY not found in environment variables."
    
    url = "https://api.tavily.com/search"
    payload = {
        "api_key": api_key,
        "query": query,
        "search_depth": "basic",
        "max_results": 5
    }
    try:
        response = requests.post(url, json=payload)
        response.raise_for_status()
        data = response.json()
        # 这里简单返回结果,也可以根据 data['answer'] 或 data['results'] 进行格式化
        return str(data)
    except Exception as e:
        return f"Error occurred during search: {str(e)}"

tool_node = ToolNode([tavily_search_tool])
model_with_tools = llm.bind_tools([tavily_search_tool])

def should_continue(state: MessagesState):
    messages = state["messages"]
    last_message = messages[-1]
    if last_message.tool_calls:
        return "tools"
    return END

def call_model(state: MessagesState):
    messages = state["messages"]
    response = model_with_tools.invoke(messages)
    return {"messages": [response]}

builder = StateGraph(MessagesState)
builder.add_node("call_model", call_model)
builder.add_node("tools", tool_node)
builder.add_edge(START, "call_model")
builder.add_conditional_edges("call_model", should_continue, ["tools", END])
builder.add_edge("tools", "call_model")
graph = builder.compile()

print(graph.invoke({"messages": [{"role": "user", "content": "明天北京的天气如何?"}]}))

(2)直接删除高频损坏证书,再让系统自动更新恢复干净证书(根治)

        这个操作不太建议,删错风险很大,不建议操作。并且新增证书的话也可能安装到不那么正规的证书,风险很大。

(3)新建虚拟环境

        试了一下,成功了。新虚拟环境正常、旧环境报错, 那就是旧环境独有的配置 / 证书文件干扰,或者版本/文件损坏造成的干扰。

        不放心又去测试了第二遍,下面是SSL测试代码。        

import ssl
import urllib.request
import sys

def test_ssl():
    print(f"当前使用的 Python 路径: {sys.executable}\n")
    
    # 1. 测试 ssl 模块基础信息和证书路径
    print("1. SSL 模块基础信息:")
    print(f"   OpenSSL 版本: {ssl.OPENSSL_VERSION}")
    
    # 2. 尝试发起真实的 HTTPS 请求(访问百度,国内响应最快)
    url = "https://www.baidu.com"
    print(f"\n2. 尝试发起 HTTPS 请求: {url}")
    
    try:
        # 创建一个默认的 SSL 上下文(会加载系统证书库)
        context = ssl.create_default_context()
        print("   SSL 上下文创建成功(说明证书库读取没有崩溃)...")
        
        # 发起请求
        req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
        response = urllib.request.urlopen(req, timeout=5, context=context)
        
        print(f"\n   成功!服务器返回状态码: {response.status}")
        print("   SSL 模块和系统证书均工作正常")
        
    except urllib.error.URLError as e:
        print(f"\n   失败!网络请求报错:")
        print(f"   错误详情: {e.reason}")
    except Exception as e:
        print(f"\n   失败!发生未知错误:")
        print(f"   错误详情: {e}")

if __name__ == "__main__":
    test_ssl()

        下面是SSL模块和系统证书正常的输出

        

(4)通过 certifi 绕过 Windows 系统证书

        既然 Windows 自带的证书库靠不住,我们就彻底不让 Python 去碰它,而是改用 certifi 自带的一套纯净、跨平台、由 Mozilla 维护的第三方证书库。

        不过,因为使用的是 Mozilla 维护的公共证书库,如果公司内网使用了自签发的内部 HTTPS 证书,通过这种方式访问内网可能会被拦截,需要手动将内网证书加入 certifi 的 cacert.pem 中。

# ===== 环境修复(必须在 agents 相关 import 之前)=====
import os
import ssl
import certifi
from pathlib import Path
from dotenv import load_dotenv

# 修复 Windows 证书存储 ASN1 错误:aiohttp 在模块加载时调用
# ssl.create_default_context() -> _load_windows_store_certs(),当 Windows
# 证书存储中存在 ASN1 数据不完整的证书时会抛出 SSLError,导致 aiohttp
# 无法导入,进而级联让 dashscope / sentence_transformers 全部不可用。
# 这里吞掉该异常,改用 certifi 提供的根证书。
_orig_load_certs = ssl.SSLContext._load_windows_store_certs
def _safe_load_certs(self, *args, **kwargs):
    try:
        return _orig_load_certs(self, *args, **kwargs)
    except ssl.SSLError:
        try:
            self.load_verify_locations(cafile=certifi.where())
        except Exception:
            pass
ssl.SSLContext._load_windows_store_certs = _safe_load_certs

# 从项目根目录的 .env 文件加载配置(EMBED_* / DASHSCOPE_* / QDRANT_* 等已配置)
# 脚本位于 A/B/C/,.env 在上三级 A/ 根目录
_env_path = Path(__file__).resolve().parent.parent.parent / ".env"
load_dotenv(_env_path)

四、小结

4.1 SSL握手 

      为什么客户端和服务器要进行SSL握手呢?什么是SSL握手?这就涉及到网络安全了,服务器和客户端进行通讯的时候要确保双方身份是真的,不是伪造的(进行身份验证)、传输的数据加密过,就算被第三方看到也看不懂传输了什么数据(协商加密)、以及传输中途,数据没有被黑客偷偷掉包(数据完整)。

        这跟SSL模块有什么关系呢?

        SSL 模块本身是一套完整的“安全通信协议(规则与流程)。它本身并不从头编写加密算法(那些复杂的数学运算通常由底层的 C 语言库,比如 OpenSSL 来完成)。

       SSL作用是给 Python 开发者提供了简单易用的函数接口(比如截图里的 ssl.create_default_context();它自动执行代码,帮你管理证书、协商加密算法、校验数据完整性;把普通的、不安全的网络连接(Socket),包裹上一层“安全装甲”,变成安全的 SSL Socket。

        而查看系统证书就是SSL在进行身份验证,如果你的电脑的系统证书是全球公认的、权威的“发证机关”(CA机构)的印章和签名样本(根证书),并且没过期,或者证书数据没有缺失或读取失败身份才是合格的。

        我这里是 Python 在读取本地系统证书库时,发现数据缺失或读取失败,导致无法进行身份验证,所以报错。        

        网址里的 https 比 http 多出来的那个 s(Secure/安全),就是靠这个握手过程来实现的。

4.2 依赖、环境报错简单思路

        最简单的思路是换个环境,等有空了再去排查细节问题,这样效率才高,不然主任务会被拖累。

更多推荐