转换为CNG后私钥与X509证书不匹配问题排查
问题描述
我尝试通过PInvoke调用CNG处理X509证书,目标是导入带私钥的证书,设置NCRYPT_UI_FORCE_HIGH_PROTECTION_FLAG(访问密钥时强制输入密码),并将证书和密钥重新添加到证书存储中。
操作完成后,添加证书时按要求输入了密码,证书存储显示该证书带有私钥。但从存储中取出证书后,尽管HasPrivateKey属性值为true,断点调试也能看到密钥对象,但尝试导出私钥时提示“找不到私钥”,签名验证也失败。
安装证书的代码
// certData 来自对 X509Certificate2 对象调用 Export(X509ContentType.Pfx) 的结果 public static int InstallCertificate(byte[] certData) { using (X509Certificate2 certificate = new X509Certificate2(certData, "", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet)) { // 使用导入的证书数据创建证书上下文 byte[] certBytes = certificate.RawData; const int X509_ASN_ENCODING = 0x1; IntPtr certContext = CertCreateCertificateContext(X509_ASN_ENCODING, certBytes, certBytes.Length); if (certContext == IntPtr.Zero) { var err = Marshal.GetLastWin32Error(); throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } try { int result; IntPtr hProvider; const string MS_KEY_STORAGE_PROVIDER = "Microsoft Software Key Storage Provider"; int openResult = NCryptOpenStorageProvider(out hProvider, MS_KEY_STORAGE_PROVIDER, 0); if (openResult != 0) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); IntPtr hKey = IntPtr.Zero; byte[] keyBlob = certificate.GetRSAPrivateKey().ExportPkcs8PrivateKey(); // 获取证书名称用于密钥容器 StringBuilder subjectName = new StringBuilder(1024); if (CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, IntPtr.Zero, subjectName, (uint)subjectName.Capacity) > 0) { subjectName.ToString(); } int createResult = NCryptCreatePersistedKey(hProvider, out hKey, "RSA", subjectName.ToString(), 0, 0); if (createResult == -2146893809) // 密钥容器中已存在该密钥 { int openKeyResult = NCryptOpenKey(hProvider, out hKey, subjectName.ToString(), 0, 0); if (openKeyResult != 0) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } Console.WriteLine("PRIVATE KEY FOUND"); } else if (createResult != 0) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } else { // 设置密钥可导出并启用强保护 uint exportPolicy = NCRYPT_ALLOW_EXPORT_FLAG; int exportSetResult = NCryptSetProperty( hKey, "Export Policy", BitConverter.GetBytes(exportPolicy), sizeof(uint), 0); NCRYPT_UI_POLICY uiPolicy = new NCRYPT_UI_POLICY { dwVersion = 1, dwFlags = NCRYPT_UI_FORCE_HIGH_PROTECTION_FLAG, pszCreationTitle = Marshal.StringToHGlobalUni("Key Creation"), pszFriendlyName = Marshal.StringToHGlobalUni("My Key"), pszDescription = Marshal.StringToHGlobalUni("My Key Description") }; byte[] uiPolicyBytes = StructureToByteArray(uiPolicy); int protectionSetResult = NCryptSetProperty( hKey, NCRYPT_UI_POLICY_PROPERTY, uiPolicyBytes, (uint)uiPolicyBytes.Length, 0); // 获取DER格式的密钥数据,用于导入到CNG密钥中 AsymmetricAlgorithm pk = certificate.PrivateKey; AsymmetricCipherKeyPair pkPair = DotNetUtilities.GetKeyPair(pk); PrivateKeyInfo pkInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(pkPair.Private); byte[] derEncodedPrivateKey = pkInfo.ToAsn1Object().GetDerEncoded(); int importResult = NCryptImportKey(hProvider, hKey, "PKCS8_PRIVATEKEY", IntPtr.Zero, out _, derEncodedPrivateKey, derEncodedPrivateKey.Length, 0); if (importResult != 0) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); int finalizeResult = NCryptFinalizeKey(hKey, 0); // 清理分配的非托管内存 Marshal.FreeHGlobal(uiPolicy.pszCreationTitle); Marshal.FreeHGlobal(uiPolicy.pszFriendlyName); Marshal.FreeHGlobal(uiPolicy.pszDescription); } // 将CNG密钥与证书上下文关联 CRYPT_KEY_PROV_INFO provInfo = new CRYPT_KEY_PROV_INFO { pwszContainerName = subjectName.ToString(), pwszProvName = "Microsoft Software Key Storage Provider", dwProvType = 0, dwFlags = unchecked((int)CERT_NCRYPT_KEY_SPEC), cProvParam = 0, rgProvParam = IntPtr.Zero, dwKeySpec = 1 }; bool contextSet = CertSetCertificateContextProperty(certContext, CERT_KEY_PROV_INFO_PROP_ID, 0, ref provInfo); if (!contextSet) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); // 打开证书存储并添加证书 IntPtr hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, IntPtr.Zero, CERT_SYSTEM_STORE_CURRENT_USER, "MY"); if (hStore == IntPtr.Zero) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); bool addCert = CertAddCertificateContextToStore(hStore, certContext, CERT_STORE_ADD_REPLACE_EXISTING, IntPtr.Zero); if (!addCert) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); // 从存储中获取证书并检查密钥是否匹配 using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) { store.Open(OpenFlags.ReadOnly); X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindBySubjectName, subjectName.ToString(), false); X509Certificate2 cert = null; if (certs.Count > 0) { cert = certs[0]; } store.Close(); if (cert != null && cert.HasPrivateKey) { var x = cert.GetRSAPrivateKey(); if (IsPrivateKeyMatching(cert, x)) { Console.WriteLine("Private key matches the certificate."); } else { Console.WriteLine("Private key does NOT match the certificate."); } } } NCryptFreeObject(hKey); NCryptFreeObject(hProvider); } finally { CertFreeCertificateContext(certContext); } } return 1; }
检查密钥匹配的代码
public static bool IsPrivateKeyMatching(X509Certificate2 certificate, RSA privateKey) { byte[] dataToSign = Encoding.UTF8.GetBytes("SampleData"); // 任意随机数据均可 byte[] signature; // 使用私钥创建签名 using (var rsa = privateKey) { signature = rsa.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); } // 使用证书公钥验证签名 using (var rsa = certificate.GetRSAPublicKey()) { return rsa.VerifyData(dataToSign, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); } }
内容的提问来源于stack exchange,提问作者t.probst
相关产品推荐
相关产品推荐

