最近先来无事,对PKCS#7格式的数字签名和数字信封产生了兴趣,在安全领域混饭吃,不把这些东西搞搞懂可不行啊。
PKCS#7是由RSA安全体系在公钥加密系统中交换数字证书产生的一种加密标准,最近本的标准有PKCS#1、#3、#5、#6、#7、#8、#9和#10,分别定义不同的协议标准。PKCS#7为密码信封封装标准,描述了密码操作(例如数字签名和数字信封)的数据的通用语法。该语法允许递归,例如一个数字信封可以嵌套在另一个数字信封里面,或者一个实体可以在一个已经封装的数据上签名。该语法同时允许添加如意属性,比如签名时间等。该标准和保密增强邮件(PEM)是兼容的,如果以PEM兼容形式构建,那么被签名后的数据和签名后又封装的数据内容可以很容易地被转化成PEM格式。
讲了很多PKCS#7的基础东西,那么如何创建一个符合PKCS#7格式的数字签名呢?如果按照PKCS#7格式标准自己写的话将是很复杂很麻烦的一件事,但是好在微软已经给我们封装好了一个COM--CAPICOM,里面的数字签名就是符合PKCS#7格式的;还有一个开源的代码就是OpenSSL,如果你想更详细的了解PCKS#7的话,最好下一份OpenSSL的源码下来研究下,如果你只是想应用的话,我想CAPICOM更适合一点。
下面主要讲述在MFC中如何利用CAPICOM创建符合PKCS#7的数字签名,并验证签名,使初学者可以省去很多麻烦,同时也希望有朋友来纠正我的错误。
创建简单的MFC程序就不多说了,添加两个按钮(一个签名,一个数字信封)一个edit和一个listbox。
在InitInstance函数中添加: AfxOleInit();//初始化COM,如果不是MFC则程序开始时添加:CoInitialize(0);最后程序结束时添加:CoUninitialize();
在需要调用CAPICOM的CPP中添加:#import "capicom.dll"//CAPICOM的库得导入
签名按钮下代码如下:
void CTestPKCS7Dlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
using namespace CAPICOM;
try
{
IStorePtr pStore(__uuidof(Store));
if (FAILED(pStore->Open(CAPICOM_CURRENT_USER_STORE, //打开证书池
_T("MY"),
CAPICOM_STORE_OPEN_READ_ONLY)))
return;
for (int i=1;i<=pStore->GetCertificates()->Count;i++)//列举系统中又有的证书
{
ICertificatePtr pCert = (ICertificatePtr)pStore->GetCertificates()->GetItem(i);
_bstr_t subject = pCert->GetSubjectName();
list.InsertString(0,(LPSTR)subject);//list 为一listbox
}
UpdateData();
//签名时间----属性
IAttributePtr pSignedTime(__uuidof(Attribute));
pSignedTime->PutName(CAPICOM_AUTHENTICATED_ATTRIBUTE_DOCUMENT_NAME);
SYSTEMTIME systime;
GetLocalTime(&systime);
TCHAR signtime[20] = {0};
GetTimeFormat(LOCALE_USER_DEFAULT,TIME_FORCE24HOURFORMAT,&systime,"hh':'mm':'ss tt",signtime,20);
pSignedTime->PutValue(_variant_t(signtime));
ISignerPtr pSigner(__uuidof(Signer));
pSigner->PutCertificate((ICertificatePtr)pStore->GetCertificates()->GetItem(1));
pSigner->GetAuthenticatedAttributes()->Add(pSignedTime);//此处只加了一个签名时间,也可以加其他的属性
ISignedDataPtr pSignedData(__uuidof(SignedData));
pSignedData->PutContent(_T("*"));//要签名的数据
_bstr_t Signature = pSignedData->Sign(pSigner,TRUE,CAPICOM_ENCODE_BASE64);//签名
sss =(LPSTR) Signature;//签名后的数据
UpdateData(FALSE);
ISignedDataPtr pVerifyData(__uuidof(SignedData));//验证
pVerifyData->PutContent(_T("*"));//签名时如果没有分离原文,即pSignedData->Sign的第二个参数为false的话,不必赋值
pVerifyData->Verify(Signature,TRUE,CAPICOM_VERIFY_SIGNATURE_AND_CERTIFICATE);
AfxMessageBox("OK");
}
catch(_com_error e)//验证签名时必须使用异常捕捉,只有验证通过才没有异常,否则必抛出异常
{
AfxMessageBox(e.Description());
}
}
信封和拆封按钮下代码如下:
void CTestPKCS7Dlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
using namespace CAPICOM;
try
{
IStorePtr pStore(__uuidof(Store));
if (FAILED(pStore->Open(CAPICOM_CURRENT_USER_STORE, //打开证书池
_T("MY"),
CAPICOM_STORE_OPEN_READ_ONLY)))
return;
IEnvelopedDataPtr pEnvelopedData(__uuidof(EnvelopedData));
pEnvelopedData->PutContent(_T("*"));//要封装的内容
pEnvelopedData->GetAlgorithm()->PutName(CAPICOM_ENCRYPTION_ALGORITHM_RC4);//算法名
pEnvelopedData->GetAlgorithm()->PutKeyLength(CAPICOM_ENCRYPTION_KEY_LENGTH_128_BITS);//算法长度
for (int i=1;i<=pStore->GetCertificates()->Count;i++)//列举系统中又有的证书
{
ICertificatePtr pCert = (ICertificatePtr)pStore->GetCertificates()->GetItem(i);
pEnvelopedData->GetRecipients()->Add(pCert);//添加收件人证书,否则收件人无法利用自己的私钥解密消息
_bstr_t subject = pCert->GetSubjectName();
list.InsertString(0,(LPSTR)subject);//list 为一listbox
}
UpdateData();
_bstr_t strEnvelopedData = pEnvelopedData->Encrypt(CAPICOM_ENCODE_BASE64);
sss =(LPSTR) strEnvelopedData;//封装后的数据
UpdateData(FALSE);
AfxMessageBox(_T("封装成功,下面将拆封!"));
IEnvelopedDataPtr pOpenEnvelopedData(__uuidof(EnvelopedData));
pOpenEnvelopedData->Decrypt(strEnvelopedData); //要输入私钥密码--文件证书不需要,该私钥必须和收件人列表中的某一个证书对应
sss = (LPSTR) pOpenEnvelopedData->GetContent(); //获取拆封后的数据
UpdateData(FALSE);
}
catch(_com_error e)//验证签名时必须使用异常捕捉,只有验证通过才没有异常,否则必抛出异常
{
AfxMessageBox(e.Description());
}
}
其他的细节大家可以研究下MSDN,不过MSDN上只有VB的例子。