Microsoft CryptoAPI加密技术
Microsoft CryptoAPI加密技术 在这个信息爆炸的时代,我们不得不对信息的安全提高警惕。加密作为保障数据信息安全的一种方式,越来越受到人们的关注。 一、 加密方法: 当初,计算机的研究就是为了破解德国人的密码,人们并没有想到计算机给今天带来的信息革命。随着计算机的发展,运算能力的增强,密码学已经取得了巨大的进展。大体来说有以下几种形式。 二、 CryptoAPI 微软的CryptoAPI是PKI推荐使用的加密 API。其功能是为应用程序开发者提供在Win32环境下使用加密、验证等安全服务时的标准加密接口。CryptoAPI处于应用程序和CSP(cryptographic service provider)之间(见图一)。 CryptoAPI的编程模型同Windows系统的图形设备接口 GDI比较类似,其中加密服务提供者CSP等同于图形设备驱动程序 ,加密硬件(可选)等同于图形硬件,其上层的应用程序也类似,都不需要同设备驱动程序和硬件直接打交道。 CryptoAPI共有五部分组成:简单消息函数(Simplified Message Functions)、低层消息函数(Low-level Message Functions)、基本加密函数(Base Cryptographic Functions)、证书编解码函数(Certificate Encode/Decode Functions)和证书库管理函数(Certificate Store Functions)。其中前三者可用于对敏感信息进行加密或签名处理,可保证网络传输信心的私有性;后两者通过对证书的使用,可保证网络信息交流中的认证性。 三、 CSP 看到这里,大家也许对CSP还比较迷惑。其实CSP是真正实行加密的独立模块,他既可以由软件实现也可以由硬件实现。但是他必须符合CryptoAPI接口的规范。 每个CSP都有一个名字和一个类型。每个CSP的名字是唯一的,这样便于CryptoAPI找到对应的CSP。目前已经有9种CSP类型,并且还在增长。下表列出出它们支持的密钥交换算法、签名算法、对称加密算法和Hash算法。
从图一可以看到,每个CSP有一个密钥库,密钥库用于存储密钥。而每个密钥库包括一个或多个密钥容器(Key Containers)。每个密钥容器中含属于一个特定用户的所有密钥对。每个密钥容器被赋予一个唯一的名字。在销毁密钥容器前CSP将永久保存每一个密钥容器,包括保存每个密钥容器中的公/私钥对(见图二)。 四、 创建密钥容器,得到CSP句柄 说了这么多只是一些理论性的东西,后面将详细介绍一下Microsoft CryptoAPI的使用方法。 我们已经提过,每一个CSP都有一个名字和一个类型,并且名字保证唯一。所以可以通过名字和类型得到一个CSP。然而,要想加密肯定需要密钥,那么密钥放哪里呢?对了,就放在密钥容器。(有人会问,密码库有什么用?其实密钥库是在安装CSP的时候已经存在了,他与CSP是相对应的。)但是密钥容器并不是一开始就存在的,需要用户去创建。下面的代码实现以上功能(得到CSP即密码容器)。 if(CryptAcquireContext( &hCryptProv, // 返回CSP句柄 UserName, // 密码容器名 NULL, // NULL时使用默认CSP名(微软RSA Base Provider) PROV_RSA_FULL, // CSP类型 0)) // Flag values { //以UserName为名的密钥容器存在,那么我们已经得到了CSP的句柄printf("A crypto context with the %s key container \n", UserName);printf("has been acquired.\n\n"); } else //如果密钥容器不存在,我们需要创建这个密钥容器 { if(CryptAcquireContext(&hCryptProv, UserName, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) //创建以UserName为名的密钥容器{//创建密钥容器成功,并得到CSP句柄printf("A new key container has been created.\n");}else{HandleError("Could not create a new key container.\n");} } // End of else 好了,我们已经创建了密钥容器,并得到了CSP的句柄。也可以这样理解,我们得到了一个CSP的句柄,并且它被绑定到以UserName为名的密钥容器上。嘿嘿…… 那么,以后的加解密等操作,都将在这个CSP上进行。 五、 一个文件加密的例子 看到这里肯定有人开始说了,“这么多废话,还不快讲怎么加密怎么解密!”您先别急,有些原理性的东西还是先了解了比较好,对以后的使用会有很大帮助。 言归正传,我们来看一段文件加密的代码。 #include <stdio.h> #include <windows.h> #include <wincrypt.h> #define MY_ENCODING_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING) #define KEYLENGTH 0x00800000 void HandleError(char *s);//-------------------------------------------------------------------- // These additional #define statements are required. #define ENCRYPT_ALGORITHM CALG_RC4 #define ENCRYPT_BLOCK_SIZE 8 // Declare the function EncryptFile. The function definition // follows main.BOOL EncryptFile(PCHAR szSource, PCHAR szDestination, PCHAR szPassword); //-------------------------------------------------------------------- // Begin main.void main(void) { CHAR szSource[100]; CHAR szDestination[100]; CHAR szPassword[100]; printf("Encrypt a file. \n\n");printf("Enter the name of the file to be encrypted: ");scanf("%s",szSource);printf("Enter the name of the output file: ");scanf("%s",szDestination);printf("Enter the password:");scanf("%s",szPassword);//--------------------------------------------------------------------// Call EncryptFile to do the actual encryption.if(EncryptFile(szSource, szDestination, szPassword)){printf("Encryption of the file %s was a success. \n", szSource);printf("The encrypted data is in file %s.\n",szDestination);}else{HandleError("Error encrypting file!"); } } // End of main//-------------------------------------------------------------------- // Code for the function EncryptFile called by main.static BOOL EncryptFile(PCHAR szSource, PCHAR szDestination, PCHAR szPassword)//--------------------------------------------------------------------// Parameters passed are:// szSource, the name of the input, a plaintext file.// szDestination, the name of the output, an encrypted file to be // created.// szPassword, the password. { //--------------------------------------------------------------------// Declare and initialize local variables.FILE *hSource; FILE *hDestination; HCRYPTPROV hCryptProv; HCRYPTKEY hKey; HCRYPTHASH hHash; PBYTE pbBuffer; DWORD dwBlockLen; DWORD dwBufferLen; DWORD dwCount; //--------------------------------------------------------------------// Open source file. if(hSource = fopen(szSource,"rb")){printf("The source plaintext file, %s, is open. \n", szSource);}else{ HandleError("Error opening source plaintext file!");} //--------------------------------------------------------------------// Open destination file. if(hDestination = fopen(szDestination,"wb")){printf("Destination file %s is open. \n", szDestination);}else{HandleError("Error opening destination ciphertext file!"); }//以下获得一个CSP句柄if(CryptAcquireContext(&hCryptProv, NULL, //NULL表示使用默认密钥容器,默认密钥容器名 //为用户登陆名NULL, PROV_RSA_FULL, 0)){printf("A cryptographic provider has been acquired. \n");}else{if(CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET))//创建密钥容器{//创建密钥容器成功,并得到CSP句柄printf("A new key container has been created.\n");}else{HandleError("Could not create a new key container.\n");}}//--------------------------------------------------------------------// 创建一个会话密钥(session key)// 会话密钥也叫对称密钥,用于对称加密算法。// (注: 一个Session是指从调用函数CryptAcquireContext到调用函数// CryptReleaseContext 期间的阶段。会话密钥只能存在于一个会话过程)//--------------------------------------------------------------------// Create a hash object. if(CryptCreateHash(hCryptProv, CALG_MD5, 0, 0, &hHash)){printf("A hash object has been created. \n");}else{ HandleError("Error during CryptCreateHash!\n");} //--------------------------------------------------------------------// 用输入的密码产生一个散列if(CryptHashData(hHash, (BYTE *)szPassword, strlen(szPassword), 0)){printf("The password has been added to the hash. \n");}else{HandleError("Error during CryptHashData. \n"); }//--------------------------------------------------------------------// 通过散列生成会话密钥if(CryptDeriveKey(hCryptProv, ENCRYPT_ALGORITHM, hHash, KEYLENGTH, &hKey)){printf("An encryption key is derived from the password hash. \n"); }else{HandleError("Error during CryptDeriveKey!\n"); }//--------------------------------------------------------------------// Destroy the hash object. CryptDestroyHash(hHash); hHash = NULL; //--------------------------------------------------------------------// The session key is now ready. //--------------------------------------------------------------------// 因为加密算法是按ENCRYPT_BLOCK_SIZE 大小的块加密的,所以被加密的 // 数据长度必须是ENCRYPT_BLOCK_SIZE 的整数倍。下面计算一次加密的 // 数据长度。dwBlockLen = 1000 - 1000 % ENCRYPT_BLOCK_SIZE; //--------------------------------------------------------------------// Determine the block size. If a block cipher is used, // it must have room for an extra block. if(ENCRYPT_BLOCK_SIZE > 1) dwBufferLen = dwBlockLen + ENCRYPT_BLOCK_SIZE; else dwBufferLen = dwBlockLen; //--------------------------------------------------------------------// Allocate memory. if(pbBuffer = (BYTE *)malloc(dwBufferLen)){printf("Memory has been allocated for the buffer. \n");}else{ HandleError("Out of memory. \n"); }//--------------------------------------------------------------------// In a do loop, encrypt the source file and write to the source file. do { //--------------------------------------------------------------------// Read up to dwBlockLen bytes from the source file. dwCount = fread(pbBuffer, 1, dwBlockLen, hSource); if(ferror(hSource)){ HandleError("Error reading plaintext!\n");}//--------------------------------------------------------------------// 加密数据if(!CryptEncrypt(hKey, //密钥0, //如果数据同时进行散列和加密,这里传入一个 //散列对象feof(hSource), //如果是最后一个被加密的块,输入TRUE.如果不是输.//入FALSE这里通过判断是否到文件尾来决定是否为 //最后一块。0, //保留pbBuffer, //输入被加密数据,输出加密后的数据&dwCount, //输入被加密数据实际长度,输出加密后数据长度dwBufferLen)) //pbBuffer的大小。{ HandleError("Error during CryptEncrypt. \n"); } //--------------------------------------------------------------------// Write data to the destination file. fwrite(pbBuffer, 1, dwCount, hDestination); if(ferror(hDestination)){ HandleError("Error writing ciphertext.");}} while(!feof(hSource)); //--------------------------------------------------------------------// End the do loop when the last block of the source file has been// read, encrypted, and written to the destination file.//--------------------------------------------------------------------// Close files.if(hSource) fclose(hSource); if(hDestination) fclose(hDestination); //--------------------------------------------------------------------// Free memory. if(pbBuffer) free(pbBuffer); //--------------------------------------------------------------------// Destroy session key. if(hKey) CryptDestroyKey(hKey); //--------------------------------------------------------------------// Destroy hash object. if(hHash) CryptDestroyHash(hHash); //--------------------------------------------------------------------// Release provider handle. if(hCryptProv) CryptReleaseContext(hCryptProv, 0);return(TRUE); } // End of Encryptfile//-------------------------------------------------------------------- // This example uses the function HandleError, a simple error // handling function, to print an error message to the standard error // (stderr) file and exit the program. // For most applications, replace this function with one // that does more extensive error reporting.void HandleError(char *s) {fprintf(stderr,"An error occurred in running the program. \n");fprintf(stderr,"%s\n",s);fprintf(stderr, "Error number %x.\n", GetLastError());fprintf(stderr, "Program terminating. \n");exit(1); } // End of HandleError 上面的代码来自MSDN,并作了修改。注释已经很详细了,这里就不赘述了, 这次先写这么多,也许很多人觉得我写这些大家都知道,并且也太简单了。不要急慢慢来,嘿嘿:)接下来会有一些比较深入和实用的技术。 参考: |
上次我们讲了Microsoft CryptoAPI的构成以及会话密钥的使用。接下来我们将看一下公私密钥对的使用、HASH算法、数字签名等技术。
一、 公用密钥加密技术
公用密钥加密技术使用两个不同的密钥:公钥和私钥。私钥必须安全的保管好不能被外人知道,而公钥可以告诉任何人,只要他需要。通常公钥是以数字证书的形式发布的。
用公私密钥对中的一个密钥加密的数据只能用密钥对中的另一个密钥才能解密。也就是说用用户A的公钥加密的数据只能用A的私钥才能解密,同样,用A的私钥加密的数据只能用A的公钥才能解密。
如果用私钥签名一个消息,那么必须用与之对应的公钥去验证签名的有效性。
不幸的是公用密钥加密技术的效率非常低甚至只有对称加密的千分之一,所以不适合对大量的数据进行加密。实际上,公用密钥加密技术一般用来加密会话密钥,而数据加密可以用对称加密的方法。
好了,让我们回到Microsoft CryptoAPI。我们知道一个CSP有一个密钥库,这个密钥库有一个或多个密钥容器。而密钥容器中有什么呢?一般来说,一个密钥容器中有两对公私密钥对,一对用来加密会话密钥,而另一对用来进行数字签名,也就是大家知道的key exchange key pair和signature key pair。
那么,怎么得到这些密钥对呢?
if(CryptGetUserKey(hCryptProv, // 我们已经得到的CSP句柄AT_SIGNATURE, // 这里想得到signature key pair&hKey)) // 返回密钥句柄 {printf("A signature key is available.\n"); } else //取signature key pair错误 {printf("No signature key is available.\n");if(GetLastError() == NTE_NO_KEY) //密钥容器里不存在signature key pair{// 创建 signature key pair. printf("The signature key does not exist.\n");printf("Create a signature key pair.\n"); if(CryptGenKey(hCryptProv, //CSP句柄AT_SIGNATURE, //创建的密钥对类型为signature key pair0, //key类型,这里用默认值&hKey)) //创建成功返回新创建的密钥对的句柄{printf("Created a signature key pair.\n");}else{printf ("Error occurred creating a signature key.\n"); }}else{printf ("An error other than NTE_NO_KEY getting signature\key.\n");} } // end if
将参数AT_SIGNATURE换成AT_KEYEXCHANGE就可以得到key exchange key pair。
现在我们得到的仅仅是一个句柄,我们需要把这个key值存储的磁盘或文件中,这样才能传给对方来进行解密。下面让我们来看一个用于导出密钥的API。
BOOL WINAPI CryptExportKey(HCRYPTKEY hKey,HCRYPTKEY hExpKey,DWORD dwBlobType,DWORD dwFlags,BYTE* pbData,DWORD* pdwDataLen );
hKey: 需要被导出的密钥句柄
hExpKey: 前面咱们提到公用密钥加密技术的效率非常低所以公用密钥加密技术
一般用来加密会话密钥。这里传入的密钥就是用来加密被导出的密钥
的。也就是说,被导出的密钥hKey的数据是经过这个密钥hExpKey
加密的。如果为NULL表示不经过加密直接导出。
dwBlobType: 被导出的密钥类型,比如公钥还是私钥等
dwFlags: 标志位
pbData: 保存导出的数据,如果为NULL, pdwDataLen将返回导出数据的长度
pdwDataLen: 输入pbData缓冲区的大小,输出导出数据的长度
下面的例子演示如何导出密钥。
if(CryptExportKey( hKey, NULL, PUBLICKEYBLOB, //导出公钥0, NULL, &dwBlobLen)) //返回密钥数据长度 {printf("Size of the BLOB for the public key determined. \n"); } else {printf("Error computing BLOB length.\n");exit(1); } //-------------------------------------------------------------------- // Allocate memory for the pbKeyBlob.if(pbKeyBlob = (BYTE*)malloc(dwBlobLen)) {printf("Memory has been allocated for the BLOB. \n"); } else {printf("Out of memory. \n");exit(1); } //-------------------------------------------------------------------- // 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 {printf("Error exporting key.\n");exit(1); }
如果要导出用公用密钥加密技术加密的密钥,只要把API的第二个参数传入一个key exchange key pair句柄就可以了。
既然有了导出当然要有导入。
BOOL WINAPI CryptImportKey(HCRYPTPROV hProv, //CSP句柄BYTE* pbData, //要导入的密钥数据DWORD dwDataLen, //数据长度HCRYPTKEY hPubKey, //如果数据是被加密的这里输入解密用的密钥句柄DWORD dwFlags, //标志位HCRYPTKEY* phKey //导入后返回的密钥句柄 );
这个API比较简单,这里就不举例说明了,在以后的例子里会看到。
二、 HASH
Hash简单点讲就是把任意一段数据经过某种算法生成一段唯一的固定长度的数据。也叫做摘要。为了确保数据A免受意外或者故意(恶意)的修改,往往用这段数据A产生一个hash数据一起发送出去,接收方可以通过相同的hash算法用这段接收到的数据A产生一个新的hash数据并与接收到的hash数据比较,来验证数据A是否为真实完整的数据。
下面的API用来创建hash对象
BOOL WINAPI CryptCreateHash(HCRYPTPROV hProv, //CSP句柄ALG_ID Algid, //选择hash算法,比如CALG_MD5等HCRYPTKEY hKey, //HMAC 和MAC算法时有用DWORD dwFlags, //保留,传入0即可HCRYPTHASH* phHash //返回hash句柄 );if(CryptCreateHash(hCryptProv, CALG_MD5, 0, 0, &hHash)) {printf("An empty hash object has been created. \n"); } else {printf("Error during CryptBeginHash!\n");exit(1); }// Insert code that uses the hash object here.//-------------------------------------------------------------------- // After processing, hHash must be released.if(hHash) CryptDestroyHash(hHash); //释放句柄
我们已经得到hash对象了,下面就找点数据试试,咱也去哈一下,当然这里可不是哈日哈韩的哈,更不是哈巴狗的哈,嘿嘿。Let’s go!!
哎呀!!不好意思,忘记了介绍一个API,看看先。
BOOL WINAPI CryptHashData(HCRYPTHASH hHash, //hash对象BYTE* pbData, //被hash的数据DWORD dwDataLen, //数据的长度DWORD dwFlags //微软的CSP这个值会被忽略 );
下面代码:
BYTE *pbBuffer= (BYTE *)"The data that is to be hashed."; DWORD dwBufferLen = strlen((char *)pbBuffer)+1;if(CryptHashData(hHash, pbBuffer, dwBufferLen, 0)) {printf("The data buffer has been added to the hash.\n"); } else {printf("Error during CryptHashData.\n");exit(1); }
现在,pbBuffer里的内容已经被hash了,然后我们需要导出哈希后的数据。
BYTE *pbHash; BYTE *pbHashSize; DWORD dwHashLen = sizeof(DWORD); DWORD i;if(!(pbHashSize =(BYTE *) malloc(dwHashLen))) MyHandleError("Memory allocation failed.");//下面的这次调用我没搞清楚:( 我怎么觉得没有必要!! if(CryptGetHashParam(hHash, HP_HASHSIZE, //取hash数据的大小pbHashSize, //输出hash数据大小的缓冲区&dwHashLen, //缓冲区大小0)) { // It worked. Free pbHashSize.free(pbHashSize); } else {MyHandleError("CryptGetHashParam failed to get size."); }if(CryptGetHashParam(hHash, HP_HASHVAL, //取hash值NULL, //设为NULL,在dwHashLen返回需要的输出缓冲区大小&dwHashLen, //输出缓冲区大小0)) { // It worked. Do nothing. } else {MyHandleError("CryptGetHashParam failed to get length."); }if(pbHash = (BYTE*)malloc(dwHashLen)) { // It worked. Do nothing. } else {MyHandleError("Allocation failed."); }if(CryptGetHashParam(hHash, HP_HASHVAL, //取hash值pbHash, //返回Hash数据&dwHashLen, //hash数据长度0)) {// Print the hash value.printf("The hash is: ");for(i = 0 ; i < dwHashLen ; i++) {printf("%2.2x ",pbHash[i]);} printf("\n"); } else {MyHandleError("Error during reading hash value."); } free(pbHash);
三、 数字签名
发布一个纯文本形式信息时,接收者可以用数字签名来鉴别和验证信息的发送者。对信息签名并不改变这个信息,只是生成一个数字签名串随信息一起传送,或单独传送。
一个数字签名,就是一段被用发送者的私钥加密的数据段,而接收者只有拥有发送者的公钥才能解密这个数据段。表示如下:
由Message生成数字签名有两步。首先,对Message进行hash处理,产生hash数据。然后用签名者A的私钥对这个hash数据加密。具体如下:
验证一个签名需要上图表示的Message和Digital signatures。首先跟生成时一样对Message进行hash处理,产生hash数据。然后通过签名者A的公钥、Digital signatures以及刚生成的hash数据进行验证。具体如下:
好了,你是否学会数字签名了呢?很多技术名词听起来很唬人,其实本来是很简单的!!嘿嘿。
随文档的例程几乎将用到我们上面讲的所有内容。
参考资料:
MSDN相关章节。
Microsoft CryptoAPI加密技术 下载本文示例源代码 在这个信息爆炸的时代,我们不得不对信息的安全提高警惕。加密作为保障数据信息安全的一种方式,越来越受到人们的关注。 一、 加密方法: 当初,计算机的研究就是为了破解德国人的密码,人们并没有想到计算机给今天带来的信息革命。随着计算机的发展,运算能力的增强,密码学已经取得了巨大的进展。大体来说有以下几种形式。 二、 CryptoAPI 微软的CryptoAPI是PKI推荐使用的加密 API。其功能是为应用程序开发者提供在Win32环境下使用加密、验证等安全服务时的标准加密接口。CryptoAPI处于应用程序和CSP(cryptographic service provider)之间(见图一)。 CryptoAPI的编程模型同Windows系统的图形设备接口 GDI比较类似,其中加密服务提供者CSP等同于图形设备驱动程序 ,加密硬件(可选)等同于图形硬件,其上层的应用程序也类似,都不需要同设备驱动程序和硬件直接打交道。 CryptoAPI共有五部分组成:简单消息函数(Simplified Message Functions)、低层消息函数(Low-level Message Functions)、基本加密函数(Base Cryptographic Functions)、证书编解码函数(Certificate Encode/Decode Functions)和证书库管理函数(Certificate Store Functions)。其中前三者可用于对敏感信息进行加密或签名处理,可保证网络传输信心的私有性;后两者通过对证书的使用,可保证网络信息交流中的认证性。 三、 CSP 看到这里,大家也许对CSP还比较迷惑。其实CSP是真正实行加密的独立模块,他既可以由软件实现也可以由硬件实现。但是他必须符合CryptoAPI接口的规范。 每个CSP都有一个名字和一个类型。每个CSP的名字是唯一的,这样便于CryptoAPI找到对应的CSP。目前已经有9种CSP类型,并且还在增长。下表列出出它们支持的密钥交换算法、签名算法、对称加密算法和Hash算法。
从图一可以看到,每个CSP有一个密钥库,密钥库用于存储密钥。而每个密钥库包括一个或多个密钥容器(Key Containers)。每个密钥容器中含属于一个特定用户的所有密钥对。每个密钥容器被赋予一个唯一的名字。在销毁密钥容器前CSP将永久保存每一个密钥容器,包括保存每个密钥容器中的公/私钥对(见图二)。 四、 创建密钥容器,得到CSP句柄 说了这么多只是一些理论性的东西,后面将详细介绍一下Microsoft CryptoAPI的使用方法。 我们已经提过,每一个CSP都有一个名字和一个类型,并且名字保证唯一。所以可以通过名字和类型得到一个CSP。然而,要想加密肯定需要密钥,那么密钥放哪里呢?对了,就放在密钥容器。(有人会问,密码库有什么用?其实密钥库是在安装CSP的时候已经存在了,他与CSP是相对应的。)但是密钥容器并不是一开始就存在的,需要用户去创建。下面的代码实现以上功能(得到CSP即密码容器)。 if(CryptAcquireContext( &hCryptProv, // 返回CSP句柄 UserName, // 密码容器名 NULL, // NULL时使用默认CSP名(微软RSA Base Provider) PROV_RSA_FULL, // CSP类型 0)) // Flag values { //以UserName为名的密钥容器存在,那么我们已经得到了CSP的句柄printf("A crypto context with the %s key container \n", UserName);printf("has been acquired.\n\n"); } else //如果密钥容器不存在,我们需要创建这个密钥容器 { if(CryptAcquireContext(&hCryptProv, UserName, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) //创建以UserName为名的密钥容器{//创建密钥容器成功,并得到CSP句柄printf("A new key container has been created.\n");}else{HandleError("Could not create a new key container.\n");} } // End of else 好了,我们已经创建了密钥容器,并得到了CSP的句柄。也可以这样理解,我们得到了一个CSP的句柄,并且它被绑定到以UserName为名的密钥容器上。嘿嘿…… 那么,以后的加解密等操作,都将在这个CSP上进行。 五、 一个文件加密的例子 看到这里肯定有人开始说了,“这么多废话,还不快讲怎么加密怎么解密!”您先别急,有些原理性的东西还是先了解了比较好,对以后的使用会有很大帮助。 言归正传,我们来看一段文件加密的代码。 #include <stdio.h> #include <windows.h> #include <wincrypt.h> #define MY_ENCODING_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING) #define KEYLENGTH 0x00800000 void HandleError(char *s);//-------------------------------------------------------------------- // These additional #define statements are required. #define ENCRYPT_ALGORITHM CALG_RC4 #define ENCRYPT_BLOCK_SIZE 8 // Declare the function EncryptFile. The function definition // follows main.BOOL EncryptFile(PCHAR szSource, PCHAR szDestination, PCHAR szPassword); //-------------------------------------------------------------------- // Begin main.void main(void) { CHAR szSource[100]; CHAR szDestination[100]; CHAR szPassword[100]; printf("Encrypt a file. \n\n");printf("Enter the name of the file to be encrypted: ");scanf("%s",szSource);printf("Enter the name of the output file: ");scanf("%s",szDestination);printf("Enter the password:");scanf("%s",szPassword);//--------------------------------------------------------------------// Call EncryptFile to do the actual encryption.if(EncryptFile(szSource, szDestination, szPassword)){printf("Encryption of the file %s was a success. \n", szSource);printf("The encrypted data is in file %s.\n",szDestination);}else{HandleError("Error encrypting file!"); } } // End of main//-------------------------------------------------------------------- // Code for the function EncryptFile called by main.static BOOL EncryptFile(PCHAR szSource, PCHAR szDestination, PCHAR szPassword)//--------------------------------------------------------------------// Parameters passed are:// szSource, the name of the input, a plaintext file.// szDestination, the name of the output, an encrypted file to be // created.// szPassword, the password. { //--------------------------------------------------------------------// Declare and initialize local variables.FILE *hSource; FILE *hDestination; HCRYPTPROV hCryptProv; HCRYPTKEY hKey; HCRYPTHASH hHash; PBYTE pbBuffer; DWORD dwBlockLen; DWORD dwBufferLen; DWORD dwCount; //--------------------------------------------------------------------// Open source file. if(hSource = fopen(szSource,"rb")){printf("The source plaintext file, %s, is open. \n", szSource);}else{ HandleError("Error opening source plaintext file!");} //--------------------------------------------------------------------// Open destination file. if(hDestination = fopen(szDestination,"wb")){printf("Destination file %s is open. \n", szDestination);}else{HandleError("Error opening destination ciphertext file!"); }//以下获得一个CSP句柄if(CryptAcquireContext(&hCryptProv, NULL, //NULL表示使用默认密钥容器,默认密钥容器名 //为用户登陆名NULL, PROV_RSA_FULL, 0)){printf("A cryptographic provider has been acquired. \n");}else{if(CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET))//创建密钥容器{//创建密钥容器成功,并得到CSP句柄printf("A new key container has been created.\n");}else{HandleError("Could not create a new key container.\n");}}//--------------------------------------------------------------------// 创建一个会话密钥(session key)// 会话密钥也叫对称密钥,用于对称加密算法。// (注: 一个Session是指从调用函数CryptAcquireContext到调用函数// CryptReleaseContext 期间的阶段。会话密钥只能存在于一个会话过程)//--------------------------------------------------------------------// Create a hash object. if(CryptCreateHash(hCryptProv, CALG_MD5, 0, 0, &hHash)){printf("A hash object has been created. \n");}else{ HandleError("Error during CryptCreateHash!\n");} //--------------------------------------------------------------------// 用输入的密码产生一个散列if(CryptHashData(hHash, (BYTE *)szPassword, strlen(szPassword), 0)){printf("The password has been added to the hash. \n");}else{HandleError("Error during CryptHashData. \n"); }//--------------------------------------------------------------------// 通过散列生成会话密钥if(CryptDeriveKey(hCryptProv, ENCRYPT_ALGORITHM, hHash, KEYLENGTH, &hKey)){printf("An encryption key is derived from the password hash. \n"); }else{HandleError("Error during CryptDeriveKey!\n"); }//--------------------------------------------------------------------// Destroy the hash object. CryptDestroyHash(hHash); hHash = NULL; //--------------------------------------------------------------------// The session key is now ready. //--------------------------------------------------------------------// 因为加密算法是按ENCRYPT_BLOCK_SIZE 大小的块加密的,所以被加密的 // 数据长度必须是ENCRYPT_BLOCK_SIZE 的整数倍。下面计算一次加密的 // 数据长度。dwBlockLen = 1000 - 1000 % ENCRYPT_BLOCK_SIZE; //--------------------------------------------------------------------// Determine the block size. If a block cipher is used, // it must have room for an extra block. if(ENCRYPT_BLOCK_SIZE > 1) dwBufferLen = dwBlockLen + ENCRYPT_BLOCK_SIZE; else dwBufferLen = dwBlockLen; //--------------------------------------------------------------------// Allocate memory. if(pbBuffer = (BYTE *)malloc(dwBufferLen)){printf("Memory has been allocated for the buffer. \n");}else{ HandleError("Out of memory. \n"); }//--------------------------------------------------------------------// In a do loop, encrypt the source file and write to the source file. do { //--------------------------------------------------------------------// Read up to dwBlockLen bytes from the source file. dwCount = fread(pbBuffer, 1, dwBlockLen, hSource); if(ferror(hSource)){ HandleError("Error reading plaintext!\n");}//--------------------------------------------------------------------// 加密数据if(!CryptEncrypt(hKey, //密钥0, //如果数据同时进行散列和加密,这里传入一个 //散列对象feof(hSource), //如果是最后一个被加密的块,输入TRUE.如果不是输.//入FALSE这里通过判断是否到文件尾来决定是否为 //最后一块。0, //保留pbBuffer, //输入被加密数据,输出加密后的数据&dwCount, //输入被加密数据实际长度,输出加密后数据长度dwBufferLen)) //pbBuffer的大小。{ HandleError("Error during CryptEncrypt. \n"); } //--------------------------------------------------------------------// Write data to the destination file. fwrite(pbBuffer, 1, dwCount, hDestination); if(ferror(hDestination)){ HandleError("Error writing ciphertext.");}} while(!feof(hSource)); //--------------------------------------------------------------------// End the do loop when the last block of the source file has been// read, encrypted, and written to the destination file.//--------------------------------------------------------------------// Close files.if(hSource) fclose(hSource); if(hDestination) fclose(hDestination); //--------------------------------------------------------------------// Free memory. if(pbBuffer) free(pbBuffer); //--------------------------------------------------------------------// Destroy session key. if(hKey) CryptDestroyKey(hKey); //--------------------------------------------------------------------// Destroy hash object. if(hHash) CryptDestroyHash(hHash); //--------------------------------------------------------------------// Release provider handle. if(hCryptProv) CryptReleaseContext(hCryptProv, 0);return(TRUE); } // End of Encryptfile//-------------------------------------------------------------------- // This example uses the function HandleError, a simple error // handling function, to print an error message to the standard error // (stderr) file and exit the program. // For most applications, replace this function with one // that does more extensive error reporting.void HandleError(char *s) {fprintf(stderr,"An error occurred in running the program. \n");fprintf(stderr,"%s\n",s);fprintf(stderr, "Error number %x.\n", GetLastError());fprintf(stderr, "Program terminating. \n");exit(1); } // End of HandleError 上面的代码来自MSDN,并作了修改。注释已经很详细了,这里就不赘述了, 这次先写这么多,也许很多人觉得我写这些大家都知道,并且也太简单了。不要急慢慢来,嘿嘿:)接下来会有一些比较深入和实用的技术。 参考: |
上次我们讲了Microsoft CryptoAPI的构成以及会话密钥的使用。接下来我们将看一下公私密钥对的使用、HASH算法、数字签名等技术。
一、 公用密钥加密技术
公用密钥加密技术使用两个不同的密钥:公钥和私钥。私钥必须安全的保管好不能被外人知道,而公钥可以告诉任何人,只要他需要。通常公钥是以数字证书的形式发布的。
用公私密钥对中的一个密钥加密的数据只能用密钥对中的另一个密钥才能解密。也就是说用用户A的公钥加密的数据只能用A的私钥才能解密,同样,用A的私钥加密的数据只能用A的公钥才能解密。
如果用私钥签名一个消息,那么必须用与之对应的公钥去验证签名的有效性。
不幸的是公用密钥加密技术的效率非常低甚至只有对称加密的千分之一,所以不适合对大量的数据进行加密。实际上,公用密钥加密技术一般用来加密会话密钥,而数据加密可以用对称加密的方法。
好了,让我们回到Microsoft CryptoAPI。我们知道一个CSP有一个密钥库,这个密钥库有一个或多个密钥容器。而密钥容器中有什么呢?一般来说,一个密钥容器中有两对公私密钥对,一对用来加密会话密钥,而另一对用来进行数字签名,也就是大家知道的key exchange key pair和signature key pair。
那么,怎么得到这些密钥对呢?
if(CryptGetUserKey(hCryptProv, // 我们已经得到的CSP句柄AT_SIGNATURE, // 这里想得到signature key pair&hKey)) // 返回密钥句柄 {printf("A signature key is available.\n"); } else //取signature key pair错误 {printf("No signature key is available.\n");if(GetLastError() == NTE_NO_KEY) //密钥容器里不存在signature key pair{// 创建 signature key pair. printf("The signature key does not exist.\n");printf("Create a signature key pair.\n"); if(CryptGenKey(hCryptProv, //CSP句柄AT_SIGNATURE, //创建的密钥对类型为signature key pair0, //key类型,这里用默认值&hKey)) //创建成功返回新创建的密钥对的句柄{printf("Created a signature key pair.\n");}else{printf ("Error occurred creating a signature key.\n"); }}else{printf ("An error other than NTE_NO_KEY getting signature\key.\n");} } // end if
将参数AT_SIGNATURE换成AT_KEYEXCHANGE就可以得到key exchange key pair。
现在我们得到的仅仅是一个句柄,我们需要把这个key值存储的磁盘或文件中,这样才能传给对方来进行解密。下面让我们来看一个用于导出密钥的API。
BOOL WINAPI CryptExportKey(HCRYPTKEY hKey,HCRYPTKEY hExpKey,DWORD dwBlobType,DWORD dwFlags,BYTE* pbData,DWORD* pdwDataLen );
hKey: 需要被导出的密钥句柄
hExpKey: 前面咱们提到公用密钥加密技术的效率非常低所以公用密钥加密技术
一般用来加密会话密钥。这里传入的密钥就是用来加密被导出的密钥
的。也就是说,被导出的密钥hKey的数据是经过这个密钥hExpKey
加密的。如果为NULL表示不经过加密直接导出。
dwBlobType: 被导出的密钥类型,比如公钥还是私钥等
dwFlags: 标志位
pbData: 保存导出的数据,如果为NULL, pdwDataLen将返回导出数据的长度
pdwDataLen: 输入pbData缓冲区的大小,输出导出数据的长度
下面的例子演示如何导出密钥。
if(CryptExportKey( hKey, NULL, PUBLICKEYBLOB, //导出公钥0, NULL, &dwBlobLen)) //返回密钥数据长度 {printf("Size of the BLOB for the public key determined. \n"); } else {printf("Error computing BLOB length.\n");exit(1); } //-------------------------------------------------------------------- // Allocate memory for the pbKeyBlob.if(pbKeyBlob = (BYTE*)malloc(dwBlobLen)) {printf("Memory has been allocated for the BLOB. \n"); } else {printf("Out of memory. \n");exit(1); } //-------------------------------------------------------------------- // 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 {printf("Error exporting key.\n");exit(1); }
如果要导出用公用密钥加密技术加密的密钥,只要把API的第二个参数传入一个key exchange key pair句柄就可以了。
既然有了导出当然要有导入。
BOOL WINAPI CryptImportKey(HCRYPTPROV hProv, //CSP句柄BYTE* pbData, //要导入的密钥数据DWORD dwDataLen, //数据长度HCRYPTKEY hPubKey, //如果数据是被加密的这里输入解密用的密钥句柄DWORD dwFlags, //标志位HCRYPTKEY* phKey //导入后返回的密钥句柄 );
这个API比较简单,这里就不举例说明了,在以后的例子里会看到。
二、 HASH
Hash简单点讲就是把任意一段数据经过某种算法生成一段唯一的固定长度的数据。也叫做摘要。为了确保数据A免受意外或者故意(恶意)的修改,往往用这段数据A产生一个hash数据一起发送出去,接收方可以通过相同的hash算法用这段接收到的数据A产生一个新的hash数据并与接收到的hash数据比较,来验证数据A是否为真实完整的数据。
下面的API用来创建hash对象
BOOL WINAPI CryptCreateHash(HCRYPTPROV hProv, //CSP句柄ALG_ID Algid, //选择hash算法,比如CALG_MD5等HCRYPTKEY hKey, //HMAC 和MAC算法时有用DWORD dwFlags, //保留,传入0即可HCRYPTHASH* phHash //返回hash句柄 );if(CryptCreateHash(hCryptProv, CALG_MD5, 0, 0, &hHash)) {printf("An empty hash object has been created. \n"); } else {printf("Error during CryptBeginHash!\n");exit(1); }// Insert code that uses the hash object here.//-------------------------------------------------------------------- // After processing, hHash must be released.if(hHash) CryptDestroyHash(hHash); //释放句柄
我们已经得到hash对象了,下面就找点数据试试,咱也去哈一下,当然这里可不是哈日哈韩的哈,更不是哈巴狗的哈,嘿嘿。Let’s go!!
哎呀!!不好意思,忘记了介绍一个API,看看先。
BOOL WINAPI CryptHashData(HCRYPTHASH hHash, //hash对象BYTE* pbData, //被hash的数据DWORD dwDataLen, //数据的长度DWORD dwFlags //微软的CSP这个值会被忽略 );
下面代码:
BYTE *pbBuffer= (BYTE *)"The data that is to be hashed."; DWORD dwBufferLen = strlen((char *)pbBuffer)+1;if(CryptHashData(hHash, pbBuffer, dwBufferLen, 0)) {printf("The data buffer has been added to the hash.\n"); } else {printf("Error during CryptHashData.\n");exit(1); }
现在,pbBuffer里的内容已经被hash了,然后我们需要导出哈希后的数据。
BYTE *pbHash; BYTE *pbHashSize; DWORD dwHashLen = sizeof(DWORD); DWORD i;if(!(pbHashSize =(BYTE *) malloc(dwHashLen))) MyHandleError("Memory allocation failed.");//下面的这次调用我没搞清楚:( 我怎么觉得没有必要!! if(CryptGetHashParam(hHash, HP_HASHSIZE, //取hash数据的大小pbHashSize, //输出hash数据大小的缓冲区&dwHashLen, //缓冲区大小0)) { // It worked. Free pbHashSize.free(pbHashSize); } else {MyHandleError("CryptGetHashParam failed to get size."); }if(CryptGetHashParam(hHash, HP_HASHVAL, //取hash值NULL, //设为NULL,在dwHashLen返回需要的输出缓冲区大小&dwHashLen, //输出缓冲区大小0)) { // It worked. Do nothing. } else {MyHandleError("CryptGetHashParam failed to get length."); }if(pbHash = (BYTE*)malloc(dwHashLen)) { // It worked. Do nothing. } else {MyHandleError("Allocation failed."); }if(CryptGetHashParam(hHash, HP_HASHVAL, //取hash值pbHash, //返回Hash数据&dwHashLen, //hash数据长度0)) {// Print the hash value.printf("The hash is: ");for(i = 0 ; i < dwHashLen ; i++) {printf("%2.2x ",pbHash[i]);} printf("\n"); } else {MyHandleError("Error during reading hash value."); } free(pbHash);
三、 数字签名
发布一个纯文本形式信息时,接收者可以用数字签名来鉴别和验证信息的发送者。对信息签名并不改变这个信息,只是生成一个数字签名串随信息一起传送,或单独传送。
一个数字签名,就是一段被用发送者的私钥加密的数据段,而接收者只有拥有发送者的公钥才能解密这个数据段。表示如下:
由Message生成数字签名有两步。首先,对Message进行hash处理,产生hash数据。然后用签名者A的私钥对这个hash数据加密。具体如下:
验证一个签名需要上图表示的Message和Digital signatures。首先跟生成时一样对Message进行hash处理,产生hash数据。然后通过签名者A的公钥、Digital signatures以及刚生成的hash数据进行验证。具体如下:
好了,你是否学会数字签名了呢?很多技术名词听起来很唬人,其实本来是很简单的!!嘿嘿。
随文档的例程几乎将用到我们上面讲的所有内容。
参考资料:
MSDN相关章节。
文章一:使用windows crypto API加密解密
本文转自: http://ticktick.blog.51cto.com/823160/168538
网上的东西,许多都是来自别人的经验介绍,很多,但也很杂,为了搜索到真正有用的资料,往往需要花费很多的时间和精力。
首先,大家不要觉得加密和解密是一件很深奥的事情,其实说白了,数据加密就是对原始数据进行一种变换而已,比如:你的密码是123456,我对每一位都加1,即234567,写到文件中,这就是加密,只要别人不知道你是对每一位都加1得到的234567,别人即使拿到你的密码文件,他也不知道怎样把你的密码从234567解密出来。(当然,这种简单的加密是可以很容易被破解的)
数据加密主要分为两种,一种为对称加密,另一种为非对称加密。
对称加密主要用于大量数据的加密,加密和解密都使用同一份密钥,密钥其实就是一串数据,使用这一串数据对你要加密的数据进行与啊、或啊或者异或什么的,于是就得到了一串人家看不懂的“密文”。
非对称加密最经典的应用场合是客户端/服务器模式的系统,主要用于需要在网络中传送的少量数据进行加密(比如客户端的用户名、密码向服务器传送)。它使用两份密钥,公钥和私钥。
公钥用于发给客户端,私钥留在服务器端。数据经过公钥加密后只能使用对应的私钥来解密。
因此,客户端先对用户名密码信息使用公钥加密,然后向服务器端发送,即使中途被黑客截获,由于他没有私钥,故无法进行解密,当服务器端收到数据后,就使用自己的私钥进行解密。这样就可以实现所有的客户端都使用公钥进行加密,然后放心地发送数据到服务器端,因为只有服务器端才能够解密。(思考一下:这种场合如果使用对称密钥安全吗?)
windows crypto API提供了对称加密和非对称加密,并且提供了各种加密、解密的算法,要使用相应的算法进行加密解密,只需要对生成密钥的函数的相关参数改变一下即可。
为了学习使用windows crypto API,我沿袭以前的习惯,花费了大量的时间在网上搜索资料(许多资料用处不大),最终还是在MSND的指导下完全掌握了crypto API的用法,不过,MSND还是有其不足之处,它详细描述了crypto API相关的知识和信息,但没有从宏观上对实现用crypto API加密解密到底需要做哪些事情进行做出明确地指导,故在此,我用下面这幅流程图来告诉大家。
![](/assets/blank.gif)
我只从宏观上描述了整个加密解密需要进行的几个流程,具体每一步的代码怎么写,可以参考MSDN,或者本文后面链接的一份文档(这份文档讲得非常详细,也非常好,是我搜集的资料中最好的一份,希望对需要它的人有所帮助)
--CryptoAPI对数据进行加解密
文章二:使用Microsoft CryptoAPI进行加密、解密、签名及验证
本文转自:http://blog.csdn.net/mmpire/article/details/1640670
九、 代码:
http://blog.csdn.net/mmpire/article/details/1640670
Microsoft CryptoAPI加密技术相关推荐
- Microsoft CryptoAPI加密技术(一)
http://www.vckbase.com/index.php/wv/716.html 在这个信息爆炸的时代,我们不得不对信息的安全提高警惕.加密作为保障数据信息安全的一种方式,越来越受到人们的关注 ...
- Microsoft CryptoAPI加密技术(二)
原文:http://www.vckbase.com/index.php/wv/717.html 上次我们讲了Microsoft CryptoAPI的构成以及会话密钥的使用.接下来我们将看一下公私密钥对 ...
- 软件加密技术及实现(转载)
标题 软件加密技术及实现 选择自 whinah 的 Blog 关键字 encrypt 软件加密 保护 散列 数字签名 出处 软件加密技术及实现 雷 鹏 ( 桂林电子工业学院 计算 ...
- 【安全加密技术】 对称加密
转载请注明出处:http://blog.csdn.net/sk719887916/article/details/46822663 上篇了解了<非对称加密>后 今天我来继续了解下加密技术中 ...
- 苹果大战FBI将加速科技圈的加密技术发展?
苹果和 FBI 最终还是没有因为一部手机而走上法庭,但在此之前双方已经经历了多种形式的明争暗斗.回顾本次事件,我们可以概括为:FBI 想要让苹果解锁一部恐怖分子的 iPhone,但是遭到了拒绝.然后 ...
- 快速配置Windows 2003平台下实现 IIS(WEB)站点的安全(SSL加密技术!)
[实验名称] 快速配置Windows 2003平台下实现 IIS(WEB)站点的安全(SSL加密技术!) [实验基本概念] A. 对于公用信息--------------------www.Sohu. ...
- 2020-10-23(SMC加密技术)
第一篇博客,写得不好还请多多谅解. 今日收获: 今天接触了SMC加密技术,代码一般般,也就自我解密代码这块我这个初学者难以想到. 代码奉上,静态没法查看 judge里面的东西,需要jdb或者ida动态 ...
- *** 隧道和加密技术知识要点
×××分为二层隧道协议和三层隧道协议 一. 第二层隧道协议有PPTP L2F L2TP 主要用作远程访问×××的隧道协议 第二层隧道协议建立在点对点的协议PPP的基础上的,充分利用了PPP支持 ...
- [数据加密]GIS空间数据水印信息隐藏与加密技术方法[转]
到目前为止,国内外数字水印技术的研究主要集中在图像.视频和声音等多媒体信息的版权保护上,在GIS空间数据中,通过隐藏水印信息并对其加密.压缩以实现其安全保护的研究还很少,这是数字水印技术应用的一个新领 ...
最新文章
- python基础 继承
- iOS-生成国际化包-配置App多语言支持
- 医疗影像网络PACS系统方案
- c++读取txt中每行的数据到数组中
- Java高阶部分知识点汇总(三)-内存空间的引用
- Mac下Ruby升级与Rails的安装
- 线程、进程、多线程、多进程和多任务有啥关系?
- 5G的场景、需求、通信速率
- Android -- 发送Broadcast、有序无序
- 宁夏公安打传销端窝点为春节保平安
- 【Flink】Flink各种UDF简介
- matlab中取文件名函数,matlab从文件夹名中获取该文件夹下所图像文件名
- zz详细讲解Quartz.NET
- oracle11g 时间失效,关于oracle11g RAC 的CTSS与ntp时间同步的疑问
- 快速构建Windows 8风格应用25-数据绑定
- 银行自助填表方案介绍
- 在MAME里如何设置组合键
- 致远OA自定义函数--正则表达式匹配校验
- 央行超级网银8月上线;Win7市场份额突破10%(每日关注2010.4.2)
- Linux下useradd命令与adduser命令的区别(adduser更适合初级使用者,useradd比较适合有些高阶经验的使用者)
热门文章
- html中定义动画名字和规则,@keyframes
- 除法:传统除法、floor和真除法
- 闰年 ( The Leap Year) 怎么判断
- 含泪整理最优质效果图后期素材素材,你想要的这里都有
- 计算机制图英语试题,2017年cad期末考试训练试题「附答案」
- 【自考】网络经济与企业管理之初识总结
- HTML实现视频直播功能
- “任性”华为 | 七十八岁老人的“四渡赤水”(二)
- BZOJ2738: 矩阵乘法(整体二分)
- Bootstrap笔记(十四) 常用类别 - 间距