惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Jina AI
Jina AI
H
Help Net Security
月光博客
月光博客
Y
Y Combinator Blog
I
InfoQ
博客园 - Franky
W
WeLiveSecurity
The Register - Security
The Register - Security
博客园 - 叶小钗
Google DeepMind News
Google DeepMind News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Blog — PlanetScale
Blog — PlanetScale
H
Hacker News: Front Page
L
LangChain Blog
S
Security @ Cisco Blogs
Hacker News - Newest:
Hacker News - Newest: "LLM"
罗磊的独立博客
V2EX - 技术
V2EX - 技术
H
Heimdal Security Blog
博客园_首页
SecWiki News
SecWiki News
N
News and Events Feed by Topic
B
Blog RSS Feed
S
Secure Thoughts
TaoSecurity Blog
TaoSecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
AI
AI
Google Online Security Blog
Google Online Security Blog
MongoDB | Blog
MongoDB | Blog
T
Troy Hunt's Blog
A
About on SuperTechFans
WordPress大学
WordPress大学
小众软件
小众软件
The Last Watchdog
The Last Watchdog
J
Java Code Geeks
PCI Perspectives
PCI Perspectives
博客园 - 聂微东
N
News | PayPal Newsroom
Schneier on Security
Schneier on Security
Help Net Security
Help Net Security
Martin Fowler
Martin Fowler
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
U
Unit 42
博客园 - 司徒正美
Forbes - Security
Forbes - Security
酷 壳 – CoolShell
酷 壳 – CoolShell
S
Security Affairs

博客园 - 兵

C#异常处理的方式 Firefox中Iframe的blur与focus事件问题 .NET中几种基本的线程同步方法 为什么说WinForm的控件只能在主线程中创建和调用 C#与Native C++互相访问 PHP5中的引用 Windows版的PDO太衰了 Framework里某些类型成员方法的一点疑惑 发现一个FireFox的问题 C#中COM操作(二)---接口查询 C#中COM操作(一)---实例化 非弹出式的模态对话框的背景遮罩 DOTNET事件拾遗 CSS开发辅助工具:Internet Explorer Developer Toolbar 另外一种高效地判断奇数和偶数的方法 如何利用客户端缓存对网站进行优化? 一个多表查询引出的问题(2) 类的成员初始化顺序 一个多表查询引出的问题(1)
CryptoAPI与.NET数字签名类的交互问题
· 2009-06-18 · via 博客园 - 兵

      最近做了一个Web项目,服务端采用ASP.NET。浏览器中需要用到证书登录,为了使以后能够读取USBKey证书和密钥,所以使用了ActiveX技述。这中间遇到了好多的问题,不过最后都一个一个解决了。

      首先是从客户机里导出证书和密钥的问题,基本的CryptoAPI提供了两个函数可以导出,一个是CryptSaveStore,另外一个是PFXExportCertStore函数。网上关于这两个函数导出证书的代码都有很多了,我也不想再在这里写。不过这两个函数都有一些问题,用起来我都不太满意。第一个函数也就是CryptSaveStore导出的证书怎么也不能用Windows向导导入到证书管理器中,这个问题折腾了我好久,最终没能解决只得放弃;第二个函数也就是PFXExportCertStore,这个函数虽说能导出证书可是只能导出PFX格式的,也就是可能包括了私钥(如果原本有的话)在内的证书,而且还得给个导出密码,重新导入的时候还得再次输入相同的密码。我想要做的是只导出证书文件本身,不导出私钥,对它用私钥签名,任何拿到证书的人都可以用证书上带的公钥进行验证证书的合法性,而且检验的方式越简单越好。所以上面的两种方式都不行。又查资料又Coding和Debug,熬了两个晚上终于发现了一个传说中的函数CryptUIWizExport ,这个函数在MSDN里面也有介绍,只不过不在CryptoAPI系列中。它的强大之处是可以调用证书导出向导导出证书,功能和直接使用证书管理器一模一样,可以导出各种格式的证书,而且还支持使用参数方式,也就是说可以无窗口界面。在我仔细看了该函数的用法后直接晕死。原来一开始的时候就不知在哪里见到过这个函数,只是看到名字里带个UI以为是会出现交互界面的,而我的导出证书的过程是在提交表单的时候后台自动完成并签名的,所以不能有交互界面的。没想到这个还可以没有交互的方式使用。只不过有一点小小的不足是导出的只能导成文件,不能导入到内存中,不过这也没什么了。只需要生成到临时文件,再读到内存中就可以了。附上一段代码。

 1function TX509Certificate.ExportCert: TBytes;
 2var
 3  ExportInfo:CRYPTUI_WIZ_EXPORT_INFO ;
 4  ContextInfo: CRYPTUI_WIZ_EXPORT_CERTCONTEXT_INFO;
 5  TempPath: array [0..MAX_PATH] of WideChar;
 6  TempFile: array [0..MAX_PATH] of WideChar;
 7  FS: TFileStream;
 8begin
 9  ZeroMemory(@ExportInfo, sizeof(CRYPTUI_WIZ_EXPORT_INFO));
