Delphi 編寫數字簽名驗證並獲取簽名信息

[摘要]本文介紹Delphi 編寫數字簽名驗證並獲取簽名信息,並提供詳細的示例代碼供參考。

一個客戶想通過編程實現驗證程序自身的數字簽名來確保程序的完整性,防範病毒感染以及防止一些無聊人士的修改(通過十六進制編輯器替換一些版權、網址、LOGO..); 爲此我做了一個數字簽名驗證的小例子,其中也有獲取簽名者信息的方法,以滿足“自驗證”的需求。

示例圖:

Delphi 編寫數字簽名驗證

Windows API:

安全編錄(CAT)

  • CryptCATAdminReleaseCatalogContext
  • CryptCATCatalogInfoFromContext
  • CryptCATAdminEnumCatalogFromHash
  • CryptCATAdminCalcHashFromFileHandle
  • CryptCATAdminReleaseContext
  • CryptCATAdminAcquireContext

驗證文件的簽名(主API)

  • WinVerifyTrust

獲取簽名信息

  • WTHelperProvDataFromStateData

獲取證書名字信息

  • CertGetNameString

Delphi 編寫數字簽名驗證並獲取簽名信息代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
{
  * by: HouSoft
  * site: www.yryz.net
  * created: 2012/02/03
}
unit Unit1;
  
interface
  
uses
  Windows, Sysutils, jwaWinCrypt, WinTrustApi;
  
procedure Test;
  
implementation
  
procedure PrintCertChain(pCertChain: PCERT_SIMPLE_CHAIN);
var
  I: Integer;
  sBuf: string;
