如何设置CRYPT_FORCE_KEY_PROTECTION_HIGH以对 RSACryptoServiceProvider 强制实施高级强私钥保护?

How do I set CRYPT_FORCE_KEY_PROTECTION_HIGH to enforce high-level strong private key protection for RSACryptoServiceProvider?

提问人:t.probst 提问时间:9/15/2023 最后编辑:t.probst 更新时间:9/16/2023 访问量:90

问:

编辑:正如卢克所指出的那样,我CRYPT_FORCE_KEY_PROTECTION_HIGH传递给了错误的函数。我的标志值也不正确。完成这两项更正后,用户会弹出一个窗口来设置密钥保护密码,但在访问密钥时不需要密码。我仍在尝试排除故障,为什么不。

我正在将公共证书和私钥作为 .pfx 文件导入 .NET。我想将证书添加到当前用户证书存储中,并强制实施高级强私钥保护(将证书添加到存储时,用户必须输入密码,访问密钥时需要密码)。我知道有一个标志可以用来设置高保护 - 。CRYPT_FORCE_KEY_PROTECTION_HIGH

我正在尝试使用在密钥容器上设置的标志创建私钥的副本,然后将该私钥附加到证书中的公钥副本。

此代码运行时不会出现任何异常或错误。证书和私钥将添加到存储中,它们用于加密/解密。但是根本没有启用强密钥保护,也没有弹出窗口。

// certData is obtained from an existing certificate with X509Certificate2.Export(X509ContentType.Pfx)
public static void AddCertificateStrongKeyProtection(byte[] certData)
{
    using (X509Certificate2 certificate = new X509Certificate2(certData, "", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet))
    {
        IntPtr hProv = IntPtr.Zero;
        IntPtr hKey = IntPtr.Zero;

        const string providerName = "Microsoft Enhanced Cryptographic Provider v1.0";
        const string containerName = "HighProtectionContainer";

        // Acquire a cryptographic context with the flag for strong protection set
        if (!NCrypt.CryptAcquireContext(ref hProv, containerName, providerName, NCrypt.PROV_RSA_FULL, NCrypt.CRYPT_NEWKEYSET))
        {
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
        }

        const uint AT_KEYEXCHANGE = 1;
        const uint CRYPT_FORCE_KEY_PROTECTION_HIGH = 0x00008000;

        // Make a new key
        if (!NCrypt.CryptGenKey(hProv, AT_KEYEXCHANGE, CRYPT_FORCE_KEY_PROTECTION_HIGH, ref hKey))
        {
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
        }

        // Convert the original RSACng to RSACryptoServiceProvider by exporting/importing RSA params
        RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider();
        RSAParameters parameters = certificate.GetRSAPrivateKey().ExportParameters(true);
        rsaCryptoServiceProvider.ImportParameters(parameters);

        // Get the handle of the RSACryptoServiceProvider
        var keyField = typeof(RSACryptoServiceProvider).GetField("_safeKeyHandle", BindingFlags.NonPublic | BindingFlags.Instance);

        if (keyField == null)
        {
            throw new InvalidOperationException("Unable to access the _safeKeyHandle field.");
        }

        var safeKeyHandle = (SafeHandle)keyField.GetValue(rsaCryptoServiceProvider);
        IntPtr originalKey = safeKeyHandle.DangerousGetHandle();

        const uint PRIVATEKEYBLOB = 0x7;
        int blobLength = 0;

        // Export the original key data
        if (!NCrypt.CryptExportKey(originalKey, IntPtr.Zero, PRIVATEKEYBLOB, 0, null, ref blobLength))
        {
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
        }

        byte[] keyBlob = new byte[blobLength];

        if (!NCrypt.CryptExportKey(originalKey, IntPtr.Zero, PRIVATEKEYBLOB, 0, keyBlob, ref blobLength))
        {
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
        }

        // Import the original key data into the newly created key
        if (!NCrypt.CryptImportKey(hProv, keyBlob, keyBlob.Length, IntPtr.Zero, 0, out hKey))
        {
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
        }

        CspParameters cspParams = new CspParameters
        {
            ProviderType = (int)NCrypt.PROV_RSA_FULL,
            KeyContainerName = "HighProtectionContainer",  // Should be the same as used in step 2
            Flags = CspProviderFlags.UseExistingKey
         };

         RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParams);
         using (X509Certificate2 cert = new X509Certificate2(certificate.RawData))
         {
             X509Certificate2 newCert = cert.CopyWithPrivateKey(rsaProvider)

             using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
             {
                 store.Open(OpenFlags.ReadWrite);
                 store.Add(newCert);
                 store.Close();
             }
         }
    }             
}

我的 P/Invoke 签名:

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptAcquireContext(
    ref IntPtr hProv,
    string pszContainer,
    string pszProvider,
    uint dwProvType,
    uint dwFlags);

[DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CryptGenKey(
    IntPtr hProv,
    uint Algid,
    uint dwFlags,
    ref IntPtr phKey);

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool CryptExportKey(
    IntPtr hKey, 
    IntPtr hExpKey, 
    uint dwBlobType, 
    uint dwFlags, 
    byte[] pbData, 
    ref int pdwDataLen);

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool CryptImportKey(
    IntPtr hProv,
    byte[] pbData,
    int dwDataLen,
    IntPtr hPubKey,
    uint dwFlags,
    out IntPtr phKey);

C# .NET WinAPI 加密 pinvoke

评论

0赞 Luke 9/16/2023
您将标志传递给错误的函数。
0赞 t.probst 9/16/2023
你能详细说明@Luke吗?如果我将标志传递给 CryptGenKey,则会出现“指定无效标志”异常,但我找到的所有源都表明该标志应该对 CAPI 有效。 导致异常。CryptGenKey(hProv, AT_KEYEXCHANGE, 0x00000040, ref hKey)
0赞 t.probst 9/16/2023
我发现了一个不同的值。如果我将其设置为有效,并且用户会收到一个弹出窗口来设置密钥的密码。但是,在访问密钥进行解密时,不需要密码。当我使用 CNG 创建密钥并将其添加到应用商店时,访问密钥时需要密码。我不确定为什么会有所不同。CRYPT_FORCE_KEY_PROTECTION_HIGH0x00008000CryptGenKey
0赞 Luke 9/17/2023
可能需要设置一个标志来告知 CSP 保护密钥。它没有被记录为支持,但无论如何我都会试一试。如果失败,您可以尝试 。CryptImportKey()CRYPT_FORCE_KEY_PROTECTION_HIGHCRYPT_USER_PROTECTED

答: 暂无答案