10  ZeroMemory(@ContextInfo, sizeof(CRYPTUI_WIZ_EXPORT_CERTCONTEXT_INFO));
11
12  if (GetTempPath(MAX_PATH, @TempPath) = 0or
13    (GetTempFileName(@TempPath, 'tmp'0, @TempFile) = 0then
14  begin
15    raise Exception.Create('创建临时文件失败');
16  end;
17
18  ExportInfo.dwSize               := sizeof(CRYPTUI_WIZ_EXPORT_INFO);
19  ExportInfo.pwszExportFileName   := @TempFile;
20  ExportInfo.dwSubjectChoice      := CRYPTUI_WIZ_EXPORT_CERT_CONTEXT;
21  ExportInfo.Union.pCertContext   := Self.m_pCertContext;
22
23  ContextInfo.dwSize              := sizeof(CRYPTUI_WIZ_EXPORT_CERTCONTEXT_INFO);
24    ContextInfo.dwExportFormat      := CRYPTUI_WIZ_EXPORT_FORMAT_DER;
25    ContextInfo.fExportChain        := FALSE;
26    ContextInfo.fExportPrivateKeys  := FALSE;
27    //ContextInfo.pwszPassword        := nil;
28    //ContextInfo.fStrongEncryption  = TRUE;
29
30
31  try
32    if not CryptUIWizExport(
33      CRYPTUI_WIZ_NO_UI or CRYPTUI_WIZ_IGNORE_NO_UI_FLAG_FOR_CSPS,
34      0nil,
35      @ExportInfo,
36      @ContextInfo) then raise Exception.Create('导出客户端证书失败');
37
38    FS := TFileStream.Create(string(@TempFile), fmOpenRead);
39    SetLength(Result, FS.Size);
40    FS.Read(Result[0], FS.Size);
41  finally
42    DeleteFile(string(@TempFile));
43    if Assigned(FS) then
44      FreeAndNil(FS);
45  end;
46end;

       证书导出来了就该进行签名了。这里主要用到了CryptAcquireCertificatePrivateKey,CryptCreateHash,CryptSignHash这三个函数,都比较简单,也没什么好说的,MSDN上都写的很清楚了。

      原本以为为完成签名和签名验证是件很容易的事情,可是真正做的时候确遇到了新的问题。 CryptoAPI导出证书的签名不能通过.NET的签名验证。在.NET里面采用同样的证书和私钥签完名后的值也与CryptoAPI得到的结果不一样,难道说这两种操作方式不能互相兼容?

      带着这个问题我在网上查了大量的资料。中文的这方面的资料就很少,感觉好像是很少有人遇到过这样的问题似的,不像那些证书怎么从证书管理器中读取出来类的问题满天飞。看来这种平台交互的加密签名还是很少人做呀。又是熬了两个晚上的时间(这个时候总是觉得时间过得这么快,不够用),后来在偶然发现一个国外的BBS上有个老外也提到了相同的问题。下面有很多人跟帖,大都是说方法不正确呀,密钥不配对呀什么的。有一个人的回帖比较有意思,说的是这两种平台用的签名数据的字节顺序不一致,一个是Little-Endian,一个是Big-Endian。一开始我也没有当回事,以为就是随便胡说的。后台第N次查看MSDN的时候发现这么一段话:

同 Microsoft Cryptographic API (CAPI) 相互操作

与非托管 CAPI 中的 RSA 实现不同的是,RSACryptoServiceProvider 类会在加密之后、解密之前颠倒被加密数组的字节顺序。默认情况下,CAPI CryptDecrypt 函数无法解密由 RSACryptoServiceProvider 类加密的数据,RSACryptoServiceProvider 类无法解密由 CAPI CryptEncrypt 方法加密的数据。

如果在 API 之间互相操作时没有对颠倒的顺序进行补偿,RSACryptoServiceProvider 类会引发 CryptographicException

要同 CAPI 相互操作,必须在加密数据与其他 API 相互操作之前,手动颠倒加密字节的顺序。通过调用 Array..::.Reverse 方法可轻松颠倒托管字节数组的顺序。

       于是我在调用签名验证前把签名后的数据用Array.Reverse反转了一下,结果这回就通过验证了。这个发现让我大跌眼镜,这么个小问题折腾了我两个晚上。