在使用RSACryptoServiceProvider进行签名之前验证私钥保护

时间:2013-12-14 00:21:46

标签: c# x509 x509certificate2 rsacryptoserviceprovider

在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);    

那么检查证书安装方式的最佳方法是什么?如果没有安装具有高安全级别的强私钥保护,我可以显示错误消息?

1 个答案:

答案 0 :(得分:1)

您的片段中有几件事我不明白。

  1. 为什么你要用MaxAllowed打开。如果您只是想阅读,请使用ReadOnly。
  2. 为什么你要阅读store.Certificates [4]。但据推测,这只是一个占位符,用于"阅读证书"。
  3. 为什么要导出并重新导入密钥。 (特别是因为那必须提示,这会打败你的"它需要提示"目标)。
  4. 对于#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;
    }