1. CNG 概述:为什么微软要重做一套密码学 API?

老实说,Windows 上做密码学开发的老哥们,当年没少被 CryptoAPI 折磨。函数命名不统一、算法固定死、扩展性差——想接入一个硬件 HSM,得写一堆不可移植的底层代码。我个人印象最深的是,有一次客户要求把 RSA 密钥对放到第三方加密卡上,我在 CryptoAPI 里翻了好几天文档,最后发现它压根不支持第三方密钥存储提供者。嗯,所以微软后来推出了 CNG(Cryptography Next Generation)。

CNG,说白了就是 Windows 密码学体系的下一代基座。它从 Vista 开始引入,到现在已经成了所有 Win32 密码学接口的底层依赖。你写 .NET 的 System.Security.Cryptography,底层最终也是走到 CNG 上。咱们这门课要讲的“CNG 接口适配 HSM 密码机”,本质上就是利用 CNG 的模块化架构,把硬件密码机挂到 Windows 密码学生态里。

核心观点: CNG 不是对 CryptoAPI 的小修小补,而是一次彻底的架构重构。它把算法实现、密钥存储、密钥协商等功能拆成独立模块,每个模块都可以被第三方替换。

1.1 从 CryptoAPI 到 CNG,究竟改了啥?

我刚开始做迁移时,以为就是改改函数名。后来踩了坑才发现,两者的设计哲学完全不同。下表总结了几个关键差异:

维度CryptoAPI 1.0CNG
算法扩展性 算法固定,新增算法需打系统补丁 通过 Provider 接口动态加载算法(曾用名:BCrypt / NCrypt)
密钥存储 依赖 CSP(Cryptographic Service Provider),密钥和算法紧耦合 密钥存储提供者 KSP 独立于算法提供者
硬件适配 需要实现 CSP 接口,文档晦涩,调试困难 KSP 或 Provider 接口更清晰,微软提供了官方模板
FIPS 合规 CSP 需要自己处理合规签名 CNG 内置 FIPS 模式,Provider 可声明合规级别
异步支持 不支持异步 I/O 支持异步,适合高并发场景
我的经验: 有一次迁移一个旧版签名服务,发现 CryptoAPI 的 CryptSignHash 在 64 位系统上有奇怪的 alignment 问题。换到 CNG 的 NCryptSignHash 后,问题自动消失。你想想看,这接口设计的差距。

1.2 CNG 的模块化架构

CNG 的架构其实挺简洁的。它分了三个逻辑层:

  • 应用层: 你写的代码,或者 CryptoAPI 代理、.NET、Schannel 等客户端。
  • CNG 核心层: 微软提供的路由与管理模块,负责加载 Provider、管理密钥池、提供上下文。
  • Provider 层: 实际执行密码运算的模块。分为两类——BCrypt Provider 负责纯算法(如 AES、SHA-256),NCrypt Provider 负责密钥存储与加解密(如 RSA 私钥操作)。

这里有个容易混淆的点:BCrypt 跟 Bcrypt 哈希算法不是一个东西。BCrypt 是 “Base Cryptographic Functions” 的缩写,提供对称/非对称/哈希算法。NCrypt 是 “N (Next) Cryptographic Functions”,管密钥存储和密钥协商。

CNG 模块化架构分层图 CNG 模块化架构 应用层 CryptoAPI / .NET / Schannel / 你的代码 CNG 核心层 路由管理 · Provider 生命周期 密钥池 · FIPS 状态 BCrypt Provider AES / SHA-256 / RSA ECC / 随机数生成 纯算法,不存储密钥 NCrypt Provider RSA/ECC 签名 · 解密 密钥存储 · KSP 包装 可对接硬件 HSM 第三方 Provider(HSM 适配器) 实现 KSP 或 BCrypt/NCrypt 接口 向 CNG 核心注册后即可使用

注意看,第三方 Provider 既可以实现 BCrypt 接口(比如软件算法库),也可以实现 NCrypt 接口(比如 HSM 适配)。我们的课程重点就是后者——写一个 NCrypt Provider,把 HSM 的密钥管理能力暴露给 Windows 应用。

1.3 CNG 核心组件详解

咱们得熟悉 CNG 的几大核心 API 前缀,你会经常在代码里看到它们:

  1. BCrypt* —— 基本密码功能。比如 BCryptEncrypt、BCryptHashData。用于执行纯粹的计算,不需要持久化密钥。
  2. NCrypt* —— 密钥存储与保护。比如 NCryptOpenKey、NCryptSignHash。所有用到持久化密钥的操作都走这里。
  3. NCryptCreatePersistedKey —— 在 KSP 中生成或导入密钥。HSM 适配时这里很关键,因为你要指定密钥的算法、用途、是否可导出等属性。
  4. Key Storage Provider (KSP) —— 密钥存储提供者,是 NCrypt 的核心扩展点。你可以将自己的硬件 HSM 注册为一个 KSP,之后所有应用都能通过 NCryptOpenKey 访问 HSM 里的密钥。
踩坑提醒: 我曾经在实现 KSP 时忽略了一个细节——CNG 要求 KSP 必须支持密钥的元数据属性(如算法、密钥大小),否则 NCryptGetProperty 会返回 NTE_NOT_SUPPORTED,导致 TLS 协商失败。记得在每个属性上打上正确的支持标志。

1.4 为什么用 CNG 适配 HSM 比旧 API 更爽?

咱们做 HS M 适配的最终目标是:让上层应用无感调用硬件密码机。用 CryptoAPI 实现这个目标,你得写一个完整的 CSP,还要处理一堆遗留的兼容性问题。我用 CNG 做过两个 HSM 项目,明显感觉到几点优势:

  • 接口更少。 实现一个 KSP 只需要填写大约 15 个函数回调(对比 CSP 的 30+)。
  • 调试友好。 CNG 有自带的测试工具 certutil -csptest,也可以直接写 C# 调用 RSACng 做集成测试。
  • 算法灵活。 你可以在一个 Provider 里同时支持 RSA、ECC、SM2 等算法,无需注册多个 CSP。
  • 向后兼容。 通过 CryptoAPI 的 CryptAcquireContext 仍然可以调用 CNG Provider,因为微软做了桥接层。
一句话总结: CNG 是 Windows 上对接硬件密码机的首选接口。它把密钥管理和算法计算分离,第三方可以实现独立的 KSP,轻松接入 HSM、TPM 甚至远程密钥服务器。