begin
  // 開啓指針運算
{$POINTERMATH ON}
  //
  // 輸出書鏈元素
  for I := pCertChain^.cElement - 1 downto 0 do
  begin
    SetLength(sBuf, 1024);
    SetLength(sBuf,
      CertGetNameString(
      pCertChain^.rgpElement[I].pCertContext,
      CERT_NAME_SIMPLE_DISPLAY_TYPE, // 簡單名字
      0,
      nil,
      PChar(sBuf),
      Length(sBuf)) - 1);
  
    WriteLn(#9, StringOfChar(' ', 2 * (pCertChain^.cElement - I - 1)), sBuf);
  end;
end;
  
procedure OutSignerInfo(hWVTStateData: THANDLE);
var
  provData: PCRYPT_PROVIDER_DATA;
  LSysTime: TSystemTime;
begin
  // 獲取簽名信息
  // http://msdn.microsoft.com/ZH-CN/library/windows/desktop/aa388429(v=vs.85).aspx
  
  provData := WTHelperProvDataFromStateData(hWVTStateData);
  if (provData <> nil) and (provData^.pasSigners <> nil) then
  begin
    // 採用安全編錄(CAT)簽名
    if provData^.pPDSip^.psSipCATSubjectInfo <> nil then
    begin
      WriteLn('安全編錄: ');
      WriteLn(#9, provData^.pPDSip^.psSipCATSubjectInfo^.pwsFileName);
      WriteLn('');
    end;
  
    /// 注意: provData^.pasSigners 是數組, 但常見的都是一個元素,so...
  
    // 時間戳
    if provData^.pasSigners^.pasCounterSigners <> nil then
    begin
      FileTimeToSystemTime(provData^.pasSigners^.pasCounterSigners^.sftVerifyAsOf, LSysTime);
      WriteLn('時間戳: ');
      WriteLn(#9, FormatDateTime('yyyy-MM-dd hh:mm:ss', SystemTimeToDateTime(LSysTime)));
      WriteLn('');
      WriteLn('時間戳證書鏈: ');
      PrintCertChain(provData^.pasSigners^.pasCounterSigners^.pChainContext^.rgpChain[0]);
      WriteLn('');
    end;
  
    WriteLn('簽名者證書鏈:');
    PrintCertChain(provData^.pasSigners^.pChainContext^.rgpChain[0]);
    WriteLn('');
  end;
end;
  
function SignVerify(FileName: string): Boolean;
var
  aByteHash: array [0 .. 255] of Byte;
  iByteCount: Integer;
  
  hCatAdminContext: HCatAdmin;
  WTrustData: WINTRUST_DATA;
  WTDCatalogInfo: WINTRUST_CATALOG_INFO;
  WTDFileInfo: WINTRUST_FILE_INFO;
  CatalogInfo: CATALOG_INFO;
  
  hFile: THANDLE;
  hCatalogContext: THANDLE;
  
  swFilename: WideString;
  swMemberTag: WideString;
  
  ilRet: Longint;
  I: Integer;
begin
  Result := False;
  
  if not FileExists(FileName) then
    Exit;
  
  swFilename := FileName;
  
  ZeroMemory(@CatalogInfo, SizeOf(CatalogInfo));
  ZeroMemory(@WTDFileInfo, SizeOf(WTDFileInfo));
  ZeroMemory(@WTDCatalogInfo, SizeOf(WTDCatalogInfo));
  ZeroMemory(@WTrustData, SizeOf(WTrustData));
  
  hCatalogContext := 0;
  hCatAdminContext := 0;
  
  try
    // 先查詢安全編目
    if not CryptCATAdminAcquireContext(@hCatAdminContext,
      nil,
      0) then
      Exit;
  
    hFile := CreateFile(PChar(FileName),
      GENERIC_READ,
      FILE_SHARE_READ,
      nil,
      OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL,
      0);
  
    if hFile = INVALID_HANDLE_VALUE then
      Exit;
  
    iByteCount := SizeOf(aByteHash);
  
    // 文件哈希函數計算的
    CryptCATAdminCalcHashFromFileHandle(hFile,
      @iByteCount,
      @aByteHash,
      0);
  
    for i := 0 to iByteCount - 1 do
    begin
      swMemberTag := swMemberTag + IntToHex(aByteHash[i], 2);
    end;
  
    CloseHandle(hFile);
  
    // 枚舉目錄包含一個指定的哈希
    hCatalogContext := CryptCATAdminEnumCatalogFromHash(hCatAdminContext,
      @aByteHash,
      iByteCount,
      0,
      nil);
  
    // 準備驗證參數
    // http://msdn.microsoft.com/en-us/library/windows/desktop/aa388205(v=vs.85).aspx
  
    WTrustData.dwUIChoice := WTD_UI_NONE;
    WTrustData.fdwRevocationChecks := WTD_REVOKE_NONE;
    WTrustData.dwStateAction := WTD_STATEACTION_VERIFY; // 獲取信息後需要手動 WTD_STATEACTION_CLOSE
    WTrustData.dwProvFlags := WTD_REVOCATION_CHECK_NONE;
  
    if hCatalogContext = 0 then // 未找到包含此文件的安全編目
    begin
      WTDFileInfo.cbStruct := SizeOf(WTDFileInfo);
      WTDFileInfo.pcwszFilePath := PWideChar(swFilename);
  
      WTrustData.cbStruct := SizeOf(WTrustData);
      WTrustData.dwUnionChoice := WTD_CHOICE_FILE;
      WTrustData.union.pFile := @WTDFileInfo;
  
    end
    else
    begin
      CryptCATCatalogInfoFromContext(hCatalogContext, @CatalogInfo, 0);
  
      WTDCatalogInfo.cbStruct := SizeOf(WTDCatalogInfo);
      WTDCatalogInfo.pcwszCatalogFilePath := CatalogInfo.sCatalogFile;
      WTDCatalogInfo.pcwszMemberFilePath := PWideChar(swFilename);
      WTDCatalogInfo.pcwszMemberTag := PWideChar(swMemberTag);
  
      WTrustData.cbStruct := SizeOf(WTrustData);
      WTrustData.dwUnionChoice := WTD_CHOICE_CATALOG;
      WTrustData.union.pCatalog := @WTDCatalogInfo;
  
      // WriteLn(CatalogInfo.sCatalogFile);
    end;
  
    // 驗證
    // http://msdn.microsoft.com/en-us/library/windows/desktop/aa388208(v=vs.85).aspx
    ilRet := WinVerifyTrust(INVALID_HANDLE_VALUE,
      @WINTRUST_ACTION_GENERIC_VERIFY_V2,
      @WTrustData);
  
    Result := ilRet = 0;
  
    // 輸出簽名信息
    OutSignerInfo(WTrustData.hWVTStateData);
  
    // 釋放
    WTrustData.dwStateAction := WTD_STATEACTION_CLOSE;
    WinVerifyTrust(INVALID_HANDLE_VALUE,
      @WINTRUST_ACTION_GENERIC_VERIFY_V2,
      @WTrustData);
  finally
    if hCatAdminContext > 0 then
    begin
      if hCatalogContext > 0 then
        CryptCATAdminReleaseCatalogContext(hCatAdminContext,
          hCatalogContext, 0);
  
      CryptCATAdminReleaseContext(hCatAdminContext, 0);
    end;
  end;
end;
  
procedure Test;
begin
  if ParamCount < 1 then
  begin
    WriteLn('請輸入要驗證的文件名!');
    Exit;
  end;
  
  if SignVerify(ParamStr(1)) then
    WriteLn('簽名有效.')
  else
    WriteLn('簽名無效.');
end;
  
end.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章