用户最近在使用我的软件时报告了一个奇怪的错误。我使用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.CreateEphemeralKey或CryptAcquireContext
与CspProviderFlags.CreateEphemeralKey
一起使用(CspProviderFlags.UseDefaultKeyContainer
作为NULL
的pszContainer
参数也意味着默认 key 容器)。此外,如果可能,可以尝试在可以重现问题的计算机上调试问题。为了进行调试,您可以使用.NET源,可以启用这些源(请参见here和here),也可以使用从here下载的。然后,您可以回答有关程序中当前使用的
CryptAcquireContext
值的一些问题,并比较.NET 3.5和.NET 4.0的值。如果我写的内容不能帮助解决问题,请您在问题中附加其他信息:
更新了:在阅读了最初发布问题的论坛之后,我对解决问题感到悲观。如果您没有直接与可以复制该问题的计算机进行联系,并且仅在论坛中进行张贴,则仅与有问题的唯一用户进行通信...不过,我一直在考虑该问题,因此我决定尝试自己重现问题。我在这方面取得了成功。因此,在这里我将仔细描述我的结果。我将描述如何重现该问题,使其看起来与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/