You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

转换为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 06:34:57