使用C#讀取自然人憑證(MOICA)及數位簽章
最近在開發電子公文系統的線上簽核功能,雖然學校幾年前就已經採購了一批32K RSA Smartcard,也已經可以做一些應用了;但若教職員忘了帶卡片,就沒辦法簽公文了。
因此想多一個「自然人憑證」做數位簽章的功能,讓使用者可以二選一。接著依自然人憑證網站上的申請方法,取得了MOICA API,壓縮檔中包含了DLL、文件、範例(C++),這些DLL都是Unmanaged,因此沒辦法直接讓C#來引用參考。
以下是我寫出來的一些功能,分享出來給大家參考,其中包含PIN碼驗證、取出卡片中的憑證、將訊息丟到卡片做數位簽章、驗證由自然人憑證所簽出來的Signature、驗證身分。由於這些API(DLL)是有版權的(內政部所有),因此不能放上來給大家抓,大家有需要API的話,請至自然人憑證網站申請,申請完畢後,幾天之後,內政部憑證管理中心就會mail給你一組帳號密碼,你就能上去Download了!Download下來後,把那些DLL和你的程式放在同一個目錄下,就可執行了(不必再安裝SafeSign軟體,因為API是用PKCS#11,而不是CSP)。
目前所使用的API版本是5.3,內政部近期將更新為6版,是為了要支援2048-bit的卡片,但把第6版的DLL放到我的程式執行,會有一些衝突,我有機會再來測試看看。目前所遇到的問題是:
- 放到x64的OS(XP, Vista)會無法執行,打電話去內政部詢問,他們也無解(傳給我第6版API,但我測了還是不能使用)
- 使用OCSP時,永遠都回應「2」(找不到憑證),測了三天還是試不出來,再找時間測測LDAP的方式來驗是否被Revoke
To內政部、中華電信:以下的程式是我自己寫出來的,若部分程式碼有侵權,絕對不是故意的,請通知我,立即移除。
2011/6/28註:
由於新書已包含此原始碼,有違著作合約,因此將原始碼移除;若您有需求,拜託拜託請買我們的新書:
資訊安全實務:數位憑證技術與應用
http://www.tenlong.com.tw/items/9572180983?item_id=324442
http://goods.ruten.com.tw/item/show?21106198965334
書裡介紹的,會比這裡還詳盡,可省去您找資料的寶貴時間,一本三百多,保證買了不會後悔。
若您有任何問題,歡迎與我聯絡,謝謝您的支持。
fongming 說:
hi

不好意思我又來了
試了一下您的code之後,果然是可以正常得到ost的回傳值
但是很奇怪的也跟您一樣certStatus永遠都是2
這點真的是不知道為什麼,我是拿我自己的卡試的= =
難道說這個OCSP是不能用的嗎
另外一點就是回到我最原始的問題,之前我說我會有一個
-603912959的err code, 當時是用vb.net寫的
後來把您的code翻成vb.net也會有同樣error
我對照了一下我原始的code跟您的,發覺差異的地方就在於
一些宣告的部份,現在我還沒有找到原因,如果您熟悉vb.net的話
是否也能指教一下呢? 我試著把vb的code貼一下
_
Private Shared Function BuildTobesignedOCSPRequest(ByRef OCSp As OCSstruct, ByVal num As Integer, _
ByVal Nounce As String, ByVal iNounceLength As Integer, _
ByRef RequestIssueCertificate As Byte(), ByRef ucppTobesignedOCSPReq As Byte(), _
ByRef iTobesignedOCSPReqLength As Integer) As Integer
End Function
_
Public Structure OCSstruct
_
Public SerialNumber As Byte() '憑證序號
Public ResponseStatus As Integer 'OCS之Response狀態
Public CertStatus As Integer '憑證狀態
Public RevokeReason As Integer '廢止理由代碼
_
Public RequestTime As Byte() 'Request時間
End Structure
Public Shared Function VerifyCertViaOCSP(ByVal certCA As X509Certificate2, ByVal certOCSP As X509Certificate2, ByVal certUser As X509Certificate2) As OCSstruct
Dim ocs As New OCSstruct()
Dim strSN As String = certUser.SerialNumber
ocs.SerialNumber = New Byte(19) {}
For i As Integer = 0 To 19
If i <= strSN.Length \ 2 - 1 Then
ocs.SerialNumber(i) = Convert.ToByte(strSN.Substring(i * 2, 2), 16)
Else
ocs.SerialNumber(i) = Convert.ToByte("0", 16)
End If
Next
ocs.CertStatus = 0
ocs.ResponseStatus = 0
ocs.RevokeReason = 0
ocs.RequestTime = New Byte(31) {}
For i As Integer = 0 To 31
ocs.RequestTime(i) = Convert.ToByte("0", 16)
Next
Dim TBSReq As Byte() = New Byte(0) {}
Dim iTBSReqLength As Integer = 0
Dim iReturn As Integer = BuildTobesignedOCSPRequest(ocs, 1, "1234567890", 10, certCA.Export(X509ContentType.Cert), TBSReq, _
iTBSReqLength)
TBSReq = New Byte(iTBSReqLength - 1) {}
iReturn = BuildTobesignedOCSPRequest(ocs, 1, "1234567890", 10, certCA.Export(X509ContentType.Cert), TBSReq, _
iTBSReqLength)
Return ocs
End Function
在第一次呼叫BuildTobesignedOCSPRequest就會彈那個錯了
而且iTBSReqLength也沒法正常拿到值
至於結構的宣告,試過改用VBFixedArray 也沒有用
目前對於這點感到相當苦惱中
回應
Admin 回應:
三月 21st, 2011 at 14:52:03
我記得當時搞這個也搞很久…
後來發現查詢時間應為RequestTime,應為API開發人員將R誤植為P,因此只能將錯就錯。
因此OCSstruct裡的最後一個property,故意改為public fixed byte PequestTime[32];就可以了,您試試看?
回應
fongming 回應:
三月 21st, 2011 at 15:20:28
是的,我也是看到他sample code中似乎是打錯字來的~

但是我想因為他是memcpy進去的,那個打錯字似乎是沒有影響才對
VB版本的我可能再試試看, 如果真的不行就先參考您的c#版本了XD
另外,針對OCSP的certStatus回傳值是2
我剛剛得到中華電信的解釋
原來要丟進去的SerialNumber如果第一個byte為00,ㄧ定要拿掉= =
我試過,拿掉之後果真回傳值就是0了
至於為什麼要拿掉,我也不解, 我猜想可能是他們當初在做時
工商憑證跟自然人的都做同一套所以一定要ㄧ樣的規則吧
以下是他們mail給我的解釋:
GPKI中所使用的憑證序號是一個長度為16 bytes的正整數,
但根據DER編碼對正數所使用2補數規則,
所以若遇到憑證序號開頭是a_或b_或c_或d_或e_或f_,
則會在最開頭補上0(16進位顯示為00),因此憑證序號長度就變成17 bytes(例:MOEACA憑證序號);
若遇到憑證序號開頭是0~9,則憑證序號長度為16 bytes(例: GRCA自簽憑證)。
結論是:憑證序號長度有16 bytes和17 bytes兩種。
建議可以到MOEACA網站下載"GRCA自簽憑證"和"MOEACA憑證"來比較一下會更清楚。
GRCA自簽憑證
1f 9d 59 5a d7 2f c2 06 44 a5 80 08 69 e3 5e f6
MOEACA憑證
00 fd b3 f5 36 49 99 e6 4e 8c cb 36 62 12 90 e3 2b
回應
Admin 回應:
三月 21st, 2011 at 16:28:42
嗯!我也記得好像有序號長度的問題,有機會再來試試囉~~
fongming 說:
您好
我最近也在弄這塊,從您的程式學到不少東西!
現在碰到的問題是要弄OCSP時
呼叫他的BuildTobesignedOCSPRequest 都會彈一個-603912959的錯誤
不知道您之前有做這塊嗎
如果有的話是否能指教一下呢?感激不盡!
回應
Admin 回應:
三月 18th, 2011 at 20:09:48
弟近日將出版一本介紹數位憑證的書,以下是節錄第12章的部分內容,希望對您有幫助。
1. 先弄一個struct:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)]
unsafe public struct OCSstruct
{
public fixed byte SerialNumber[20];
public int ResponseStatus;
public int CertStatus;
public int RevokeReason;
public fixed byte PequestTime[32];
}
CertStatus回傳值的代表:
0:憑證存在且未廢止
1:憑證已經永久或暫時廢止
2:無此憑證(該憑證不是MOICA所發行)
2. 建立Entry Points:
[DllImport("CHTHiSECURE5_NetFuncva.dll")]
private static extern int BuildTobesignedOCSPRequest(
ref OCSstruct ocs,
int num, // OCS個數,填1即可
string Nounce, // OCSPRequest的Nounce欄位資料(填1~0)
int NounceLength, // Nounce的長度
byte[] RequestIssueCertificate, // MOICA憑證
byte[] ToBeSignedRequest, // 用來簽章的資料
ref int ToBeSignedRequestLength
);
[DllImport("CHTHiSECURE5_NetFuncva.dll")]
private static extern int QueryOCSfromOCSPRequest(
ref OCSstruct ocs,
int num,
byte[] ToBeSignedOCSPReq,
int ToBeSingedOCSPReqLength,
byte[] RequstSignature,
int RequestSignatureLength,
byte[] RequestIssueCertificate, // MOICA憑證
byte[] SenderCertificate, // 要檢查的憑證
byte[] OCSPServerCertificate, // OCSP憑證
string ServerURL, // OCSP網址
string ProxyName,
int ProxyPort);
3. 下面這個方法,用來呼叫API,請API幫我們檢驗指定的憑證是否有問題:
unsafe public static OCSstruct VerifyCertViaOCSP(X509Certificate2 certCA, X509Certificate2 certOCSP, X509Certificate2 certUser)
{
OCSstruct ocs = new OCSstruct();
string strSN = certUser.SerialNumber;
for (int i = 0; i < strSN.Length / 2; i++)
ocs.SerialNumber[i] = Convert.ToByte(strSN.Substring(i * 2, 2), 16);
byte[] TBSReq = new byte[0];
int iTBSReqLength = 0;
int iReturn = BuildTobesignedOCSPRequest(
ref ocs,
1,
"1234567890",
10,
certCA.Export(X509ContentType.Cert),
TBSReq,
ref iTBSReqLength);
TBSReq = new byte[iTBSReqLength];
iReturn = BuildTobesignedOCSPRequest(
ref ocs,
1,
"1234567890",
10,
certCA.Export(X509ContentType.Cert),
TBSReq,
ref iTBSReqLength);
iReturn = QueryOCSfromOCSPRequest(
ref ocs,
1,
TBSReq,
iTBSReqLength,
null,
0,
certCA.Export(X509ContentType.Cert),
certUser.Export(X509ContentType.Cert),
certOCSP.Export(X509ContentType.Cert),
"moica.nat.gov.tw",
null,
0);
return ocs;
}
4. 在專案屬性中,將「容許Unsafe程式碼」打勾
屆時出版,還請大家多多支持~
回應
fongming 回應:
三月 21st, 2011 at 09:18:29
真是太感謝您了,
小弟研究一下,另外也祝您的新書大賣!!
回應
Blave Huang 回應:
六月 24th, 2011 at 16:39:10
新書已出版囉~有需要的朋友們請參考參考囉~
http://www.blave.net.tw/599
nanwaychen 說:
Hi 請教一下
HiSecure 的API 有辦法在 DELPHI 下編譯成WIN32 的程式使用嗎!!
在 DELPHI 下 要呼叫DLL 都需要 自行去宣告的!!
有這方面的資料或宣告檔可提供參考嗎??
只是要單純讀出卡號和比對USER輸入的PINCODE 是否符合的功能即可!!
若不使用 HiSecure ~ 那使用微軟的CSP 方式是否也可做到??
回應
Admin 回應:
三月 18th, 2011 at 20:12:21
抱歉…現在才看到這則留言…
Delphi弟不熟,但應該還是可以Access CSP才對~
您再試試看囉~
回應
小目 說:
就是我是先建置一個VS專案
裡面有一個WEB FORM的專案,和一個CLASS的專案
我在WEB FORM去寫呼叫CLASS專案的METHOD
也就是您以上的範例來跑
它可以抓出憑證資料,並驗證PIN CODE
但我把此專案發行到本機的WEB,然後把VS開掉
用IIS的方式去呼叫剛才在VS可以RUN的網頁
它就抓不到憑證了~~
這和我沒包裝成ACTIVEX有關嗎
回應
Blave 回應:
八月 11th, 2010 at 16:14:16
我記得好像要包成activex,而且好像要用「簽署程式碼」的憑證去包activex
太久沒碰這塊了,不太記得了~
您有試出來,還請您分享給弟啊
回應
小目 說:
你好,請教一下
我用了這個範例去試
vs runtime 的時候,它可以抓到資料
但我把它發行到本機的網站去試,它就抓不到憑證了~
這是什麼問題呢
不是一樣都在本機嗎 @@
回應
Blave 回應:
八月 11th, 2010 at 15:37:35
抱歉…不太懂「它發行到本機的網站去試,它就抓不到憑證了」
可以詳加說明嗎?
回應
gattaca 說:
網路有一篇
自行開發之自然人憑證SDK(不需中華電信Hisecure API)
請問作者是你嗎?
有支援2048bit的新卡嗎?
回應
sk 說:
感謝!Blave Huang 這樣我有點方向了!
回應
sk 說:
您好 想請教您 自然人憑證要如何整合到 web的應用
簽章,驗證網站會員登入的部份
因完全摸不到頭緒~
麻煩您了~
回應
Blave 回應:
十一月 20th, 2009 at 14:24:25
要讓網頁可以驗證自然人憑證,通常都是要寫成ActiveX,用ATL或MFC來寫,您可以去自然人憑證申請MOICA API(要是機關單位,所以要發函),將這個API整合到您的ActiveX元件,就可以用了。
不過老實說,弟沒有寫過ActiveX,所以也沒辦法深入跟您分享了。
回應
Roy 說:
很抱歉又來打擾了!~
請問使用Win32的方法大概是怎樣的一個流程?
跟使用ActiveX有什麼樣的不同?
您所分享的函式庫範例中
[DllImport("ChtHiSECURE5_GPKICardFunction.dll")]是否為動態載入嗎?
這些DLL是不是要先註冊到Client的電腦?
非常感謝!!
回應
Blave Huang 回應:
七月 9th, 2009 at 12:12:20
我想Win32和ActiveX的流程應該都一樣,就是把資料塞給卡片,叫它給你一串的數位簽章,然後你就可以拿這串簽章值去儲存或其他應用…。
那些Dll必須放在Client的電腦裡面,要和.exe檔放在一起,或許放在system32目錄下也行,但我沒試過就是了。
回應
Kim 說:
沒想到本網頁有檔飆記語法
以下是我試過的
1.傳入一個的完整SignedInfo區塊
2.僅傳入DigestValue的值
回應
Kim 說:
Hi , 再請教個問題,簽章時呼叫的MakeSignature(),就我所知道的電子簽章是傳入一個訊息摘要,但究竟是要傳入何種形式的字串內容?就我測試過的簽出來都有點問題。以下是我試過的
1.傳入一個的完整tag區塊
2.僅傳入DigestValue的值
試問正確傳入簽章的 訊息摘要應該是哪一種呢?
回應
Roy 說:
我是將您的C# Class改成VB來進行開發(VS2005)
假設使用者電腦都有.net framework
我還是必須要用到ActiveX+Script嗎?有可能不用到嗎?
如果沒有其它方式,冒昧地請問有ActiveX及Script範例可以參考嗎?
我的Mail : iori945@gmail.com
回應
Blave Huang 回應:
七月 3rd, 2009 at 09:02:12
數位簽章一定要把程式放到Client執行,所以一定要用ActiveX才行!
我們的數位簽章是應用到電子公文系統,電子公文是用Win32的方式,所以我們沒有ActiveX的範例囉…所以也沒去研究ActiveX怎麼寫呢…
加油!!
回應
Kim 說:
多謝你的連結!
不過因為自然人憑證的私鑰的Key是無法直接取得的,所以MSDN上的範例不太適用。我剛查詢了,簽章取回的byte array 是需要以Base64String編碼的。
回應
Roy 說:
真的是非常的感謝您熱心的回答!
Smartcard Reader Driver 安裝無誤。
我是做web Form的程式,作法上會有什麼樣的不同嗎?
我只能用ActiveX + Script才能達到我要的功能嗎?
因為我不會ActiveX及Script,有其他的作法嗎?萬分感激!~
回應
Blave Huang 回應:
七月 2nd, 2009 at 16:32:10
原來您是用aspx啊…那麼數位簽章就一定要用activex了
建議用c++(mfc, atl)開發
不然用C#開發,使用者電腦如果沒有.net framework,還是一樣不能用…
回應
Roy 說:
黃先生 你好 :
我已經取得HiSECURE 5.3 Windows(C語言)標準版
但是更正過DLL檔後,Local及Server都無法執行
請問一下我要怎麼知道DllImport是否成功?
謝謝!~
回應
Blave Huang 回應:
七月 2nd, 2009 at 16:12:05
您好:
所有DLL檔和主程式.exe是否放在同一個目錄呢?
另外,Smartcard Reader Driver是否安裝成功?
正常而言應該是可以執行的,而且讀卡機的燈會閃…
是不是有遺漏了什麼呢?
回應
Kim 說:
Hi 黃先生您好,
再請叫個問題,當呼叫 MakeSignature(int moduleHandle, int sessionHandle, int algorithm, string Message, int iMessageLength, int PrivateKeyObj, byte[] Signature, ref int iSignatureLength);,執行簽章的作業之後,簽回來的資料都存在於 Signature,請問這個要以何種編碼方式進行轉換,才能轉為符合XML Signature格式的資料內容?
回應
Blave Huang 回應:
七月 2nd, 2009 at 15:55:48
抱歉…小弟沒轉成 XML Signature過,不過您或許可以參考這個:
http://msdn.microsoft.com/zh-tw/library/system.security.cryptography.xml.signature.aspx
回應
Roy 說:
謝謝你相當迅速的回覆了!~
我是安裝http://moica.nat.gov.tw/html/download_1.htm中的
憑證匯入工具MOICA_HiCOS_Setup
安裝後再到C:\Windows\System32目錄下將檔案取出來(DLL檔名有些許不同)
引用到您所分享的函式中
在本機端(VS的執行環境)執行正常
將程式放上IIS後,就出現 " 並未將物件參考設定為物件的執行個體 "的錯誤
請問我可能少了什麼動作,還是DLL的不同所造成的?
在此再次感謝!謝謝!~
回應
Blave Huang 回應:
七月 1st, 2009 at 14:20:53
看起來應該是DLL不同的樣子…
您可能還是需要那些DLL檔呦~~
回應
Roy 說:
黃先生你好,
請問一下 API的部份是應該要安裝SafeSign還是HiCos?
因為我是新手可以提供範例嗎?
在此先謝謝你的回答!
回應
Blave Huang 回應:
七月 1st, 2009 at 08:30:33
讀取卡片有兩種方式最方便,一種是CSP,這是一種Microsoft提出的介面,只要廠商的API符合CSP介面,應用程式就可以去操作卡片,就像Outlook也可以叫卡片來數位簽章,所以Safesign是CSP。
另一種方式是PKCS#11,通常廠商會給你C++的檔案;或是DLL檔,當然也要有說明文件才能去使用DLL。這個範例就是使用PKCS#11,然而範例中使用的DLL是內政部所有,因此我不可放到網路上給大家抓,您可自行到以下網址申請API:
http://61.60.9.15/apply/action/basicWaymarkDispatcher?waymark=register
或是從moica網站進入後也找得到。
範例的話…上面這個就是範例了,您缺少的應該是API的DLL...
回應
Kim 說:
Hi 黃先生你好,
在使用憑證加簽時,一直有碰到個問題就加簽完後的byte array,以 Convert.ToBase64String 取回時,這個值應該就是SignatureValue對嗎?
我取得的這個值 看起來似乎是錯的,因為最後一個字元並不是" = "
請問你先前開發時有碰到類似問題嗎?
回應
Blave Huang 回應:
六月 11th, 2009 at 19:00:47
Base64String的最後面不一定會是 "=",您可以再把Base64String再轉回byte[](記得應該是Convert.FromBase64String()),如果發生錯誤,代表那串base64是有問題的!
我會轉為Base64是為了顯示給使用者看,及存入資料庫較方便。
回應