摘要
阅读本文并探索
- 如何突破Web程序无状态性这个让人抓狂的障碍实现自动显示签名结果和批量签名功能。
- 如何将签名功能封装到一个实现了IHttpHandler接口的类库中,使Client端的代码尽可能的简单。
- 使用数字签名API函数需要注意的几个问题。
本文介绍在Web程序中使用数字签名所遇到的特殊困难和解决方法,并给出一个超简单但相当实用的DEMO。
DEMO程序的效果
让我们先来看看实现之后的效果。
让Client端代码尽可能的简单
我们将数字签名操作的复杂性全部封装到一个命名空间为mylib.util.lnca的类库中,类库只暴露一个名为Signer的类。
Signer的Client (本例中的Default.aspx)的职责只有
- 构造一个含有待签名的数据的Dictionary作为Signer的输入,然后调用Signer.do_sign()函数进行数字签名。
- 在页面上放置一个专门用于取得并显示签名结果的按钮,并将这个按钮的ClientID传递给Signer,这样Signer在完成签名后就可以自动触发这个按钮。在将程序发布给最终用户时,要把这个按钮的top属性设为-10000,这样最终用户就看不到这个按钮了。
Default.aspx 的设计视图的截图
Default.aspx 的源代码如下
Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>无标题页</title>
</head>
<body>
<form id="form1" runat="server">
<table>
<tr>
<td>
<asp:Button ID="do_sign_button" runat="server" Text="签名" OnClick="do_sign_button_Click" />
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label></td>
<td>
<asp:Button ID="show_signed_data_button" runat="server" Text="显示签名结果(自动)" OnClick="show_signed_data_button_Click" /></td>
</tr>
<tr>
<td style="border: solid 1px black; vertical-align: top;">
<asp:GridView ID="sign_candidates_gridview" runat="server" Caption="签名前的数据" CellPadding="4"
ForeColor="#333333" Font-Names="宋体" Font-Size="10pt">
<FooterStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" />
<RowStyle BackColor="#E3EAEB" />
<EditRowStyle BackColor="#7C6F57" />
<SelectedRowStyle BackColor="#C5BBAF" Font-Bold="True" ForeColor="#333333" />
<PagerStyle BackColor="#666666" ForeColor="White" HorizontalAlign="Center" />
<HeaderStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" />
<AlternatingRowStyle BackColor="White" />
</asp:GridView>
</td>
<td style="border: solid 1px black; vertical-align: top;">
<asp:GridView ID="signed_data_gridview" runat="server" Caption="签名结果" BackColor="LightGoldenrodYellow"
BorderColor="Tan" BorderWidth="1px" CellPadding="2" ForeColor="Black" OnRowDataBound="signed_data_gridview_RowDataBound" Font-Names="宋体" Font-Size="10pt">
<FooterStyle BackColor="Tan" />
<SelectedRowStyle BackColor="DarkSlateBlue" ForeColor="GhostWhite" />
<PagerStyle BackColor="PaleGoldenrod" ForeColor="DarkSlateBlue" HorizontalAlign="Center" />
<HeaderStyle BackColor="Tan" Font-Bold="True" />
<AlternatingRowStyle BackColor="PaleGoldenrod" />
</asp:GridView>
</td>
</tr>
</table>
</form>
</body>
</html>
Default.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
using mylib.util.lnca;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void do_sign_button_Click(object sender, EventArgs e)
{
// 这是待签名的数据,保存在一个Dictionary中。Key为数据的ID,Value 为待签名的数据。
Dictionary<string, string> sign_candidates = new Dictionary<string, string>();
sign_candidates.Add("1", "123.45");
sign_candidates.Add("2", "678.90");
sign_candidates.Add("3", "zhf");
sign_candidates.Add("4", "7788");
sign_candidates.Add("5", "1-2-3");
sign_candidates.Add("6", "cnblogs");
sign_candidates_gridview.DataSource = sign_candidates;
sign_candidates_gridview.DataBind();
// 调用 Signer.do_sign() 进行签名
Signer.do_sign(Page, show_signed_data_button.ClientID, sign_candidates);
}
protected void show_signed_data_button_Click(object sender, EventArgs e)
{
if (Signer.error_code == 0) // 签名成功
{
signed_data_gridview.DataSource = Signer.signed_datas;
signed_data_gridview.DataBind();
}
else // 签名失败
{
Label1.Text = Signer.error_message;
}
}
protected void signed_data_gridview_RowDataBound(object sender, GridViewRowEventArgs e)
{
// 每个签名结果的长度都要将近2000个字符,会把GridView撑得很大,为了方便写Blog时截图,我
// 加了一个滚动条,实际作程序时是不需将签名数据显示给用户看的,也就用不着这段代码了。
if (e.Row.RowType == DataControlRowType.DataRow)
{
string content = e.Row.Cells[1].Text;
e.Row.Cells[1].Text = "<div style='overflow: auto; width: 300px; height: 150px;'>" + content + "</div>";
}
}
}
由于Signer是一个HTTP 处理程序,所以需要在Web.config中添加一行对Signer.ashx的注册:
Web.config
<?xml version="1.0"?>
<configuration>
<appSettings/>
<connectionStrings/>
<system.web>
<httpHandlers>
<add path="Signer.ashx" verb="*" type=" mylib.util.lnca.Signer, mylib.util.lnca" validate="false"/>
</httpHandlers>
</system.web>
</configuration>
有关HTTP处理程序的创建和应用,可以看《实战 HTTP 处理程序(HTTP Handler)系列》。
由于我们把复杂性都放在了Signer.cs中,Signer.cs的代码有些长,我们会在后面讨论它的几个要点。
Signer.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Web;
5 using System.Web.UI;
6 using System.Web.SessionState;
7 using mylib.util.lnca.Properties;
8 using System.Diagnostics;
9
10 namespace mylib.util.lnca
11 {
12 public class Signer : IHttpHandler, IRequiresSessionState
13 {
14 /// <summary>
15 /// Opens a new window to do signing.
16 /// Example
17 /// Dictionary<string, string> sign_candidates = new Dictionary<string, string>();
18 /// sign_candidates.Add("1", "123.45");
19 /// sign_candidates.Add("2", "678.90");
20 /// sign_candidates.Add("3", "zhf");
21 /// sign_candidates.Add("4", "7788");
22 /// sign_candidates.Add("5", "1-2-3");
23 /// sign_candidates.Add("6", "cnblogs");
24 /// Signer.do_sign(Page, on_finished_signing_button.ClientID, sign_candidates);
25 /// </summary>
26 public static void do_sign(Page page, string on_signing_finished_button_client_id,
27 Dictionary<string, string> sign_candidates)
28 {
29 Debug.Assert(sign_candidates != null, "sign_candidates should not be null");
30
31 Signer.sign_candidates = sign_candidates;
32 Signer.sign_candidates_enumerator = Signer.sign_candidates.GetEnumerator();
33 Signer.on_signing_finished_button_client_id = on_signing_finished_button_client_id;
34
35 // initializes Signer.signed_datas
36 Signer.signed_datas = new Dictionary<string, string>();
37 Signer.signing_counter = 0;
38 foreach (string key in sign_candidates.Keys)
39 {
40 Signer.signed_datas.Add(key, "");
41 }
42
43 error_code = 0;
44 error_message = "";
45
46 if (!page.ClientScript.IsStartupScriptRegistered(page.GetType(), "OpenSignerWindow"))
47 {
48 page.ClientScript.RegisterStartupScript(page.GetType(), "OpenSignerWindow",
49 "window.open('Signer.ashx', 'Signer_ashx', 'height=100, alwaysRaised=true, width=200, top=300, left=400, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=no, status=no');", true);
50 }
51 }
52
53 /// <summary>
54 /// Stores error_code during signing.
55 /// </summary>
56 public static long error_code
57 {
58 get { return (long)System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.error_code"]; }
59 set { System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.error_code"] = value; }
60 }
61
62 /// <summary>
63 /// Stores error_message during signing.
64 /// </summary>
65 public static string error_message
66 {
67 get { return System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.error_message"] as string; }
68 set { System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.error_message"] = value; }
69 }
70
71 /// <summary>
72 /// Stores datas had be signed.
73 /// </summary>
74 public static Dictionary<string, string> signed_datas
75 {
76 get { return System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.signed_datas"] as Dictionary<string, string>; }
77 set { System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.signed_datas"] = value; }
78 }
79
80 /// <summary>
81 /// The enumerator of sign_candidates
82 /// </summary>
83 private static IEnumerator<KeyValuePair<string, string>> sign_candidates_enumerator
84 {
85 get { return System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.sign_candidates_enumerator"] as IEnumerator<KeyValuePair<string, string>>; }
86 set { System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.sign_candidates_enumerator"] = value; }
87 }
88
89 /// <summary>
90 /// Datas needs signing.
91 /// </summary>
92 private static Dictionary<string, string> sign_candidates
93 {
94 get
95 {
96 return System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.sign_candidates"] as Dictionary<string, string>;
97 }
98 set
99 {
100 System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.sign_candidates"] = value;
101 }
102 }
103
104 // Returns the current signing progress.
105 private static int signing_counter
106 {
107 get { return (int)System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.signing_counter"]; }
108 set { System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.signing_counter"] = value; }
109 }
110
111 /// <summary>
112 /// Returns javascript code called doSignData() function at client.
113 /// <param name="sourceData">想要进行签名的数据</param>
114 /// <param name="signAlgo">签名算法,推荐使用SignAlgo.RSA_MD5</param>
115 /// <param name="isAddSignCert">是否在结果中携带证书</param>
116 /// <param name="isAddSrcData">是否在结果中携带原文</param>
117 /// <param name="innerOid">数据类型,使用InnerOid.EMPUTY即可</param>
118 /// <param name="isAddTime">是否添加签名时间</param>
119 /// <param name="pin">用户口令,如果设为空字符串,则每次都会提示用户输入密码;如果在此参数中传入了正确的密码,则只要求用户输入密码一次.</param>
120 /// </summary>
121 private string sign_data(string source_data, string sign_algo, bool is_add_sign_cert, bool is_add_src_data, string inner_oid, long is_add_time, string pin)
122 {
123 return string.Format("doSignData('{0}', '{1}', {2}, {3}, '{4}', {5}, '{6}');",
124 source_data,
125 sign_algo,
126 is_add_sign_cert == true ? "1" : "0",
127 is_add_src_data == true ? "1" : "0",
128 inner_oid,
129 is_add_time,
130 pin);
131 }
132
133 /// <summary>
134 /// Alias of sign_data(source_data, SignAlgo.RSA_MD5, is_add_sign_cert, true, InnerOid.EMPUTY, 0, string.Empty)
135 /// </summary>
136 private string sign_data(string source_data, bool is_add_sign_cert)
137 {
138 return sign_data(source_data, SignAlgo.RSA_MD5, is_add_sign_cert, true, InnerOid.EMPUTY, 0, string.Empty);
139 }
140
141 /// <summary>
142 /// Alias of sign_data(source_data, SignAlgo.RSA_MD5, true, true, InnerOid.EMPUTY, 0, string.Empty);
143 /// </summary>
144 private string sign_data(string source_data)
145 {
146 return sign_data(source_data, SignAlgo.RSA_MD5, true, true, InnerOid.EMPUTY, 0, string.Empty);
147 }
148
149 /// <summary>
150 /// Returns signed data from HiddenFields in form.
151 /// </summary>
152 private string get_signed_data(HttpRequest request)
153 {
154 if (get_error_code(request) != 0)
155 {
156 return string.Empty;
157 }
158
159 if (request.Form["Signer_SubPage_SignedData_HiddenField"] == null)
160 {
161 return string.Empty;
162 }
163
164 return request.Form["Signer_SubPage_SignedData_HiddenField"].ToString();
165 }
166
167 /// <summary>
168 /// Returns error_code from the HiddenField in form.
169 /// </summary>
170 private long get_error_code(HttpRequest request)
171 {
172 if (request.Form["Signer_SubPage_ErrorCode_HiddenField"] == null)
173 {
174 return -1;
175 }
176
177 try
178 {
179 return long.Parse(request.Form["Signer_SubPage_ErrorCode_HiddenField"].ToString());
180 }
181 catch (Exception)
182 {
183 return -1;
184 }
185 }
186
187 /// <summary>
188 /// Returns error_message from the HiddenField in form.
189 /// </summary>
190 private string get_error_message(HttpRequest request)
191 {
192 if (get_error_code(request) == -1)
193 {
194 return "取得签名结果失败。";
195 }
196
197 if (request.Form["Signer_SubPage_ErrorMessage_HiddenField"] == null)
198 {
199 return string.Empty;
200 }
201
202 return request.Form["Signer_SubPage_ErrorMessage_HiddenField"].ToString();
203 }
204
205 /// <summary>
206 /// Stores the client_id of button of father page wants to auto trigger.
207 /// </summary>
208 private static string on_signing_finished_button_client_id
209 {
210 get { return System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.on_signing_finished_button_client_id"] as string; }
211 set { System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.on_signing_finished_button_client_id"] = value; }
212 }
213
214 public bool IsReusable
215 {
216 get { return true; }
217 }
218
219 public void ProcessRequest(System.Web.HttpContext context)
220 {
221 context.Response.Clear();
222 context.Response.ContentType = "text/html";
223 context.Response.Write("<!DOCTYPE html PUBLIC /"-//W3C//DTD XHTML 1.0 Transitional//EN/" /"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd/">");
224 context.Response.Write("<html xmlns=/"http://www.w3.org/1999/xhtml/" >" + Environment.NewLine);
225 context.Response.Write("<title>签名中</title>");
226 context.Response.Write("<form name=/"form1/" method=/"post/" id=/"form1/">" + Environment.NewLine);
227 context.Response.Write(Settings.Default.controls + Environment.NewLine);
228 context.Response.Write(Settings.Default.js + Environment.NewLine);
229
230 if (sign_candidates_enumerator.Current.Key != null) // not first reach here.
231 {
232 if (get_error_code(context.Request) == 0) // signing success
233 {
234 signed_datas[sign_candidates_enumerator.Current.Key] = get_signed_data(context.Request); // gathers signed data.
235 }
236 else // signing failed.
237 {
238 // sets error_code and error_message
239 error_code = get_error_code(context.Request);
240 error_message = get_error_message(context.Request);
241 // terminates signing
242 context.Response.Write("<script type='text/javascript'>" + Environment.NewLine);
243 context.Response.Write(
244 string.Format("opener.document.getElementById('{0}').click();", on_signing_finished_button_client_id));
245 context.Response.Write("window.close();");
246 context.Response.Write("</script>" + Environment.NewLine);
247 }
248 }
249
250 if (sign_candidates_enumerator.MoveNext())
251 {
252 signing_counter++;
253 context.Response.Write("<span>正在签名" + signing_counter.ToString() + "/" + sign_candidates.Count.ToString() + "</span>");
254 context.Response.Write("<script type='text/javascript'>" + Environment.NewLine);
255 context.Response.Write(sign_data(sign_candidates_enumerator.Current.Value) + Environment.NewLine);
256 context.Response.Write("</script>" + Environment.NewLine);
257 }
258 else
259 {
260 context.Response.Write("<script type='text/javascript'>" + Environment.NewLine);
261 context.Response.Write(
262 string.Format("opener.document.getElementById('{0}').click();", on_signing_finished_button_client_id));
263 context.Response.Write("window.close();");
264 context.Response.Write("</script>" + Environment.NewLine);
265 }
266 context.Response.Write("</form>" + Environment.NewLine);
267 context.Response.Write("</html>" + Environment.NewLine);
268 }
269 } // class Signer
270 } // namespace mylib.util.lnca
271
Signer.cs的第227和228行的“Settings.Default.controls”和“Settings.Default.js”是需要发送给客户端浏览器用于回传签名结果的HiddenFields和执行签名操作的Javascript语句。我把它们放在了类库的配置文件里,它们的代码如下:
Settings.Default.controls
1 <object id="subpage_Signer_SubPage_LNCAToolkits" classid="clsid:6FBE853D-0DB0-4C62-B7DC-B49E9D447AF9" style="width: 0px;
2 height: 0px;" codebase="http://172.16.1.1/LNCAToolkits.cab#version=2,8,6,0">
3 </object>
4 <input id="Signer_SubPage_ErrorCode_HiddenField" name="Signer_SubPage_ErrorCode_HiddenField" type="hidden" />
5 <input id="Signer_SubPage_ErrorMessage_HiddenField" name="Signer_SubPage_ErrorMessage_HiddenField" type="hidden" />
6 <input id="Signer_SubPage_SignedData_HiddenField" name="Signer_SubPage_SignedData_HiddenField" type="hidden" />
Settings.Default.js
1 <script type="text/javascript">
2 function doSignData(sourceData, signAlgo, isAddSignCert, isAddSrcData, innerOid, isAddTime, pin)
3 {
4 var caTool = document.getElementById("subpage_Signer_SubPage_LNCAToolkits");
5 var ret = caTool.EnumClientCert();
6
7 if(ret == 0)
8 {
9 var signedData = caTool.SignDataEx(sourceData,signAlgo,isAddSignCert,isAddSignCert,innerOid,isAddTime,pin);
10
11 document.getElementById("Signer_SubPage_ErrorCode_HiddenField").value = caTool.ErrorCode;
12 document.getElementById("Signer_SubPage_ErrorMessage_HiddenField").value = caTool.ErrorMessage;
13 document.getElementById("Signer_SubPage_SignedData_HiddenField").value = signedData;
14 }
15 else
16 {
17 document.getElementById("Signer_SubPage_ErrorCode_HiddenField").value = caTool.ErrorCode;
18 document.getElementById("Signer_SubPage_ErrorMessage_HiddenField").value = caTool.ErrorMessage;
19 document.getElementById("Signer_SubPage_SignedData_HiddenField").value = '';
20 }
21
22 document.forms[0].submit();
23 }
24 </script>
自动显示签名结果
我们想要实现这样的交互效果:用户选定想要进行签名的数据后,只要按一个按钮就会自动弹出一个小窗体显示签名的进度;当签名结束后,可以自动显示签名结果,就像上面那个DEMO程序所展示的那样。
如果我们开发的是WinForm的信息系统,实现这样的效果简直易如反掌。可是在Web程序中,我们却遇到了一点麻烦。
自动显示签名结果的困难
正如我们在第2篇所介绍的,为了防复制破解,我们是使用USB Key做数字签名。这个USB Key必须插在客户端的电脑上,我们在Server端无法直接控制它,只能通过在客户端浏览器上执行的javascript代码调用一个由辽宁CA认证中心开发的一个ActiveX控件操作USB Key进行签名,再将签名结果通过HiddenFields Post回Server端——不过这个Server端已经不是以前的Server端了,Web程序的这种无状态性没少让我们吃亏。
换句话说,我们没办法像下面这样写(伪码):
protected void do_sign_button_Click(object sender, EventArgs e)
{
1 Dictionary<string, string> sign_candidates = prepare_sign_candidates();
2 Dictionary<string, string> signed_datas = excute javascript at client browser to sign data, and return signed datas
3 signed_data_gridview.DataSource = signed_datas;
4 signed_data_gridview.DataBind();
}
实现自动显示签名结果
我们遇到的问题的实质是:准备签名数据(伪代码第1行)、显示签名结果(伪代码第3、4行)的操作在Server端进行;而使用USB Key进行签名的操作(伪代码第2行)必须在Client端的浏览器上执行,并且这两种操作是异步的!所以我们只能将显示签名结果的代码放到另一个函数中,在签名结束后以某种方法触发它。我们在Demo中所使用的方法是,将显示签名结果的代码放到“显示签名结果(自动)”按钮的Click事件中,在签名结束后,使用
javascript:opener.document.getElementById(show_signed_data_button.ClientID).click();
来触发这个按钮的Click事件。
思考题
我们使用一个“伪隐藏”的按钮可以简单地实现自动显示签名结果的效果,不过这种作法似乎有点土。你能否使用其它更“高级”的方法来实现同样的效果?
实现批量签名
我们需要让用户按一次按钮,就可以签名 n 条数据,可是数字签名API SignDataEx(sourcedata,...) 一次只能签名一条数据。我们需要遍历每条待签名数据,调用SignDataEx()进行签名。我们有两种选择:
1. 在Server端进行遍历,每次传送一条数据给Client端进行签名。
2. 将 n 条待签名数据一次全部传给Client端,在Client端使用javascript的for循环遍历待签名数据并进行签名。
我们在Demo程序中是使用了第1种方法。基于和“自动显示签名结果”一节所述的同样的困难,我们无法在Signer.cs的ProcessRequest()中这样写(伪码):
public void ProcessRequest(System.Web.HttpContext context)
{
foreach (string key in sign_candidates.Keys)
{
string signed_data = excute javascript at client browser to sign data, and return signed data
}
}
好在已经有大师发明了外部迭代器(external iterator),我们可以在第一次迭代之前,先创建一个待签名数据的一个外部迭代器,并把它保存在Session中。每次签名后,Client端PostBack回Server端,在Server端从Session中取出这个外部迭代器,调用sign_candidates_enumerator.MoveNext(),之后继续向Client端发送签名用的javascript语句,直至完成全部遍历,请参见Signer.cs的250~268行。下面的时序图表示批量签名3条数据的过程。
思考题
我们的DEMO实现了第1种方法,你能否实现第2种方法?这两种实现方法各有什么优缺点?
综合起来
我们把批量签名与自动显示签名结果的功能都放在Signer.cs中,可以用下面这个经过简化的时序图来表示。
附录 数字签名API简介
我们使用的是辽宁省数字认证中心发放的数字证书。他们还提供了两套数字签名API:一个是ActiveX控件;一个是COM组件。两套API都有完整、丰富的数字签名相关的函数,可以单独使用。如果是WinForm程序,直接使用COM组件即可。不过由于Web程序必须使用ActiveX控件,所以我们在作数字签名的时候使用ActiveX控件,在验证签名的时候使用COM组件。也许您手头的API和我们使用的API并不相同,不过您仍然可以下载这两套API的手册找找感觉。
LNCAToolkits 控件(通用版)程序员手册_v2.pdf <- 这个是ActiveX控件的手册
LNCA-CryptoAPI-Com版程序员手册_v1.pdf <- 这个是COM组件的手册
作数字签名的API函数是SignDataEx()。
函数声明: BSTR SignDataEx (BSTR szSrc,
BSTR sSignAlgo,
long IsAddSignCert,
long IsAddSrcData,
BSTR szInnerOid,
long IsAddTime,
BSTR pPin);
说明:进行签名数据操作(使用客户端证书)。
参数:
.. szSrc :原文数据
.. sSignAlgo:指定签名算法
szOID_OIWSEC_sha1 = “1.3.14.3.2.26”
szOID_RSA_MD5 = “1.2.840.113549.2.5”
szOID_RSA_MD2 = “1.2.840.113549.2.2”
.. IsAddSignCert 是否在结果中携带证书
0 = 不携带证书
1 = 携带证书
.. IsAddSrcData 是否在结果中携带原文
0 = 不携带原文
1 = 携带原文
.. szInnerOid:数据类型OID,(默认:NULL)
szOID_TSP_TSTInfo = "1.2.840.113549.1.9.16.1.4"
.. IsAddTime:是否添加签名时间
0: 不进行时间编码
1: 取当前系统时间,进行时间编码
2、从时间戳服务器取得时间,必须首先设置时间戳服务器URL。请参考7.14
章节的时间戳操作。
.. pPin:用户Key 口令
如果输入正确的口令,则不弹出输入口令窗口,直接签名数据。
如果输入错误的口令,则弹出输入口令窗口
其中:通过InputDataType 属性来指定原文数据格式
0 = 输入原文为二进制编码,此函数内部不进行转码
1 = 输入原文为BASE64 编码,此函数内部进行转码
返回值:成功时返回签名数据(BASE64 编码),
失败时返回空,由ErrorCode 属性中取错误码,由ErrorMessage 属性中取错误信
息。
需要说明的是IsAddSignCert这个参数。它指示是否在签名数据中携带证书。
携带证书的优点:如果选择携带证书,在验证签名时就不用再向验证签名的函数显式传递一个证书。验证签名的函数会自动从签名数据中解析出证书,然后验证签名,这在编程上无疑是非常方便的!如果选择不携带证书,我们就必须将系统所有用户的证书保存在一个“证书表”中,再在含有签名数据字段的表中创建一个专门保存“证书表”ID的字段,在验证签名前要从“证书表”中取得证书,再验证签名。
携带证书的缺点:缺点是会使签名数据比较长,例如对“1234”签名的Base64编码会有1942个字符;而如果不携带证书只有560个字符。所以如果客户十分吝啬数据库的存储空间,就需要使用不携带证书的方式。不过我个人是十分喜欢携带证书的方式的(偶是懒人^_^)
还有就是IsAddSrcData这个参数应该指定为“携带原文”,这样在验证签名的时候(使用COM组件的VerifySign函数)就不用再给出原文了(SourceData参数设为null),而且VerifySign()函数在验证成功后会返回原文,这样还可以向用户显示这样的信息:“看,以前签名的是这个数据(VerifySign()函数返回的原文),现在被改成了这个数据(表中原文字段中的值),所以验证失败”。
本篇源代码下载
本篇源代码。运行本篇的DEMO需要预先安装签名用的ActiveX控件:ActiveX_bin_v2.8.6.0_20061130.rar,解压缩后运行reg_ActiveX.bat即可完成安装,还有你的 USB Key要支持这个ActiveX控件才行。
致谢
非常感谢辽宁省数字认证中心软件开发部项目经理张铁夫先生的指导和帮助。每次电话咨询都能得到他的耐心讲解,即使我们已经试用了半年多仍然没有购买一个数字证书^_^
感谢一直关注本系列的各位同仁,大家的鼓励和指导令我受益匪浅。感谢古巴、Clark Zheng、菜菜灰在Http 处理程序方面对我的指导。感谢笑望人生、蛙蛙池塘、大石头、aspnetx、银河、慢一拍、游民一族、yoyolion对本系列的补充和指正。
工具箱
本系列的所有流程图均使用Visio 2003绘制。
UML 时序图使用Dia v0.96.1绘制。
抓图软件使用的是SnigIt v7.1.1。图片上使用了手写字体方正静蕾简体。
图片预览和格式转换使用了ACDSee v5.0。
文字部分使用Google 拼音输入法键入。