天天看点

白话数字签名(3)——Web程序中的数字签名

摘要

阅读本文并探索

    - 如何突破Web程序无状态性这个让人抓狂的障碍实现自动显示签名结果和批量签名功能。

    - 如何将签名功能封装到一个实现了IHttpHandler接口的类库中,使Client端的代码尽可能的简单。

    - 使用数字签名API函数需要注意的几个问题。

本文介绍在Web程序中使用数字签名所遇到的特殊困难和解决方法,并给出一个超简单但相当实用的DEMO。

DEMO程序的效果

让我们先来看看实现之后的效果。

白话数字签名(3)——Web程序中的数字签名
白话数字签名(3)——Web程序中的数字签名

让Client端代码尽可能的简单

我们将数字签名操作的复杂性全部封装到一个命名空间为mylib.util.lnca的类库中,类库只暴露一个名为Signer的类。

Signer的Client (本例中的Default.aspx)的职责只有

    - 构造一个含有待签名的数据的Dictionary作为Signer的输入,然后调用Signer.do_sign()函数进行数字签名。

    - 在页面上放置一个专门用于取得并显示签名结果的按钮,并将这个按钮的ClientID传递给Signer,这样Signer在完成签名后就可以自动触发这个按钮。在将程序发布给最终用户时,要把这个按钮的top属性设为-10000,这样最终用户就看不到这个按钮了。

Default.aspx 的设计视图的截图

白话数字签名(3)——Web程序中的数字签名

Default.aspx 的源代码如下

白话数字签名(3)——Web程序中的数字签名
白话数字签名(3)——Web程序中的数字签名

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>

白话数字签名(3)——Web程序中的数字签名
白话数字签名(3)——Web程序中的数字签名

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的注册:

白话数字签名(3)——Web程序中的数字签名
白话数字签名(3)——Web程序中的数字签名

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的代码有些长,我们会在后面讨论它的几个要点。

白话数字签名(3)——Web程序中的数字签名
白话数字签名(3)——Web程序中的数字签名

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>签名中

白话数字签名(3)——Web程序中的数字签名

</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>正在签名

白话数字签名(3)——Web程序中的数字签名

" + 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语句。我把它们放在了类库的配置文件里,它们的代码如下:

白话数字签名(3)——Web程序中的数字签名
白话数字签名(3)——Web程序中的数字签名

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" />

白话数字签名(3)——Web程序中的数字签名
白话数字签名(3)——Web程序中的数字签名

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程序的这种无状态性没少让我们吃亏。

白话数字签名(3)——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事件。

白话数字签名(3)——Web程序中的数字签名

思考题

我们使用一个“伪隐藏”的按钮可以简单地实现自动显示签名结果的效果,不过这种作法似乎有点土。你能否使用其它更“高级”的方法来实现同样的效果?

实现批量签名

我们需要让用户按一次按钮,就可以签名 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条数据的过程。

白话数字签名(3)——Web程序中的数字签名

思考题

我们的DEMO实现了第1种方法,你能否实现第2种方法?这两种实现方法各有什么优缺点?

综合起来

我们把批量签名与自动显示签名结果的功能都放在Signer.cs中,可以用下面这个经过简化的时序图来表示。

白话数字签名(3)——Web程序中的数字签名

附录 数字签名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 拼音输入法键入。

转载于:https://www.cnblogs.com/1-2-3/archive/2007/10/08/colloquialism-digital-certificate-part3.html

继续阅读