当工作负荷在 Windows 上加载 PKCS#12/PFX 而不设置 PersistKeySet 或 EphemeralKeySet 存储选项时,.NET 将确定何时不再需要私钥并应将其擦除。 在早期版本的 .NET(和 .NET Framework 中),使用了两组不同的逻辑。 在 .NET 9 中,只有一组逻辑。
旧行为
以前,当从具有 new X509Certificate2(pfx, password, flags)
的 PKCS#12/PFX 加载证书(及其私钥)时,加载的证书表示私钥的生存期。 当此证书对象被释放(或者如果被垃圾回收而没有被丢弃,则最终确定),关联的私钥被删除。 没有发生所有权共享或所有权转移。
当从具有 X509Certificate2Collection.Import(pfx, password, flags)
的 PKCS#12/PFX 加载证书(及其私钥)时,每个加载的具有私钥的证书都会跟踪其生存期,就像单个证书加载一样。 但是此外,在证书的本机副本上放置了一个标记,以指示任何副本也应跟踪私钥生存期。 如果第二个 X509Certificate2 对象是根据相同的基础 PCERT_CONTEXT
值创建的,那么首先处理(或最终确定)的任何一个副本都会从另一个副本下擦除私钥。
以下代码由于私钥被删除而失败(使用 CryptographicException 或NullReferenceException):
X509Certificate2Collection coll = new X509Certificate2Collection(pfx, password, X509KeyStorageFlags.DefaultKeySet);
X509Certificate2Collection coll2 = coll.Find(X509FindType.FindBySubjectName, "", false);
coll2 = null;
GC.Collect();
GC.WaitForPendingFinalizers();
using (RSA key = coll[0].GetRSAPrivateKey())
{
key.SignData(pfx, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
}
新行为
从 .NET 9 开始,生存期始终与直接从 PKCS#12/PFX 加载生成的 X509Certificate2 实例相关联。
现在,先前行为部分中的相同代码片段成功了。
引入的版本
.NET 9 预览版 7
中断性变更的类型
此更改为行为更改。
更改原因
加载 PKCS#12/PFX 的大多数工作负荷都使用单个证书加载,并了解与该方法关联的生存期机制。 与收集负荷相关的机制往往令人惊讶,有时会导致过早的键擦除。
建议的操作
如果了解集合负荷生存期管理,并依赖于对克隆调用 Dispose
来导致键擦除,请确保你也(或相反)对原始加载的对象调用 Dispose
。