Windows 提供了Crypto API, 使用这些API, 我们可以比较轻松的实现Hash,签名等工作。MSDN上有很多信息,
http://technet.microsoft.com/zh-cn/library/aa382371
下面的例子是对一个给定的字符串进行hash计算,并且把hash值签名。给定的字符串如下:
BYTE *pbBuffer = (BYTE *)"The data that is to be hashed and signed.";
CryptAcquireContext
第一个需要使用的API 是CryptAcquireContext。
原型如下:
BOOL WINAPI CryptAcquireContext( _Out_ HCRYPTPROV *phProv, _In_ LPCTSTR pszContainer, _In_ LPCTSTR pszProvider, _In_ DWORD dwProvType, _In_ DWORD dwFlags );
pszContainter是一个字符串,可以指定key container的名字。
pszProvider指定CSP,如果传NULL的话,那就是default CSP.
dwProvType指定类型
更多信息查看MSDN。
CryptAcquireContext会首先查找指定的CSP,然后再查找指定的key container。如果成功的话,就返回一个CSP的handle(第一个参数)。
下面代码是一个简单的例子。当程序第一次执行的时候,第一个CryptAcquireContext会失败,因为"MyContainer" 密钥容器根本不存在,这样我们就可以创建一个。一旦创建好了,以后就可以直接获取了。
//------------------------------------------------------------------- // Acquire a cryptographic provider context handle. if (CryptAcquireContext( &hProv, L"MyContainer", NULL, PROV_RSA_FULL, 0)) { printf("CSP context acquired.\n"); } else { if (GetLastError() == NTE_BAD_KEYSET) { if (!CryptAcquireContext( &hProv, L"MyContainer", NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) { MyHandleError("Error during CryptAcquireContext."); } } else { MyHandleError("Error during CryptAcquireContext."); } }
CryptGetUserKey
这个API可以获取密钥容器里面的密钥,同样第一次调用会失败,因为还不存在,我们可以尝试创建一个。根据MSDN上的说法,一个key container只有一个key。新创建的会覆盖旧的。
if (CryptGetUserKey( hProv, AT_SIGNATURE, &hKey)) { printf("The signature key has been acquired. \n"); } else { if (GetLastError() == NTE_NO_KEY) // NTE_NO_KEY意味着密钥不存在,下面就生成一个密钥 { _tprintf(TEXT("The signature key does not exist./n")); _tprintf(TEXT("Create a signature key pair./n")); if (CryptGenKey( // CryptGenKey生成一个密钥 hProv, //指定CSP模块的句柄 AT_SIGNATURE, //对于公钥密码系统,生成一个私钥和一个公钥,这个参数指定了这个密钥是公钥,于是生成了一个密码对。如果不是公钥系统,则指定了密码算法,具体看MSDN。 0, //指定了生成密钥的类型,这个参数的说明挺多的,想获取更为详尽的资料请看MSDN。 &hKey)) { _tprintf(TEXT("Created a signature key pair./n")); } else { MyHandleError("CryptGenKey failed"); } } else { MyHandleError("Error during CryptGetUserKey for signkey."); } }
CryptExportKey
我们可以通过CryptExportKey把key里面的公钥导出来,然后可以写到一个文件或者通过网络发送给别人。下面的例子就是导出到一个buffer里面。注意这里的第一个参数就是上面的CryptGetUserKey返回的。
if (CryptExportKey( hKey, NULL, PUBLICKEYBLOB, 0, NULL, &dwBlobLen)) { printf("Size of the BLOB for the public key determined. \n"); } else { MyHandleError("Error computing BLOB length."); } //------------------------------------------------------------------- // Allocate memory for the pbKeyBlob. if (pbKeyBlob = (BYTE*)malloc(dwBlobLen)) { printf("Memory has been allocated for the BLOB. \n"); } else { MyHandleError("Out of memory. \n"); } //------------------------------------------------------------------- // Do the actual exporting into the key BLOB. if (CryptExportKey( hKey, NULL, PUBLICKEYBLOB, 0, pbKeyBlob, &dwBlobLen)) { printf("Contents have been written to the BLOB. \n"); } else { MyHandleError("Error during CryptExportKey."); }
接下来就给指定的字符串计算一个hash值。
CryptCreateHash 和CryptHashData
先使用CryptCreateHash创建一个hash对象,使用MD5.
然后用这个hash对象把一个指定的buffer计算一个MD5值。最终的hash值保存在hash对象里面hHash。
//------------------------------------------------------------------- // Create the hash object. if (CryptCreateHash( hProv, CALG_MD5, 0, 0, &hHash)) { printf("Hash object created. \n"); } else { MyHandleError("Error during CryptCreateHash."); } //------------------------------------------------------------------- // Compute the cryptographic hash of the buffer. if (CryptHashData( hHash, pbBuffer, dwBufferLen, 0)) { printf("The data buffer has been hashed.\n"); } else { MyHandleError("Error during CryptHashData."); }
现在hash值也有了,那么接下来就是签名了。
CryptSignHash
通过这个API可以把hHash里面的hash值(md5)进行签名,也就是使用密钥进行加密。(密钥也存在于hHash中,因为hHash本身也是上面的hProv创建的)
下面的代码先技术签名所需要的大小,然后分配一块内存。这样CryptSignHash成功后,签名后的密文就保存在了pSignature里面。至此,我们成功得到了一段给定内容的hash值的签名(密文)。
//------------------------------------------------------------------- // Determine the size of the signature and allocate memory. dwSigLen = 0; if (CryptSignHash( hHash, AT_SIGNATURE, NULL, 0, NULL, &dwSigLen)) { printf("Signature length %d found.\n", dwSigLen); } else { MyHandleError("Error during CryptSignHash."); } //------------------------------------------------------------------- // Allocate memory for the signature buffer. if (pbSignature = (BYTE *)malloc(dwSigLen)) { printf("Memory allocated for the signature.\n"); } else { MyHandleError("Out of memory."); } //------------------------------------------------------------------- // Sign the hash object. if (CryptSignHash( hHash, AT_SIGNATURE, NULL, 0, pbSignature, &dwSigLen)) { printf("pbSignature is the hash signature.\n"); } else { MyHandleError("Error during CryptSignHash."); }
现在我们有了
1. 内容:BYTE *pbBuffer = (BYTE *)"The data that is to be hashed and signed.";
2. 签名后的hash值(密文)pSignature
3. 公钥 pbKeyblob
我们可以把这3个信息发给对方,然后对方需要
1. 用公钥把签名进行解密(其实还要先验证公钥(证书),这是另外一码事)
2. 把收到的内容进行hash计算
3. 把#1得到的签名明文和#2里面计算出来的hash值进行比较,如果一样的话,就说明数据有效。如果不一样,那么就说明数据被破坏了,有可能传输过程中被修改了,或者丢了。注意,至于别人冒充的问题,其实是通过验证公钥来确保的,一般需要把公钥和用户信息发给对方,也就是证书。这样对方就可以验证证书的真伪。这个是用来防止有人冒充,使用他们自己的证书。比如正常交易双方是A和B,A是客户。C可以截获这个网络包,然后C伪造内容,并且用C自己的私钥签名,并且把含有公钥的证书发给B。如果B不检查发过来的证书,那么就可以被骗。有关证书的鉴别就需要用到CA了。证书本身也是有签名的,证书签名的公钥在当前证书的上一级证书里面,直到最后的根证书(也就是CA拥有的那个根证书)。
OK, 有关具体的验证签名下次再介绍。