Python 实战:从零构建后量子密码就绪性扫描器——为你的 TLS 端点做「量子体检」

实践教程 · 2026-07-11 · 15 阅读

前言

本文为《白宫后量子密码迁移行政令 EO 14412 深度解读》的配套实操指南。行政令要求联邦机构在 2026 年 9 月前完成高价值资产的量子影响清单——但对大多数机构来说,第一步甚至不知道自己的 HTTPS 端口是否已经支持后量子密码。

现有工具要么是重量级商业产品(、),要么是命令行工具()不适合批量扫描。本文向你展示如何用不到 200 行 Python 构建一个量子影响扫描器

PLAINTEXT
扫描目标:your-domain.com:443
├─ TLS 1.2 支持 ⚠️ 不安全 — 无 PQC 支持
├─ TLS 1.3 支持 ✓
│  ├─ X25519MLKEM768      ✓ 后量子密钥交换
│  ├─ X25519              ⚠️ 经典 ECDH
│  └─ P256                ⚠️ 经典 ECDH
├─ 证书链                ⚠️ RSA 2048(非 PQC)
└─ 整体评级              ⚠️ 部分就绪

环境准备

BASH
pip install ssl cryptography dnspython

需要的版本:

  • Python 3.10+
  • cryptography >= 42.0(支持 ML-KEM OID 查询)
  • dnspython >= 2.0

核心代码

1. TLS 握手探测:提取密码套件列表

2. 运行示例

3. JSON 批量输出版本

关键概念速查

为确保读者理解扫描逻辑,这里简要说明三个核心概念:

混合密钥交换(Hybrid KEM)

现代浏览器和服务器不直接切换到纯后量子密钥交换,而是同时执行经典 ECDH 和 ML-KEM,将两个共享密钥混合后作为 TLS 密钥。这样即使 ML-KEM 存在未被发现的漏洞,还有 ECDH 兜底;反之如果量子计算机成熟了,ECDH 被破解,ML-KEM 仍然安全。

CODE
TLS 1.3 混合密钥交换流程:
客户端 → 同时发送 X25519 pubkey + ML-KEM 密文
服务器 → 同时使用 X25519 私钥 + ML-KEM 私钥解出共享密钥
最终密钥 = HKDF(X25519_shared || MLKEM_shared)

证书签名算法(为什么"暂时够用")

你会发现上述扫描器几乎总是给出"PARTIAL"评级——因为目前还没有公共 CA 签发 ML-DSA/SLH-DSA 证书。当前主流 CA(Let's Encrypt、DigiCert、Sectigo)签发的证书仍然是 ECDSA 或 RSA。

并不紧迫。证书签名属于认证(Authentication),只有在 Q-Day 实际到来时才会面临伪造风险。而密钥交换属于机密性(Key Establishment),面临"先收集、后解密"的即时威胁。这也是 EO 14412 将密钥交换(2030)和数字签名(2031)分开要求的原因。

Shor vs Grover 威胁不对称

算法类型示例Shor 威胁Grover 威胁PQC 对策
公钥密钥交换ECDH, RSA-KEM多项式时间破解(毁灭性)ML-KEM
公钥数字签名ECDSA, RSA-PSS多项式时间破解(毁灭性)ML-DSA, SLH-DSA
对称加密AES-128, AES-256二次加速(减半安全强度)增大密钥长度(AES-256 足够)
哈希函数SHA-256, SHA-3二次加速(减半安全强度)增大输出长度
关键结论:AES-256 和 SHA-384 在量子攻击下仍然安全(Grover 算法只将安全强度减半:AES-256 → 128 位量子安全)。不需要替换对称算法,只需要替换非对称算法。

常见陷阱与故障排除

陷阱 1:Python ssl 模块无法直接获取 KEM 组

Python 的 ssl 模块目前(3.12/3.13)对 TLS 1.3 密钥交换信息的暴露非常有限。socket.cipher() 只返回密码套件名称,不返回协商的 Named Group。这就是扫描器的 probe_tls_connection() 为什么还要调用 openssl s_client 作为补充探测的原因。

替代方案:如果你不想依赖 openssl CLI,可以使用 scapy 直接构造 TLS Client Hello 并解析 Server Hello 中的 Key Share Extension,但代码复杂度大幅增加。

陷阱 2:系统 OpenSSL 版本过旧

X25519MLKEM768 需要 OpenSSL 3.2+ 或 BoringSSL。如果你在 Ubuntu 22.04(默认 OpenSSL 3.0)上运行,扫描器会正确报告"无法协商 PQC"——不是因为目标不支持,而是因为客户端 OpenSSL 版本不够。

BASH
$ openssl version
OpenSSL 3.2.x  ← 检查版本

# 升级方法(Ubuntu 22.04 → 24.04 或手动编译)
sudo apt install openssl  # Ubuntu 24.04 默认为 3.2+

陷阱 3:企业 TLS 拦截设备的影响

许多企业网络中存在 SSL/TLS 解密代理(Firewall、SWG、DPI)。这些设备可能与客户端完成 PQC 混合握手,但与企业内网服务器使用经典 ECDH。扫描器报告的是你到最近一跳的 PQC 状态,不是端到端状态。

解决方法

  • 内网扫描时,向安全团队确认 TLS 代理是否已配置为透传 PQC 流量
  • 公网扫描时,直接面向目标主机(绕过 VPN/代理的 split tunnel)

进阶扩展思路

扫描器目前是一个基础框架,可以根据需要扩展以下功能:

  • 证书透明度日志查询:通过 CT logs 监控是否有人为你的名字签发了 PQC 测试证书
  • DNS TLSA 记录分析:通过 DANE 发布 TLSA 记录约束对端证书算法
  • SBOM/CBOM 关联:将扫描结果与软件物料清单关联,标记哪些组件使用了 PQC 不兼容的库
  • Shodan/Censys 集成:通过 Shodan API 批量查询公网资产的 PQC 就绪状态
  • 告警集成:将 FAIL/PARTIAL 事件推送到 Slack/PagerDuty/企业微信

总结

后量子密码就绪性扫描是构建量子影响清单的第一步。本文提供的扫描器有以下特点:

  • 轻量级:单文件,无需数据库或复杂配置
  • 非侵入:只完成 TLS 握手,不发送任何恶意载荷
  • 可扩展:模块化设计(探测→分析→报告),每层可独立替换
实际部署建议:
  • 内网关键服务:每月扫描一次,跟踪 PQC 支持进度变更
  • 出站 API 依赖:每季度扫描一次,评估供应链 PQC 就绪性
  • 为扫描器建立基线:每个目标记录第一次扫描结果,后续对比变化
代码已发布在配套的 pqc-scanner 代码仓库(链接示例,仅示意)。欢迎在评论区分享你扫描到的有趣发现——哪些国内站点已经支持 X25519MLKEM768?哪些还停留在 TLS 1.0?


延伸阅读