代码之家  ›  专栏  ›  技术社区  ›  KristoferA

对于一个用户,从xml字符串导入DSA密钥失败。权限?坏的装置?糟糕的KSP?

  •  27
  • KristoferA  · 技术社区  · 15 年前

    一个用户最近报告说,在使用我的软件时出现了一个奇怪的错误。我使用DSA签名来验证许可证。当软件导入公钥以验证签名时,DSA提供程序的 FromXmlString 方法抛出 密码异常 带着描述“ 密钥在指定状态下无效。 "

    从System.Security.Cryptography.Utils.CreateProvHandle调用的_OpenCSP方法似乎返回一个NTE_BAD_KEY_状态(0x8009000b)。这是第一次有人向我报告这个错误,而且这个代码已经很多年没有改变了。

    可能的原因是什么?屏蔽权限错误?损坏的CAPI装置?被.net信任/权限设置阻止?密钥存储提供程序存储的垃圾,或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
    

    更新: 在同一台计算机上运行.NETFX2.0时,相同的代码可以正常工作,但在.NETFX4.0下失败。

    更新2: 看来DSA提供商查找存储在%AppDATAs\\Cudio\Disto\ds\[SID]下的密钥,甚至用现有密钥初始化。会不会和这个机制有冲突?任何人都知道密钥存储是如何操作的,以及为什么从字符串加载公钥时会命中它?

    1 回复  |  直到 15 年前
        1
  •  85
  •   Oleg    15 年前

    你所描述的这个问题,似乎我很感兴趣,但如果没有一些额外的信息和一些实验,很难确切地说出原因是什么。所以我试着描述我是如何理解这个问题的。

    首先.NET加密类在内部使用非托管CryptoAPI。所以方法 _OpenCSP 内部呼叫 CryptAcquireContext 功能。在它的文档中,我们可以阅读以下关于错误的内容 NTE_BAD_KEY_STATE (0x8009000BL):

    用户密码已更改 私钥被加密了。

    用户 私有的 DSA提供程序使用的密钥作为文件保存在目录中 %APPDATA%\Microsoft\Crypto\DSS\[SID] 将使用相对复杂的算法进行加密,您可以阅读 here . 重要的是要了解目录中的文件对应于用户密钥的密钥容器。通常,用户可以完全访问文件系统中的文件。文件将使用密钥进行加密,密钥取决于用户的密码。在许多标准情况下,文件会在密码更改后重新加密,但恢复算法,这取决于很多事情。如果密码被重置而不是由用户自己更改(由域管理员/帐户操作员等更改),则目录的旧内容 %APPDATA%\Microsoft\Crypto\DSS\[SID] 再有用不过了。例如,如果用户不是Active Directory用户(本地用户),并且本地管理员重置了他的密码,那么密码容器的问题就会发生。

    因此,第一个建议是询问用户是否重置了他的Active Directory密码。接下来,您应该验证目录 %APPDATA%\Microsoft\Crypto\DSS\[SID] 存在于用户的配置文件中,用户可以完全访问文件系统中的目录。您应该从目录中删除所有文件(以前创建文件的备份副本)。顺便说一下,了解用户是否有中央保存的配置文件(保存在服务器上)是很有趣的。如果它具有中心轮廓,你可以验证你所描述的同样的问题存在于另一台计算机上,而其他用户在原有的计算机上没有问题。

    还有一个我不太清楚的问题是 为什么? 目录中的密钥容器 %APPDATA%\Microsoft\Crypto\DSS\[SID] 因为你只使用 公钥 . 在CryptoAPI中应该使用 CryptAcquireContext 具有 NULL 作为 pszContainer 参数和 CRYPT_VERIFYCONTEXT 在里面 dwFlags . 我不确定.NET是否使用 加密上下文 你的问题可能是间接的。

    你可以创建 DSACryptoServiceProvider 具有 the constructor 有 CspParameters 参数。 CSP参数 另一边有 Flags 在.NET 4.0中使用值扩展的属性 CreateEphemeralKey . 描述 CspProviderFlags.CreateEphemeralKey 非常接近 加密上下文 旗 CryptAcquireContext 功能。所以使用可以尝试使用 CspProviderFlags.CreateEphemeralKey 或 CspProviderFlags.CreatePhemeralkey 一起 CspProviderFlags.UseDefaultKeyContainer ( 无效的 作为 PSZ容器 参数 CryptAcquireContext 也意味着默认密钥容器)。

    此外,如果可能,您可以尝试在可以重现问题的计算机上调试该问题。要进行调试,可以使用可以启用的.NET源(请参见 here 和 here )或者 下载 从 here . 然后你可以回答一些关于 CspParameters 当前在程序中使用并比较.NET3.5和.NET4.0的值。

    如果我所写的对解决问题没有帮助,请你在你的问题后面加上其他信息:

    • 哪个操作系统和哪个服务包的计算机上的问题可以复制?
    • 问题是否取决于用户?我的意思是:同一台电脑上的其他用户有相同的问题吗?
    • 域(ActiveDirectory)用户或本地用户帐户是否存在问题?是否有保存在服务器上的具有问题中心用户配置文件的用户?如果他有,那么这个问题是否也可以由用户在其他计算机上重现呢?
    • 您能再描述一下您使用公钥验证的环境吗?特别是程序是在用户安全上下文中运行还是进行了一些模拟?

    更新 :在阅读了最初发布问题的论坛后,我对问题的解决变得悲观。如果你没有直接与电脑联系,在那里问题可能会被复制,与唯一有问题的用户的沟通只在每次发布在论坛。。。尽管如此,我一直在思考这个问题,所以我决定自己尝试重现这个问题。我在这方面取得了成功。所以我会在这里详细描述我的结果。我将描述如何重现问题,使其看起来与 the forum thread .

    您应该执行以下步骤:

    1)在计算机上创建本地测试帐户。 2)使用测试帐户登录并为DSA提供程序生成默认密钥容器。例如,可以对调用以下简单函数的小型.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;
    }
    

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

    所以所有的结果都和 论坛主题 .

    如果使用DSA提供程序的默认密钥容器删除或重命名文件,则问题将得到解决。如我之前所述,在重置用户密码后,默认密钥容器的内容将无法解密因此,如果您知道用户唯一的默认容器的名称(例如,您可以从 Process Monitor )您只需将任何密钥容器文件从任何其他用户和任何其他计算机复制到目录 %APPDATA%\Microsoft\Crypto\DSS\[SID] ,重命名文件,使其名称成为默认容器名称,然后。。。您将得到与重置用户密码完全相同的结果。

    我做了一些不同设置的实验 CSP参数 作为的参数 DSACryptoServiceProvider服务提供商 (参见我关于使用 CspProviderFlags.CreateEphemeralKey ),但没有任何成功。之后,我调试了.NET 4.0的源代码,并可以确定地说 _开放式CSP 函数(代码未打开)将使用以下参数调用 独立的 从 DSACryptoServiceProvider服务提供商 构造器。因此,使用不同的 CSP参数 作为的参数 DSACryptoServiceProvider服务提供商 .

    因此,如果你想修改你的代码,使它也能在损坏的默认密钥提供程序的情况下工作,我认为目前只有两种方法可以解决这个问题:

    1. 使用非托管实现全部或部分代码 CryptAcquireContext 使用 加密上下文 旗帜。
    2. 用错误检测问题 NTE_BAD_KEY_状态 (0x8009000b)并包括从中删除或临时重命名文件的代码部分 %APPDATA%\Microsoft\Crypto\DSS\[SID] 包含损坏的默认密钥容器。要检测文件名,我想您可以尝试使用 CryptGetProvParam 使用 PP_UNIQUE_CONTAINER 或/和 PP_ENUMCONTAINERS 参数。

    很抱歉,我的答案太长了,感谢所有能读到这里的人。:-)

    我希望我的答案能帮助你解决问题,改进你开发的软件。

    推荐文章