【问题标题】:Verify Private Key Protection before signing with RSACryptoServiceProvider在使用 RSACryptoServiceProvider 签名之前验证私钥保护
【发布时间】:2013-12-14 00:21:46
【问题描述】:

在 C# 中使用 RSACryptoServiceProvider 对数据进行签名时,我需要确保导入的证书具有强密钥保护和高安全级别,以要求用户每次使用密钥签名时都输入密码。这是签名代码的快速简化示例:

X509Store myCurrentUserStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
myCurrentUserStore.Open(OpenFlags.MaxAllowed);
X509Certificate2 currentCertificate = myCurrentUserStore.Certificates[4];

RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(currentCertificate.PrivateKey.ToXmlString(true));

byte[] signedData = Encoding.UTF8.GetBytes(originalFileContent);
byte[] signature = key.SignData(signedData, CryptoConfig2.CreateFromName("SHA256CryptoServiceProvider") as HashAlgorithm);    

那么检查证书是如何安装的最好的方法是什么,以便在没有安装具有高安全级别的强私钥保护时显示错误消息?

【问题讨论】:

    标签: c# x509 x509certificate2 rsacryptoserviceprovider


    【解决方案1】:

    你的 sn-p 中有几处我不明白。

    1. 为什么要使用 MaxAllowed 打开。如果您只想阅读,请使用 ReadOnly。
    2. 为什么要阅读 store.Certificates[4]。但大概这只是“读取证书”的占位符。
    3. 为什么要导出和重新导入密钥。 (特别是因为这将不得不提示,这会破坏你的“它需要提示”的目标)。

    对于#3,我假设您只是希望拥有一个独特的实例,在这种情况下:好消息! .NET 4.6 向 X509Certificate2 添加了一个 GetRSAPrivateKey(扩展)方法,该方法总是返回一个唯一的实例。 (您可能会很高兴知道 SignData 的新重载,它不鼓励将对象发送到终结器队列:https://msdn.microsoft.com/en-us/library/mt132675(v=vs.110).aspx

    无论如何,我在这里写的内容适用于中等(同意)或高(密码)保护。基于 CngKey 的方法可以区分中等和高,但经典的 CAPI 回退无法区分哪个是哪个。 (经典的 CAPI 回退只会发生在没有 CNG 兼容驱动程序的晦涩 HSM 上)。

    private static bool HasProtectedKey(X509Certificate2 cert)
    {
        if (!cert.HasPrivateKey)
        {
            return false;
        }
    
        using (RSA rsa = cert.GetRSAPrivateKey())
        {
            return HasProtectedKey(rsa);
        }
    }
    
    private static bool HasProtectedKey(RSA rsa)
    {
        RSACng rsaCng = rsa as RSACng;
    
        if (rsaCng != null)
        {
            return rsaCng.Key.UIPolicy.ProtectionLevel != CngUIProtectionLevels.None;
        }
    
        RSACryptoServiceProvider rsaCsp = rsa as RSACryptoServiceProvider;
    
        if (rsaCsp != null)
        {
            CspKeyContainerInfo info = rsaCsp.CspKeyContainerInfo;
    
            // First, try with the CNG API, it can answer the question directly:
            try
            {
                var openOptions = info.MachineKeyStore
                    ? CngKeyOpenOptions.MachineKey
                    : CngKeyOpenOptions.UserKey;
    
                var cngProvider = new CngProvider(info.ProviderName);
    
                using (CngKey cngKey =
                    CngKey.Open(info.KeyContainerName, cngProvider, openOptions))
                {
                    return cngKey.UIPolicy.ProtectionLevel != CngUIProtectionLevels.None;
                }
            }
            catch (CryptographicException)
            {
            }
    
            // Fallback for CSP modules which CNG cannot load:
            try
            {
                CspParameters silentParams = new CspParameters
                {
                    KeyContainerName = info.KeyContainerName,
                    KeyNumber = (int)info.KeyNumber,
                    ProviderType = info.ProviderType,
                    ProviderName = info.ProviderName,
                    Flags = CspProviderFlags.UseExistingKey | CspProviderFlags.NoPrompt,
                };
    
                if (info.MachineKeyStore)
                {
                    silentParams.Flags |= CspProviderFlags.UseMachineKeyStore;
                }
    
                using (new RSACryptoServiceProvider(silentParams))
                {
                }
    
                return false;
            }
            catch (CryptographicException e)
            {
                const int NTE_SILENT_CONTEXT = unchecked((int)0x80090022);
    
                if (e.HResult == NTE_SILENT_CONTEXT)
                {
                    return true;
                }
    
                throw;
            }
        }
    
        // Some sort of RSA we don't know about, assume false.
        return false;
    }
    

    【讨论】:

    • bartonjs,一个键可以只实现RSACng而不是RSACryptoServiceProvider吗?如果是,那么在这种情况下,我们如何获得诸如(全部来自CspParametersProviderNameProviderTypeKeyNumberKeyContainerNameKeyPasswordFlags...之类的信息?
    • @PedroGaspar 是的。 RSACng.Key 的属性(也许有些可能是您必须通过 GetProperty 请求的额外命名属性)。
    • RSACng 是否有类似CspParameters.KeyPassword 的信息,用于通知硬件证书 PIN?我只是找不到它...
    • bartonjs,我想我刚刚找到它:RSACng.Key.SetProperty( NCRYPT_PIN_PROPERTY ),虽然我现在无法测试它,但我认为是这样,谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-21
    • 1970-01-01
    • 1970-01-01
    • 2019-02-04
    • 1970-01-01
    相关资源
    最近更新 更多