在 Windows 的安全体系中,数字签名扮演着“软件身份证”的角色。它可以证明一个程序确实来自某个发布者,并且在分发的过程中没有被篡改。
当下载一个系统更新、驱动程序,或者安装第三方应用时,操作系统往往会验证数字签名,确保软件来源合法、安全。那么,作为开发者或安全研究人员,该如何编程获取并验证这些数字签名呢?
本文将通过代码示例,逐步实现一个数字签名验证工具,并解析其原理和应用场景。
为什么要验证数字签名?
在信息安全领域,数字签名主要解决两个问题:
- 来源认证 通过证书绑定发布者的身份,确认文件确实来自于声称的公司或组织。比如系统文件通常签名于 Microsoft Corporation。
- 完整性保护 签名过程包含哈希计算,如果文件在传输中被篡改,签名将立刻失效。
对普通用户来说,这能防止中途被植入恶意代码;对企业来说,则能确保生产环境中运行的软件是可信的。
验证思路与 Windows API
Windows 提供了专门的 API 来处理数字签名验证,其中最关键的包括:
- WinVerifyTrust:验证文件的数字签名是否有效;
- WTHelperProvDataFromStateData:获取验证过程中的证书链数据;
- WTHelperGetProvSignerFromChain:提取签名者、时间戳签名者等信息;
- CertGetNameString:获取证书的持有人或颁发者名称;
- CryptHashCertificate2:计算证书的 SHA1 指纹。
我们的目标是用这些 API 编写一个签名验证器,它不仅要能告诉我们签名是否有效,还能展示签名的详细信息。
定义数据结构:存储验证结果
首先,需要定义一个结构体,用来存储每次验证得到的签名信息。
|
1 2 3 4 5 6 7 8 9 |
struct SignatureInfo { std::string status; // 验证状态简写(如 Valid、Expired) std::string statusMessage; // 状态详细描述 std::string thumbprint; // 证书指纹(SHA1) std::string signer; // 签名者 std::string issuer; // 颁发者 std::string timeStamper; // 时间戳服务器(如果存在) bool isValid = false; // 签名是否有效 }; |
通过这样的结构体,就能清晰地记录和返回验证结果,方便展示和后续处理。
调用 WinVerifyTrust 验证签名
验证的第一步就是调用 WinVerifyTrust,系统会说明这个文件的签名是否可信。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
WINTRUST_FILE_INFO fileData = { sizeof(WINTRUST_FILE_INFO) }; fileData.pcwszFilePath = filePath; WINTRUST_DATA wintrustData = { sizeof(WINTRUST_DATA) }; wintrustData.dwUIChoice = WTD_UI_NONE; // 不弹出 UI wintrustData.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN; // 检查整个证书链 wintrustData.dwUnionChoice = WTD_CHOICE_FILE; wintrustData.pFile = &fileData; wintrustData.dwStateAction = WTD_STATEACTION_VERIFY; GUID action = WINTRUST_ACTION_GENERIC_VERIFY_V2; LONG result = WinVerifyTrust(NULL, &action, &wintrustData); |
通过返回值 result 可以验证是否成功:
- ERROR_SUCCESS:签名有效;
- 其他值:签名无效,需要进一步分析原因,比如证书过期、吊销、不受信任等。
这相当于签名验证的入口,后续的步骤都建立在这之上。
提取签名证书信息
如果验证成功,便可以提取证书链的详细信息,包括:
- 签名者(Signer):谁签了这个文件;
- 颁发者(Issuer):证书由谁颁发;
- 证书指纹(Thumbprint):用于唯一标识证书;
- 时间戳(Timestamp):签名时刻的时间戳,确保即使证书后来过期,签名依然有效。
核心代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
CRYPT_PROVIDER_DATA* providerData = WTHelperProvDataFromStateData(wintrustData.hWVTStateData); if (providerData) { CRYPT_PROVIDER_SGNR* signer = WTHelperGetProvSignerFromChain(providerData, 0, FALSE, 0); if (signer && signer->pasCertChain[0].pCert) { PCCERT_CONTEXT certContext = signer->pasCertChain[0].pCert; info.thumbprint = GetCertificateThumbprint(certContext); info.signer = GetCertificateName(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE); info.issuer = GetCertificateName(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE | CERT_NAME_ISSUER_FLAG); // 获取时间戳(如果存在) if (signer->csCounterSigners > 0) { CRYPT_PROVIDER_SGNR* timeStamper = WTHelperGetProvSignerFromChain(providerData, 0, TRUE, 0); if (timeStamper && timeStamper->pasCertChain[0].pCert) { info.timeStamper = GetCertificateName(timeStamper->pasCertChain[0].pCert, CERT_NAME_SIMPLE_DISPLAY_TYPE); } } } } |
通过证书链,可以把签名的每一个关键细节抽取出来。
辅助函数:让结果更直观
仅仅有 API 返回的原始数据并不友好,还需要写几个辅助函数,便于转化结果。
获取证书指纹
证书指纹是证书的 SHA1 哈希,常用于唯一标识:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
static std::string GetCertificateThumbprint(PCCERT_CONTEXT certContext) { BYTE thumbprint[20] = {0}; DWORD thumbprintSize = sizeof(thumbprint); if (CryptHashCertificate2(BCRYPT_SHA1_ALGORITHM, 0, NULL, certContext->pbCertEncoded, certContext->cbCertEncoded, thumbprint, &thumbprintSize)) { std::stringstream ss; for (DWORD i = 0; i < thumbprintSize; i++) { ss << std::hex << std::setw(2) << std::setfill('0') << (int)thumbprint[i]; } return ss.str(); } return ""; } |
获取证书名称
证书包含了签名者和颁发者的名称,我们用 CertGetNameString 提取:
|
1 2 3 4 5 6 7 8 9 |
static std::string GetCertificateName(PCCERT_CONTEXT certContext, DWORD type) { DWORD size = CertGetNameStringA(certContext, type, 0, NULL, NULL, 0); if (size > 1) { std::vector<char> buffer(size); CertGetNameStringA(certContext, type, 0, NULL, buffer.data(), size); return buffer.data(); } return ""; } |
状态码映射
不同的错误码代表不同的签名状态,可以映射为更易懂的文字:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
static std::string GetStatusText(LONG status) { switch (status) { case ERROR_SUCCESS: return "Valid"; case TRUST_E_NOSIGNATURE: return "NotSigned"; case CERT_E_EXPIRED: return "Expired"; case CERT_E_REVOKED: return "Revoked"; case TRUST_E_EXPLICIT_DISTRUST: return "Distrusted"; case TRUST_E_BAD_DIGEST: return "Invalid"; case CERT_E_UNTRUSTEDROOT: return "UntrustedRoot"; default: return "Error"; } } |
测试与运行结果
最后,可以这样使用:
|
1 2 3 4 5 6 7 8 9 10 11 |
int main() { auto info = SignatureVerifier::GetSignatureInfo(L"C:\\Windows\\explorer.exe"); printf("Status: %s\n", info.status.c_str()); printf("Message: %s\n", info.statusMessage.c_str()); printf("Thumbprint: %s\n", info.thumbprint.c_str()); printf("Signer: %s\n", info.signer.c_str()); printf("Issuer: %s\n", info.issuer.c_str()); printf("TimeStamper: %s\n", info.timeStamper.c_str()); printf("IsValid: %s\n", info.isValid ? "Yes" : "No"); } |
运行结果:
|
1 2 3 4 5 6 7 |
Status: Valid Message: Signature verified Thumbprint: 3b77d......... Signer: Microsoft Windows Issuer: Microsoft Windows TimeStamper: Microsoft Time-Stamp Service IsValid: Yes |
这表明 explorer.exe 的签名有效,签名者是微软,签名带有可信时间戳。
















