c# - 对于一个用户,从xml字符串导入DSA key 失败。权限?安装损坏? KSP错误?

标签 c# .net cryptography cryptoapi dsa

用户最近在使用我的软件时报告了一个奇怪的错误。我使用DSA签名来验证许可证。当软件导入公共(public) key 以验证签名时,DSA提供程序的FromXmlString方法将抛出 CryptographicException ,其描述为“ key 在指定状态下无效”。

似乎从System.Security.Cryptography.Utils.CreateProvHandle调用的_OpenCSP方法返回NTE_BAD_KEY_STATE(0x8009000b)。这是任何人第一次向我报告此错误,并且该代码多年来未曾更改。

可能的原因是什么?隐藏的权限错误? CAPI安装损坏?被.net信任/权限设置阻止?是由 key 存储提供商存储的垃圾,还是KSP向cryptoapi返回了意外的东西?

我已经搜索了错误代码/描述/等,但没有发现任何真正的答案,可能是什么原因引起的...

失败的代码的隔离版本位于此处:
http://forum.huagati.com/getattachment.ashx?fileid=78

using System;
using System.Security.Cryptography;
using System.Reflection;

public class Test
{
  public static void Main()
  {
    try
    {
      string key = "<DSAKeyValue><P>wrjxUnfKvH/1s5cbZ48vuhTjflRT5PjOFnr9GeUPZSIoZhYATYtME4JRKrXBtSkyioRNtE1xgghbGAyvAJ5jOWw88fLBF+P1ilsZyq72G1YcbB+co8ImQhAbWKmdCicO9/66Th2MB+7kms/oY3NaCzKEuR7J3b23dGrFpp4ccMM=</P><Q>xmxoSErIJCth91A3dSMjC6yQCu8=</Q><G>bwOLeEaoJHwSiC3i3qk9symlG/9kfzcgrkhRSWHqWhyPAfzqdV1KxJboMpeRoMoFr2+RqqKHgcdbzOypmTeN4QI/qh4nSsl5iEfVerarBOrFuRdOVcJO0d8WE233XQznd1K66nXa5L8d9SNZrM6umZ1YuBjhVsTFdPlIXKfGYhk=</G><Y>wZnEEdMUsF3U3NBQ8ebWHPOp37QRfiBn+7h5runN3YDee1e9bC7JbJf+Uq0eQmU8zDs+avEgD68NpxTKEHGr4nQ3rW6qqacj5SDbwO7nI6eN3wWrVhvrWcQm0tUO93m64HsEJREohfoL+LjqgrqIjZVT4D1KXE+k/iAb6WKAsIA=</Y><J>+zmcCCNm2kn1EXH9T45UcownEe7JH+gl3Lw2lhVzXuX/dYp5sGCA2lK119iQ+m3ogjOuwABATCVFLo6J66DsSlMd0I8WSD5WKPvypQ7QjY0Iv71J2N0FW0ZXpMlk/CE8zq4Z7arM1N564mNe</J><Seed>QDrZrUFowquY5Uay8YtUFOXnv28=</Seed><PgenCounter>Gg==</PgenCounter></DSAKeyValue>";

      DSACryptoServiceProvider csp2 = new DSACryptoServiceProvider();
      csp2.FromXmlString(key);

      Console.WriteLine("Success!");
    }
    catch (Exception ex)
    {
      int hResult = 0;
      try
      {
          PropertyInfo pi = typeof(Exception).GetProperty("HResult", BindingFlags.NonPublic | BindingFlags.Instance);
          hResult = (int)pi.GetValue(ex, null);
      }
      catch (Exception ex2)
      {
          Console.WriteLine("HResult lookup failed: " + ex2.ToString());
      }
      Console.WriteLine("Initializing CSP failed: " + ex.ToString() + "\r\nHResult: " + hResult.ToString("x"));
    }
    Console.WriteLine("\r\nPress Enter to continue");
    Console.ReadLine();
  }
}

...并在受影响的用户的计算机上返回:
Initializing CSP failed: System.Security.Cryptography.CryptographicException: Ke
y not valid for use in specified state.

at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters paramete
rs, Boolean randomKeyContainer)
at System.Security.Cryptography.Utils.get_StaticDssProvHandle()
at System.Security.Cryptography.DSACryptoServiceProvider.ImportParameters(DSA
Parameters parameters)
at System.Security.Cryptography.DSA.FromXmlString(String xmlString)
at Test.Main()
HResult: 8009000b

更新:当在同一台计算机上的.net fx 2.0下运行时,相同的代码可以正常工作,但在.net fx 4.0下无法运行。

