你所描述的这个问题,似乎我很感兴趣,但如果没有一些额外的信息和一些实验,很难确切地说出原因是什么。所以我试着描述我是如何理解这个问题的。
首先.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服务提供商
.
因此,如果你想修改你的代码,使它也能在损坏的默认密钥提供程序的情况下工作,我认为目前只有两种方法可以解决这个问题:
-
使用非托管实现全部或部分代码
CryptAcquireContext
使用
加密上下文
旗帜。
-
用错误检测问题
NTE_BAD_KEY_状态
(0x8009000b)并包括从中删除或临时重命名文件的代码部分
%APPDATA%\Microsoft\Crypto\DSS\[SID]
包含损坏的默认密钥容器。要检测文件名,我想您可以尝试使用
CryptGetProvParam
使用
PP_UNIQUE_CONTAINER
或/和
PP_ENUMCONTAINERS
参数。
很抱歉,我的答案太长了,感谢所有能读到这里的人。:-)
我希望我的答案能帮助你解决问题,改进你开发的软件。