如何在Windows下使用C/C++验证PE文件数字签名并获取签名方信息
实现方案
你提到的WinVerifyTrust和CertGetCertificateChain两个API完全适配该场景,前者是Windows资源管理器校验文件签名的底层接口,返回结果和文件属性数字签名标签页的显示完全一致,后者用于解析证书链提取签名主体信息,具体实现逻辑如下:
1. 第一阶段:校验数字签名有效性
如果已经持有文件句柄可以直接传入API,无需重复读取文件;如果只持有内存缓冲区,也可以跳过磁盘读写直接做内存校验。
核心调用逻辑
#include <windows.h> #include <wintrust.h> #include <softpub.h> #pragma comment(lib, "wintrust.lib") BOOL VerifyFileSignature(HANDLE hFile, LPCWSTR lpFilePath) { // 填充文件信息结构体,已持有文件句柄可直接传入,API会优先用句柄 WINTRUST_FILE_INFO fileInfo = {0}; fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO); fileInfo.hFile = hFile; fileInfo.pcwszFilePath = lpFilePath; // 填充校验参数 WINTRUST_DATA trustData = {0}; trustData.cbStruct = sizeof(WINTRUST_DATA); trustData.dwUIChoice = WTD_UI_NONE; // 后台静默校验,不弹出窗口 trustData.fdwRevocationChecks = WTD_REVOKE_NONE; // 按需开启证书吊销校验 trustData.dwUnionChoice = WTD_CHOICE_FILE; trustData.pFile = &fileInfo; trustData.dwStateAction = WTD_STATEACTION_VERIFY; trustData.dwProvFlags = WTD_DISABLE_MD2_MD4; // 禁用弱哈希,对齐系统默认策略 GUID actionGuid = WINTRUST_ACTION_GENERIC_VERIFY_V2; LSTATUS status = WinVerifyTrust(NULL, &actionGuid, &trustData); // 校验完成后必须释放状态数据,此处先暂存状态数据用于后续提取证书 HANDLE hWvtState = trustData.hWVTStateData; trustData.dwStateAction = WTD_STATEACTION_CLOSE; WinVerifyTrust(NULL, &actionGuid, &trustData); // 返回值为ERROR_SUCCESS代表签名有效、文件未被篡改 return status == ERROR_SUCCESS; }
内存缓冲区校验适配
如果仅持有文件内容缓冲区,只需将上述代码中WINTRUST_DATA的dwUnionChoice改为WTD_CHOICE_BLOB,填充WINTRUST_BLOB_INFO结构体传入缓冲区地址和长度即可,逻辑完全一致。
2. 第二阶段:提取实际签名主体公司名称
签名校验通过后,从WinVerifyTrust返回的状态数据中提取证书链,证书链的第一个元素就是最底层的实际签名证书(非根CA证书),直接读取其主体信息即可。
核心调用逻辑
注意:该段代码需要放在第一阶段校验成功、释放WVT状态数据之前执行
#include <cryptuiapi.h> #pragma comment(lib, "crypt32.lib") BOOL GetSignerCompanyName(HANDLE hWvtState, LPWSTR lpNameBuf, DWORD dwBufSize) { // 从状态数据中获取加密提供者信息 CRYPT_PROVIDER_DATA* pProvData = WTHelperProvDataFromStateData(hWvtState); if (!pProvData || pProvData->csSigners < 1) return FALSE; // 取第一个签名的证书链,多签名场景可遍历csSigners字段 CRYPT_PROVIDER_SGNR* pSigner = &pProvData->pSigners[0]; if (!pSigner || pSigner->csCertChain < 1) return FALSE; // 证书链第一个元素就是实际签名的叶子证书,不是上层CA根证书 CERT_CONTEXT* pLeafCert = pSigner->pasCertChain[0].pCert; if (!pLeafCert) return FALSE; // 提取证书主体的组织名称,也可指定OID获取精确字段 return CertGetNameStringW(pLeafCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, lpNameBuf, dwBufSize) > 1; }
注意事项
- 驱动文件和用户态可执行文件的校验逻辑完全一致,无需调整参数
- 如果需要和系统默认策略完全对齐,可将
fdwRevocationChecks改为WTD_REVOKE_WHOLECHAIN开启全链吊销校验,注意会产生网络请求增加耗时 - 叶子证书的生命周期和WVT状态数据绑定,无需手动释放,释放状态数据时会自动回收
内容的提问来源于stack exchange,提问作者OneAndOnly
相关产品推荐
相关产品推荐