更新2:似乎DSA提供程序会寻找存储在%APPDATA%\Microsoft\Crypto\DSS\[SID]下的 key ,即使使用现有 key 进行初始化也是如此。可能与该机制有冲突吗?任何人都知道该 key 存储事物的工作方式,以及为什么从字符串中加载公共(public) key 时会被击中?

最佳答案

您所描述的问题似乎让我很有趣,但是如果没有一些其他信息和一些实验,则很难确定是什么原因。因此,我尝试描述我如何理解问题。

首先,.NET加密类在内部使用非托管的CryptoAPI。因此,_OpenCSP方法在内部调用CryptAcquireContext函数。在其文档中,我们可以阅读有关错误NTE_BAD_KEY_STATE(0x8009000BL)的以下内容:

The user password has changed since the private keys were encrypted.



DSA提供程序使用的用户私有(private) key 将另存为文件,保存在%APPDATA%\Microsoft\Crypto\DSS\[SID]目录中,并将使用相对复杂的算法进行加密,您可以通过该算法读取here。重要的是要了解目录中的文件与用户 key 的 key 容器相对应。通常,用户对文件系统中的文件具有完全访问权限。文件将使用取决于用户密码的 key 进行加密。在许多标准情况下,更改密码后将重新加密文件,但是恢复算法取决于许多因素。如果重设密码而不是由用户本身(由域管理员/帐户运算符(operator)等)更改密码,则目录%APPDATA%\Microsoft\Crypto\DSS\[SID]的旧包含可能不再有用。例如,如果该用户不是Active Directory用户(本地用户),并且本地管理员重置其密码,则将发生加密容器问题。

因此,第一个建议是询问用户是否重置了他的Active Directory密码。接下来,您应该验证用户配置文件中是否存在%APPDATA%\Microsoft\Crypto\DSS\[SID]目录,并且用户对文件系统中的目录具有完全访问权限。您应该从目录中删除所有文件(之前已创建文件的备份副本)。顺便说一句,很有趣的是知道用户是否具有中央保存的配置文件(保存在服务器上)。如果它具有中央配置文件,则可以验证该用户在另一台计算机上是否存在您描述的相同问题,而其他用户在其原始计算机上也不会出现问题。

对我来说还不清楚的另一个问题是,为什么只使用公钥来使用目录中的 key 容器,所以它根本就被使用了。在CryptoAPI中,应该使用%APPDATA%\Microsoft\Crypto\DSS\[SID]CryptAcquireContext作为NULL参数,并使用pszContainer作为CRYPT_VERIFYCONTEXT参数。我不确定.NET是否使用dwFlags标志,它可能是间接的问题。

您可以使用具有the constructor参数的CspParameters创建CRYPT_VERIFYCONTEXT。另一端的CspParameters具有Flags属性,该属性在.NET 4.0中使用CreateEphemeralKey值进行了扩展。 DSACryptoServiceProvider的描述与CspProviderFlags.CreateEphemeralKey函数的CRYPT_VERIFYCONTEXT标志的描述非常接近。因此,使用可以尝试将CspProviderFlags.CreateEphemeralKeyCryptAcquireContextCspProviderFlags.CreateEphemeralKey一起使用(CspProviderFlags.UseDefaultKeyContainer作为NULLpszContainer参数也意味着默认 key 容器)。

此外,如果可能,可以尝试在可以重现问题的计算机上调试问题。为了进行调试,您可以使用.NET源,可以启用这些源(请参见herehere),也可以使用here下载的。然后,您可以回答有关程序中当前使用的CryptAcquireContext值的一些问题,并比较.NET 3.5和.NET 4.0的值。

