天天看點

白話數字簽名(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

繼續閱讀