CryptoAPI调用指南(四)——数字签名及验证

数字签名

和非对称加密类似,数字签名通过调用CryptSignMessage这一个方法即可实现,这一个方法里将摘要运算和私钥加密摘要两步都包含进去了。

BOOL CryptSignMessage(

PCRYPT_SIGN_MESSAGE_PARA pSignPara,

BOOL fDetachedSignature,

DWORD cToBeSigned,

const BYTE * [] rgpbToBeSigned,

DWORD [] rgcbToBeSigned,

BYTE *pbSignedBlob,

DWORD *pcbSignedBlob

)

pSignPara是签名参数,比如可做如下设置:

CRYPT_SIGN_MESSAGE_PARA SigParams;

SigParams.cbSize = sizeof(CRYPT_SIGN_MESSAGE_PARA);

SigParams.dwMsgEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING;//编码方式

SigParams.pSigningCert = pCertContext;//签名所用证书的上下文

SigParams.HashAlgorithm.pszObjId = szOID_RSA_SHA1RSA;//摘要算法用SHA1

SigParams.HashAlgorithm.Parameters.cbData = NULL;

SigParams.cMsgCert = 1;// CryptSignMessage生成的是标准的PKCS7格式签名,支持多用户签名,这里指定签名用户数,也就是rgpMsgCert数组元素个数。当然,绝大部分情况下只有一个用户签名。

SigParams.rgpMsgCert = &pCertContext;//包含所有签名证书的数组,必须将pSigningCert指向的证书上下文对象添加到这个数组里

SigParams.cAuthAttr = 0;//用户认证属性数组元素个数,也就是rgAuthAttr元素个数,没有的话就传0

SigParams.rgAuthAttr = NULL;// 用户认证属性数组

SigParams的其他属性值可以参看相关文档,一般情况下都设置为0或NUL;fDetachedSignature标识签名结果是否与原文分离,一般设置为TRUE;cToBeSigned 是待签名原文的个数,也就是rgpbToBeSigned和rgcbToBeSigned的元素个数;注意,如果fDetachedSignature为FALSE,即签名和原文不分离的情况下,cToBeSigned只能是1,也就是只能有一个原文进行签名;rgpbToBeSigned是待签名的原文数组;rgcbToBeSigned是待签名原文长度的数组;pbSignedBlob为返回的PKCS7格式签名结果,如果fDetachedSignature为TRUE,则结果为纯签名;否则结果里包含签名和原文数据;pcbSignedBlob是pbSignedBlob的字节长度。

同样,每一次签名时此方法应调用两次,第一次pbSignedBlob传NULL,pcbSignedBlob返回签名结果的实际长度,第二次调用时为pbSignedBlob分配pcbSignedBlob的字节长度。第二次调用一定要用实际长度分配空间,否则即使签名成功,验证时也可能失败

方法成功返回TRUE,失败返回FALSE,调用GetLastError可返回具体错误信息。

签名验证

根据签名结果是否与原文分离,验证签名的方法也相应有两种:

签名与原文分离

  1. CryptAcquireContext

  2. BOOL CryptVerifyDetachedMessageSignature(

PCRYPT_VERIFY_MESSAGE_PARA pVerifyPara,

DWORD dwSignerIndex,

const BYTE *pbDetachedSignBlob,

DWORD cbDetachedSignBlob,

DWORD cToBeSigned,

const BYTE * [] rgpbToBeSigned,

DWORD [] rgcbToBeSigned,

PCCERT_CONTEXT *ppSignerCert

)

pVerifyPara是验证参数,可以做如下设置:

CRYPT_VERIFY_MESSAGE_PARA VerifyParams;

VerifyParams.cbSize = sizeof(CRYPT_VERIFY_MESSAGE_PARA);

VerifyParams.dwMsgAndCertEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING;//编码方式

VerifyParams.hCryptProv = hProv;// CryptAcquireContext返回的CSP句柄

VerifyParams.pfnGetSignerCertificate = NULL;//用于返回签名证书的回调函数,如果为NULL,则使用缺省回调函数,即从签名数据里返回

VerifyParams.pvGetArg = NULL;//上述回调函数的参数

dwSignerIndex是被验证的签名的索引值,从0开始。通过循环调用此方法并递增dwSignerIndex,可以验证签名数据中的所有签名。当验证结果返回FALSE,并且用GetLastError返回的错误是CRYPT_E_NO_SIGNER,所有签名已验证完毕;

pbDetachedSignBlob为待验证的签名数据;cbDetachedSignBlob为待验证的签名数据长度;cToBeSigned为生成签名的原文个数;rgpbToBeSigned是原文数组;rgcbToBeSigned是原文长度的数组;ppSignerCert返回签名证书的上下文,同样这个上下文对象在使用完毕后要用CertFreeCertificateContext释放。如果不需要返回,ppSignerCert参数传NULL。

方法返回值为TRUE时表明验证成功,否则验证失败,可以调用GetLastError得到具体错误信息。和签名本身有关的常见错误有:
CryptoAPI调用指南(四)——数字签名及验证

签名与原文不分离

  1. CryptAcquireContext

  2. BOOL CryptVerifyMessageSignature(

PCRYPT_VERIFY_MESSAGE_PARA pVerifyPara,

DWORD dwSignerIndex,

const BYTE *pbSignedBlob,

DWORD cbSignedBlob,

BYTE *pbDecoded,

DWORD *pcbDecoded,

PCCERT_CONTEXT *ppSignerCert

)

CryptVerifyMessageSignature方法的前两个和最后一个参数与CryptVerifyDetachedMessageSignature方法一致;pbSignedBlob为待验证签名数据,里面包含签名和原文;cbSignedBlob为签名数据长度;pbDecoded为返回的原文;pcbDecoded为原文的长度;如果想返回原文,那CryptVerifyMessageSignature需调用两次,第一次pcbDecoded传NULL,pcbDecoded返回其需要的实际长度,第二次为pcbDecoded分配实际长度空间。

在返回值方面,CryptVerifyMessageSignature方法多了一种常见错误码ERROR_MORE_DATA(0xEA),表示为pbDecoded分配的空间不够。

另外,调用CryptoAPI的程序处理的数据经常是BASE64编码字符串类型,但CryptoAPI输入输出参数都是字节流类型,所以在实际开发时需要进行BASE64和字节数组之间的转换。

到此就把常用的CryptoAPI功能接口介绍完毕了。完整的CryptoAPI函数接口大家可以参考相关文档和头文件WinCrypt.h,有任何问题欢迎在评论区交流。