密钥生命周期管理(创建、分发、使用、销毁)
密钥生命周期管理,说白了就是管好一把密钥从生到死的全过程。我见过不少项目,密钥生成得很随意,分发靠邮件附件,销毁就delete一下文件……结果呢?要么泄露,要么该用的时候找不到私钥了。这一章我们就把这四个阶段拆开讲讲,每个阶段都有我踩过的坑。
1. 密钥创建(Key Generation)
创建密钥是整个生命周期的基础。我自己习惯用CNG(Cryptography Next Generation)或者OpenSSL的底层接口,而不是直接调openssl命令行——因为命令行不记录熵源信息,对合规审计是个漏洞。
为什么强调熵源?说白了,伪随机数生成器(PRNG)的种子如果不够随机,生成的密钥就容易撞上。我记得有一次在嵌入式设备上,系统刚启动就调用CNG生成RSA密钥,结果因为硬件熵池还没灌满,连续两次生成的私钥只有几个比特不同……嗯,从那以后我生成密钥前必先检查熵余量。
// 使用 CNG 生成 2048 位 RSA 密钥对(伪代码)
BCRYPT_ALG_HANDLE hAlg;
BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RSA_ALGORITHM, NULL, 0);
BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_OAEP, 8, 0);
BCRYPT_KEY_HANDLE hKey;
BCryptGenerateKeyPair(hAlg, &hKey, 2048, 0);
BCryptFinalizeKeyPair(hKey, 0);
// 导出公钥供分发
PBYTE pbPublicKey;
ULONG cbPublicKey;
BCryptExportKey(hKey, NULL, BCRYPT_RSAPUBLIC_BLOB, NULL, 0, &cbPublicKey, 0);
pbPublicKey = (PBYTE)malloc(cbPublicKey);
BCryptExportKey(hKey, NULL, BCRYPT_RSAPUBLIC_BLOB, pbPublicKey, cbPublicKey, &cbPublicKey, 0);
2. 密钥分发(Key Distribution)
密钥一旦生成,就要送到该用它的地方去。我见过最野的做法:用微信把私钥的Base64发过去……别笑,真的发生过。安全分发从来没有“够用就好”,必须加密信封保护。
常见的分发路径有两种:封装(Key Encapsulation Mechanism, KEM)和直接加密导出。KEM更适合点对点场景,比如两端的TPM之间交换;直接导出则适合中心化分发(KDC模式)。你选哪种?我建议优先用CNG的BCryptExportKey配合对端公钥做OAEP加密,这样即便网络中间人截获,拿到也是乱码。
| 分发方法 | 保护机制 | 适用场景 | 风险 |
|---|---|---|---|
| KEM(密钥封装) | 非对称加密 + 对称密钥 | 两节点之间首次交互 | 需要在线交换临时公钥 |
| 加密导出(CNG Export) | OAEP + 目标公钥 | 中心化密钥管理系统 | 导出后目标系统的安全取决于后续存储 |
| 带外传递(人工口述) | 无 | 应急恢复(极少用) | 人为泄密风险极高 |
3. 密钥使用(Key Usage)
密钥分完了,就要用到具体的加密、签名、协商中去。这个阶段最容易出问题的是“超限使用”——比如一把只应该用于对称加密的AES密钥,被拿来当HMAC密钥用;或者私钥的使用次数超出安全阈值。
我个人习惯是在CryptoAPI的BCRYPT_KEY_HANDLE创建时,通过BCryptSetProperty设置BCRYPT_KEY_USAGE_POLICY,明确指定允许的算法和操作。这样即使后续代码不小心传错了参数,API也会拒绝执行。
// 设置密钥使用策略:只允许 AES-GCM 加密
BCRYPT_KEY_USAGE_POLICY usagePolicy;
usagePolicy.dwLength = sizeof(usagePolicy);
usagePolicy.dwUsage = BCRYPT_KEY_USAGE_ENCRYPT; // 仅加密
BCryptSetProperty(hKey, BCRYPT_KEY_USAGE_POLICY, (PBYTE)&usagePolicy, sizeof(usagePolicy), 0);
另外,使用阶段一定要考虑轮换。我参与的一个金融项目要求每3000次加密操作后自动生成新密钥,旧密钥归档为“只可解密,不可加密”。你可以用HSM的计数器(如PKCS#11的CKM_RSA_PKCS_OAEP配合CKM_DERIVE)或者干脆在应用层维护一个使用计数,到了阈值就触发轮换流程。
4. 密钥销毁(Key Destruction)
销毁,不是把Key文件删了就算完。你知道文件系统删文件只是删除目录条目,数据还在磁盘上等着被恢复吗?密钥销毁必须做到:内存中的密钥位清零 + 存储介质上的密文(如果有)被覆写或物理销毁。
在Windows CNG下,用BCryptDestroyKey之后,最好手动对密钥材料调用SecureZeroMemory,防止编译器优化掉清零操作。嗯,我记得有一次因为没有调用SecureZeroMemory,密钥的残余数据竟然在崩溃转储文件里被分析出来了……
// 安全销毁密钥内存
if (hKey) {
BCryptDestroyKey(hKey);
// 额外清零缓冲区
if (pbPublicKey) {
SecureZeroMemory(pbPublicKey, cbPublicKey);
free(pbPublicKey);
pbPublicKey = NULL;
}
}
对于硬件密钥(TPM、HSM),销毁通常通过TPM2_EvictControl或者HSM的DeleteKey API。注意:HSM上的密钥被删除后,如果备份副本没同步删除,恢复备份时旧钥又会死灰复燃。我刚才说的那个项目,误备份导致旧钥复活,差点造成双密钥冲突。所以销毁前一定要先确认所有备份、所有派生密钥都被标记为“已撤回”。
生命周期全景图
下面这张SVG图把这四个阶段串了起来,顺便加上了常见的备份和轮换回流节点。你看一眼就明白了。