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 密码学生态里。
1.1 从 CryptoAPI 到 CNG,究竟改了啥?
我刚开始做迁移时,以为就是改改函数名。后来踩了坑才发现,两者的设计哲学完全不同。下表总结了几个关键差异:
| 维度 | CryptoAPI 1.0 | CNG |
|---|---|---|
| 算法扩展性 | 算法固定,新增算法需打系统补丁 | 通过 Provider 接口动态加载算法(曾用名:BCrypt / NCrypt) |
| 密钥存储 | 依赖 CSP(Cryptographic Service Provider),密钥和算法紧耦合 | 密钥存储提供者 KSP 独立于算法提供者 |
| 硬件适配 | 需要实现 CSP 接口,文档晦涩,调试困难 | KSP 或 Provider 接口更清晰,微软提供了官方模板 |
| FIPS 合规 | CSP 需要自己处理合规签名 | CNG 内置 FIPS 模式,Provider 可声明合规级别 |
| 异步支持 | 不支持异步 I/O | 支持异步,适合高并发场景 |
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”,管密钥存储和密钥协商。
注意看,第三方 Provider 既可以实现 BCrypt 接口(比如软件算法库),也可以实现 NCrypt 接口(比如 HSM 适配)。我们的课程重点就是后者——写一个 NCrypt Provider,把 HSM 的密钥管理能力暴露给 Windows 应用。
1.3 CNG 核心组件详解
咱们得熟悉 CNG 的几大核心 API 前缀,你会经常在代码里看到它们:
- BCrypt* —— 基本密码功能。比如
BCryptEncrypt、BCryptHashData。用于执行纯粹的计算,不需要持久化密钥。 - NCrypt* —— 密钥存储与保护。比如
NCryptOpenKey、NCryptSignHash。所有用到持久化密钥的操作都走这里。 - NCryptCreatePersistedKey —— 在 KSP 中生成或导入密钥。HSM 适配时这里很关键,因为你要指定密钥的算法、用途、是否可导出等属性。
- Key Storage Provider (KSP) —— 密钥存储提供者,是 NCrypt 的核心扩展点。你可以将自己的硬件 HSM 注册为一个 KSP,之后所有应用都能通过
NCryptOpenKey访问 HSM 里的密钥。
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,因为微软做了桥接层。