如果我写的内容不能帮助解决问题,请您在问题中附加其他信息:
  • 哪个计算机可以在哪个操作系统和Service Pack上再现问题?
  • 问题是否取决于用户?我的意思是:同一台计算机上的其他用户是否遇到相同的问题?
  • 域( Activity 目录)用户或本地用户帐户是否存在问题?存在问题的中央用户配置文件的用户是否保存在服务器上?如果他有,那么该问题能否由用户在其他计算机上重现?
  • 您能否再介绍一下使用公钥验证的哪种环境?特别是程序是在用户安全性上下文中运行还是您冒充了某些身份?

  • 更新了:在阅读了最初发布问题的论坛之后,我对解决问题感到悲观。如果您没有直接与可以复制该问题的计算机进行联系,并且仅在论坛中进行张贴,则仅与有问题的唯一用户进行通信...不过,我一直在考虑该问题,因此我决定尝试自己重现问题。我在这方面取得了成功。因此,在这里我将仔细描述我的结果。我将描述如何重现该问题,使其看起来与the forum thread中描述的完全相同。

    您应该执行以下步骤:

    1)您在计算机上创建一个本地测试帐户。
    2)您使用测试帐户登录,并为DSA提供程序生成默认 key 容器。例如,您可以针对调用以下简单函数的小型.NET程序执行此操作:
    static string GenerateDsaKeyInDefaultContainer()
    {
        const int PROV_DSS_DH = 13;
        CspParameters cspParam = new CspParameters(PROV_DSS_DH);
        cspParam.KeyContainerName = null;
        cspParam.KeyNumber = (int)KeyNumber.Signature;
        cspParam.Flags = CspProviderFlags.UseDefaultKeyContainer;
        DSACryptoServiceProvider csp = new DSACryptoServiceProvider(cspParam);
        return csp.CspKeyContainerInfo.UniqueKeyContainerName;
    }
    

    该函数返回将在CspParameters目录中创建的文件名称,该文件将包含生成的 key 对。
    3)您注销测试帐户,然后使用具有本地管理权限的另一个帐户登录。您重置测试帐户的密码。
    4)您再次使用测试帐户登录一次,并验证您发布的在Visual Studio 2010中为.NET 4.0编译的测试程序会产生错误%APPDATA%\Microsoft\Crypto\DSS\[SID](0x8009000b),并且将引发相应的异常。
    5)如果重新编译.NET 3.5而不是.NET 4.0程序(也可以使用Visual Studio 2010),则测试程序将运行,没有任何错误。

    因此,所有结果将与the forum thread中描述的完全相同。

    如果使用DSA提供程序的默认 key 容器删除或重命名文件,则将解决问题。就像我之前所述,在重置用户密码之后,默认 key 容器的包含将无法解密。因此,如果您知道用户唯一的默认容器的名称(例如,您可以从Process Monitor的痕迹中看到该名称),则可以将任何其他用户和任何其他计算机上的任何 key 容器文件复制到该目录中NTE_BAD_KEY_STATE,重命名文件,以使该名称成为默认的容器名称,并且...您将获得与重置用户密码完全相同的结果。

    我使用%APPDATA%\Microsoft\Crypto\DSS\[SID]的不同设置作为CspParameters的参数进行了一些实验(请参阅有关CspProviderFlags.CreateEphemeralKey用法的第一个建议),但没有成功。之后,我调试了.NET 4.0的源代码,可以明确地说未打开代码的DSACryptoServiceProvider函数的调用将使用与_OpenCSP构造函数的参数无关的独立的参数进行调用。因此,无法以不同的DSACryptoServiceProvider设置作为CspParameters的参数的方式找到.NET 4.0问题的解决方法。

    因此,如果您想修改代码以使其在损坏的默认 key 提供程序的情况下也能正常工作,我目前仅能找到两种解决问题的方法:
  • 使用带有DSACryptoServiceProvider标志的非托管CryptAcquireContext函数实现全部或部分代码。
  • 检测到错误CRYPT_VERIFYCONTEXT(0x8009000b)的问题,并包括从NTE_BAD_KEY_STATE中删除或临时重命名文件的代码部分,该文件包含损坏的默认 key 容器。为了检测文件的名称,我认为您可以尝试将%APPDATA%\Microsoft\Crypto\DSS\[SID]函数与CryptGetProvParam或/和PP_UNIQUE_CONTAINER参数一起使用。

  • 很抱歉,我的回答很长,感谢所有能够阅读到这里的人。 :-)

    希望我的回答对您有所帮助,以便KristoferA解决问题并改善您开发的软件。

    关于c# - 对于一个用户,从xml字符串导入DSA key 失败。权限?安装损坏? KSP错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4274062/

    相关文章:

    android - 错误填充异常 : Blocktype mismatch: 0

    java - 安全证书-了解CA在代码签名中的角色

    c# - 执行处理程序 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerWrapper' 的子请求时出错

    c# - 从 linq 查询中获取值(value)(需要想法/建议)

    .net - NuGet 锁定文件无法使用 --locked-mode 还原

    .net - 泛型类型和泛型类型定义之间有什么区别?

    .net - 无法使用 HttpRequest 创建 SSL/TLS 安全通道

    hash - 这个登录方案安全吗?

    c# - SQL Server数据库信息到属性类c#

    c# - .NET Reflector 和 getters/setters 问题