Microsoft CryptoAPI加密技术

在这个信息爆炸的时代,我们不得不对信息的安全提高警惕。加密作为保障数据信息安全的一种方式,越来越受到人们的关注。
下面,我将把自己对Microsoft CryptoAPI的一些肤浅的理解与大家共享,有什么不妥之处望不吝赐教。

一、 加密方法:

当初,计算机的研究就是为了破解德国人的密码,人们并没有想到计算机给今天带来的信息革命。随着计算机的发展,运算能力的增强,密码学已经取得了巨大的进展。大体来说有以下几种形式。
1、 公用密钥加密技术
加密和解密使用不同的密钥,分别叫做“公钥”和“私钥”。顾名思义,“私钥”就是不能让别人知道的,而“公钥”就是可以公开的。这两个必须配对使用,用公钥加密的数据必须用与其对应的私钥才能解开。这种技术安全性高,得到广泛运用,但是效率太低。
2、 对称密钥加密技术
要求加密和解密过程使用相同的密钥,这样,密钥必须只能被加解密双方知道,否则就不安全。这种技术安全性不高,但是效率高。
3、 结合公用和对称密钥加密技术
公钥加密技术以速度为代价换取了高安全性,而对称加密以低安全换取高性能,所以另一种常见的加密方法就是结合以上两种技术。
用对称加密算法对数据进行加密,然后使用更安全的但效率更低的公钥加密算法对对称密钥进行加密。
4、 数字签名和鉴别
就是对已经加密的数据“签名”,这样接收者可以知道加密的数据的来源,以及是否被更改。

二、 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类型 交换算法 签名算法 对称加密算法 Hash算法
PROV_RSA_FULL RSA RSA RC2
RC4
MD5
SHA
PROV_RSA_SIG none RSA none MD5
SHA
PROV_RSA_SCHANNEL RSA RSA RC4
DES
Triple DES
MD5
SHA
PROV_DSS DSS none DSS MD5
SHA
PROV_DSS_DH DH DSS CYLINK_MEK MD5
SHA
PROV_DH_SCHANNEL DH DSS DES
Triple DES
MD5
SHA
PROV_FORTEZZA KEA DSS Skipjack SHA
PROV_MS_EXCHANGE RSA RSA CAST MD5
PROV_SSL RSA RSA Varies Varies

从图一可以看到,每个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上进行。
可以如下删除密钥容器。
CryptAcquireContext(&hCryptProv, userName, NULL, PROV_RSA_FULL, CRYPT_DELETEKEYSET);

五、 一个文件加密的例子

看到这里肯定有人开始说了,“这么多废话,还不快讲怎么加密怎么解密!”您先别急,有些原理性的东西还是先了解了比较好,对以后的使用会有很大帮助。

言归正传,我们来看一段文件加密的代码。

#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,并作了修改。注释已经很详细了,这里就不赘述了,
解密与加密大同小异,大家可以自己看代码。

这次先写这么多,也许很多人觉得我写这些大家都知道,并且也太简单了。不要急慢慢来,嘿嘿:)接下来会有一些比较深入和实用的技术。

参考:
MSDN相关章节。
(注:如果代码编译不过,加入宏定义:_WIN32_WINNT=0x0400)

上次我们讲了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加密技术
作者:Cuick

下载本文示例源代码

在这个信息爆炸的时代,我们不得不对信息的安全提高警惕。加密作为保障数据信息安全的一种方式,越来越受到人们的关注。
下面,我将把自己对Microsoft CryptoAPI的一些肤浅的理解与大家共享,有什么不妥之处望不吝赐教。

一、 加密方法:

当初,计算机的研究就是为了破解德国人的密码,人们并没有想到计算机给今天带来的信息革命。随着计算机的发展,运算能力的增强,密码学已经取得了巨大的进展。大体来说有以下几种形式。
1、 公用密钥加密技术
加密和解密使用不同的密钥,分别叫做“公钥”和“私钥”。顾名思义,“私钥”就是不能让别人知道的,而“公钥”就是可以公开的。这两个必须配对使用,用公钥加密的数据必须用与其对应的私钥才能解开。这种技术安全性高,得到广泛运用,但是效率太低。
2、 对称密钥加密技术
要求加密和解密过程使用相同的密钥,这样,密钥必须只能被加解密双方知道,否则就不安全。这种技术安全性不高,但是效率高。
3、 结合公用和对称密钥加密技术
公钥加密技术以速度为代价换取了高安全性,而对称加密以低安全换取高性能,所以另一种常见的加密方法就是结合以上两种技术。
用对称加密算法对数据进行加密,然后使用更安全的但效率更低的公钥加密算法对对称密钥进行加密。
4、 数字签名和鉴别
就是对已经加密的数据“签名”,这样接收者可以知道加密的数据的来源,以及是否被更改。

二、 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类型 交换算法 签名算法 对称加密算法 Hash算法
PROV_RSA_FULL RSA RSA RC2
RC4
MD5
SHA
PROV_RSA_SIG none RSA none MD5
SHA
PROV_RSA_SCHANNEL RSA RSA RC4
DES
Triple DES
MD5
SHA
PROV_DSS DSS none DSS MD5
SHA
PROV_DSS_DH DH DSS CYLINK_MEK MD5
SHA
PROV_DH_SCHANNEL DH DSS DES
Triple DES
MD5
SHA
PROV_FORTEZZA KEA DSS Skipjack SHA
PROV_MS_EXCHANGE RSA RSA CAST MD5
PROV_SSL RSA RSA Varies Varies

从图一可以看到,每个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上进行。
可以如下删除密钥容器。
CryptAcquireContext(&hCryptProv, userName, NULL, PROV_RSA_FULL, CRYPT_DELETEKEYSET);

五、 一个文件加密的例子

看到这里肯定有人开始说了,“这么多废话,还不快讲怎么加密怎么解密!”您先别急,有些原理性的东西还是先了解了比较好,对以后的使用会有很大帮助。

言归正传,我们来看一段文件加密的代码。

#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,并作了修改。注释已经很详细了,这里就不赘述了,
解密与加密大同小异,大家可以自己看代码。

这次先写这么多,也许很多人觉得我写这些大家都知道,并且也太简单了。不要急慢慢来,嘿嘿:)接下来会有一些比较深入和实用的技术。

参考:
MSDN相关章节。
(注:如果代码编译不过,加入宏定义:_WIN32_WINNT=0x0400)

上次我们讲了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

最近在学习使用windows crypto API 对数据进行加密和解密,有些收获和经验,在此分享,希望对学习加密解密的朋友们有所帮助。
经过这次windows crypto API的学习过程,让我明白一个道理。其实,使用windows API编程并不难,首先要有信心。MSDN提供了详细的帮助和指南,许多问题,只要详细查阅MSND,完全可以不用在网上查找任何资料完成自己所需要的编程任务。
       网上的东西,许多都是来自别人的经验介绍,很多,但也很杂,为了搜索到真正有用的资料,往往需要花费很多的时间和精力。

       首先,大家不要觉得加密和解密是一件很深奥的事情,其实说白了,数据加密就是对原始数据进行一种变换而已,比如:你的密码是123456,我对每一位都加1,即234567,写到文件中,这就是加密,只要别人不知道你是对每一位都加1得到的234567,别人即使拿到你的密码文件,他也不知道怎样把你的密码从234567解密出来。(当然,这种简单的加密是可以很容易被破解的)

       数据加密主要分为两种,一种为对称加密,另一种为非对称加密。
       对称加密主要用于大量数据的加密,加密和解密都使用同一份密钥,密钥其实就是一串数据,使用这一串数据对你要加密的数据进行与啊、或啊或者异或什么的,于是就得到了一串人家看不懂的“密文”。

       非对称加密最经典的应用场合是客户端/服务器模式的系统,主要用于需要在网络中传送的少量数据进行加密(比如客户端的用户名、密码向服务器传送)。它使用两份密钥,公钥和私钥。
       公钥用于发给客户端,私钥留在服务器端。数据经过公钥加密后只能使用对应的私钥来解密。
       因此,客户端先对用户名密码信息使用公钥加密,然后向服务器端发送,即使中途被黑客截获,由于他没有私钥,故无法进行解密,当服务器端收到数据后,就使用自己的私钥进行解密。这样就可以实现所有的客户端都使用公钥进行加密,然后放心地发送数据到服务器端,因为只有服务器端才能够解密。(思考一下:这种场合如果使用对称密钥安全吗?)

       windows crypto API提供了对称加密和非对称加密,并且提供了各种加密、解密的算法,要使用相应的算法进行加密解密,只需要对生成密钥的函数的相关参数改变一下即可。
       为了学习使用windows crypto API,我沿袭以前的习惯,花费了大量的时间在网上搜索资料(许多资料用处不大),最终还是在MSND的指导下完全掌握了crypto API的用法,不过,MSND还是有其不足之处,它详细描述了crypto API相关的知识和信息,但没有从宏观上对实现用crypto API加密解密到底需要做哪些事情进行做出明确地指导,故在此,我用下面这幅流程图来告诉大家。

         我只从宏观上描述了整个加密解密需要进行的几个流程,具体每一步的代码怎么写,可以参考MSDN,或者本文后面链接的一份文档(这份文档讲得非常详细,也非常好,是我搜集的资料中最好的一份,希望对需要它的人有所帮助)
当然,在学习和使用windows crypto API的过程中可能会遇到很多的问题,如果有什么疑问,可以E—Mail给我。
附件下载:
  --CryptoAPI对数据进行加解密

文章二:使用Microsoft CryptoAPI进行加密、解密、签名及验证

本文转自:http://blog.csdn.net/mmpire/article/details/1640670

使用CryptoAPI编写一个文件保护程序,具有如下功能:
(1)给定明文文件,生成加密文件,同时产生文件的数字签名文件;
(2)给定密文文件,解密出明文文件,并验证签名的正确性。
代码:见所附main.cpp
一、     程序概况
a)     开发平台:Visual Studio 2005
b)     开发语言:C/C++
c)     使用密码库:CryptoAPI
二、     主要函数
a)     主函数
void main(void)
b)     加密文件
BOOL EncryptFile(PCHAR szSource, PCHAR szDestination, PCHAR szPassword);
c)     解密文件
BOOL DecryptFile(PCHAR szSource, PCHAR szDestination, PCHAR szPassword);
d)     签名文件
BOOL SignFile (PCHAR szSource, PCHAR szDestination);
e)     验证签名
BOOL VerifyFile (PCHAR szSource, PCHAR szDestination);
f)      错误处理
void HandleError(char *s);
三、     加密文件
a)     打开源文件
hSource = fopen(szSource,"rb")
b)     取得密钥容器(CSP)句柄
CryptAcquireContext(&hCryptProv,NULL,NULL,PROV_RSA_FULL,0)
c)     根据用户输入的密码创建一个会话密钥(即对称密钥,用于对原文件加密)
//创建一个Hash对象
CryptCreateHash(hCryptProv,CALG_MD5, 0, 0, &hHash)
//用用户输入的密码产生一个散列
CryptHashData(hHash, (BYTE *)szPassword, strlen(szPassword), 0)
//通过散列生成一个会话密钥
CryptDeriveKey(hCryptProv, ENCRYPT_ALGORITHM,hHash, KEYLENGTH, &hKey))
//销毁Hash对象
CryptDestroyHash(hHash);
注:会话密钥即对称密钥,用于对原文件进行加密;非对称密钥由于效率非常低,所以一般不用于对数据直接加密,而是对会话密钥进行加密,然后把它传送给对方。对方通过非对称密钥解密获得这个会话密钥,然后再对数据文件进行解密。可以看出,一个会话密钥的生存期可以限制在这次通信中,即每次通信都用不同的会话密钥加密,而非对称密钥则必须是长期使用的。在此例中,加解密过程中没有使用到非对称RSA密钥对,而只在数字签名及验证使用它。
d)     加密数据文件
CryptEncrypt(
hKey,               // 密钥
0,                    // 如果数据同时进行散列和加密,这里传入一个散列对象
feof(hSource), // 如果是最后一个被加密的块,输入 TRUE. 如果不是输
// 入 FALSE 这里通过判断是否到文件尾来决定是否为最后一块
0,                    // 保留
pbBuffer,          // 输入被加密数据,输出加密后的数据
&dwCount,       // 输入被加密数据实际长度,输出加密后数据长度
dwBufferLen)    //pbBuffer 的大小
注:查看完整代码时可以发现这是一个循环加密的过程,pbBuffer循环读入待加密文件的固定长度的内存块;当然你也可以将pbBuffer设得很大,一次读入整个文件,但那样浪费内存空间,而且影响扩展性(存在缓冲区溢出的可能)。
e)     清理工作,如释放Buffer空间、密钥句柄、CSP句柄等。
if(pbBuffer)
free(pbBuffer);
if(hKey)
CryptDestroyKey(hKey);
if(hHash)
CryptDestroyHash(hHash);
if(hCryptProv)
CryptReleaseContext(hCryptProv, 0);
四、     解密文件
a)     打开加密文件(同上)
b)     取得密钥容器(CSP)句柄(同上)
c)     根据用户输入的密码创建一个会话密钥(即对称密钥,用于对原文件解密)(同上)
注:这里要求用户输入的密码与加密时输入的密码相同。在实际应用中,这个所谓用户输入的“密码”其实只是一个产生密钥的种子,一旦产生完会话密钥,则用户完全可以忘记当初输入的“密码”,接收方可以使用传过来的密钥直接对加密文件进行解密,而不用再重复一次“生成密钥”的过程。
d)     解密数据文件
CryptDecrypt(
hKey,               // 密钥
0,                    // 如果数据同时进行散列和加密,这里传入一个散列对象
feof(hSource), // 如果是最后一个被加密的块,输入 TRUE. 如果不是输 .
// 入 FALSE 这里通过判断是否到文件尾来决定是否为最后一块。
0,                    // 保留
pbBuffer,          // 输入被加密数据,输出加密后的数据
&dwCount))           // 输入被加密数据实际长度,输出加密后数据长度
e)     清理工作,如释放Buffer空间、密钥句柄、CSP句柄等。(同上)
五、     签名文件
a)     打开源文件(同上)
b)     取得密钥容器(CSP)句柄(同上)
c)     取得签名用的密钥句柄(非对称RSA密钥)
CryptGetUserKey(
hCryptProv,               //  我们已经得到的 CSP 句柄
AT_SIGNATURE,            //  这里想得到 signature key pair
&hKey))                       //  返回密钥句柄
d)     导出签名用密钥对的公钥,保存在pbKeyBlob中
CryptExportKey(hKey, NULL,PUBLICKEYBLOB, 0, pbKeyBlob,&dwBlobLen)
e)     计算数据文件的Hash值,保存在Hash对象hHash中
//生成一个空的Hash对象
CryptCreateHash(hCryptProv,CALG_MD5,0,0,&hHash)
//计算数据文件的Hash值,保存在Hash对象中
CryptHashData(hHash,pbBuffer,dwCount,0)
f)      对数据文件的Hash值进行签名,数字签名保存在pbSignature中
CryptSignHash(hHash, AT_SIGNATURE, NULL, 0, pbSignature, &dwSigLen)
g)     清理工作,如释放Buffer空间、密钥句柄、CSP句柄等。(同上)
六、     验证签名
a)     打开文件(同上)
b)     取得密钥容器(CSP)句柄(同上)
c)     导入 pbKeyBlob 公钥
CryptImportKey(hCryptProv, pbKeyBlob, dwBlobLen, 0, 0, &hPubKey)
注:必须是与签名时所用的私钥配对的公钥,在此例中,这个公钥在生成数字签名时已经导出到pbKeyBlob中。
d)     计算数据文件的Hash值,保存在Hash对象hHash中。(同上)
e)     验证数字签名
CryptVerifySignature(hHash, pbSignature, dwSigLen,hPubKey,NULL, 0)
f)      清理工作,如释放Buffer空间、密钥句柄、CSP句柄等。(同上)
七、     实验结果
//  加密文件
Encrypt a file.
Enter the name of the file to be encrypted: 1.txt
Enter the name of the output file: 2.txt
Enter the password:123456
The source plaintext file, 1.txt, is open.
Destination file 2.txt is open.
A cryptographic provider has been acquired.
A hash object has been created.
The password has been added to the hash.
An encryption key is derived from the password hash.
Memory has been allocated for the buffer.
Encryption of the file 1.txt was a success.
The encrypted data is in file 2.txt.
// 解密文件
Decrypt a file.
Enter the name of the file to be decrypted: 2.txt
Enter the name of the output file: 3.txt
Enter the password:123456
The source plaintext file, 2.txt, is open.
Destination file 3.txt is open.
A cryptographic provider has been acquired.
A hash object has been created.
The password has been added to the hash.
An encryption key is derived from the password hash.
Memory has been allocated for the buffer.
Decryption of the file 2.txt was a success.
The decrypted data is in file 3.txt.
// 数字签名
Sign a file.
Enter the name of the file to be signed: 1.txt
Enter the name of the signature file: 11
The source plaintext file, 1.txt, is open.
Memory has been allocated for the buffer.
Destination file 11 is open.
A cryptographic provider has been acquired.
A signature key is available.
Size of the BLOB for the public key determined.
Memory has been allocated for the BLOB.
Contents have been written to the BLOB.
Hash object created.
The data buffer has been hashed.
Signature length 128 found.
Memory allocated for the signature.
pbSignature is the hash signature.
The hash object has been destroyed.
The signing phase of this program is completed.
Signature of the file 1.txt was a success.
The signature data is in file 11.
// 验证签名
Verify a file and its signature.
Enter the name of the file to be verified: 1.txt
Enter the name of the signature file: 11
The source plaintext file, 1.txt, is open.
Memory has been allocated for the buffer.
A cryptographic provider has been acquired.
The key has been imported.
The hash object has been recreated.
The new has been created.
The signature has been verified.
Verification of the file 1.txt was a success.
八、     参考资料
1)       Microsoft CryptoAPI加密技术
http://166.111.203.236:8080/reference/crypto/Microsoft_CryptoAPI_1.mht
2)       MSDN
http://msdn2.microsoft.com/en-us/library/aa380252.aspx
http://msdn2.microsoft.com/en-us/library/aa382371.aspx

九、  代码:

http://blog.csdn.net/mmpire/article/details/1640670

Microsoft CryptoAPI加密技术相关推荐

  1. Microsoft CryptoAPI加密技术(一)

    http://www.vckbase.com/index.php/wv/716.html 在这个信息爆炸的时代,我们不得不对信息的安全提高警惕.加密作为保障数据信息安全的一种方式,越来越受到人们的关注 ...

  2. Microsoft CryptoAPI加密技术(二)

    原文:http://www.vckbase.com/index.php/wv/717.html 上次我们讲了Microsoft CryptoAPI的构成以及会话密钥的使用.接下来我们将看一下公私密钥对 ...

  3. 软件加密技术及实现(转载)

    标题   软件加密技术及实现     选择自 whinah 的 Blog 关键字   encrypt 软件加密 保护 散列 数字签名 出处   软件加密技术及实现 雷 鹏 ( 桂林电子工业学院  计算 ...

  4. 【安全加密技术】 对称加密

    转载请注明出处:http://blog.csdn.net/sk719887916/article/details/46822663 上篇了解了<非对称加密>后 今天我来继续了解下加密技术中 ...

  5. 苹果大战FBI将加速科技圈的加密技术发展?

    苹果和 FBI 最终还是没有因为一部手机而走上法庭,但在此之前双方已经经历了多种形式的明争暗斗.回顾本次事件,我们可以概括为:FBI 想要让苹果解锁一部恐怖分子的 iPhone,但是遭到了拒绝.然后 ...

  6. 快速配置Windows 2003平台下实现 IIS(WEB)站点的安全(SSL加密技术!)

    [实验名称] 快速配置Windows 2003平台下实现 IIS(WEB)站点的安全(SSL加密技术!) [实验基本概念] A. 对于公用信息--------------------www.Sohu. ...

  7. 2020-10-23(SMC加密技术)

    第一篇博客,写得不好还请多多谅解. 今日收获: 今天接触了SMC加密技术,代码一般般,也就自我解密代码这块我这个初学者难以想到. 代码奉上,静态没法查看 judge里面的东西,需要jdb或者ida动态 ...

  8. *** 隧道和加密技术知识要点

    ×××分为二层隧道协议和三层隧道协议     一. 第二层隧道协议有PPTP L2F L2TP 主要用作远程访问×××的隧道协议 第二层隧道协议建立在点对点的协议PPP的基础上的,充分利用了PPP支持 ...

  9. [数据加密]GIS空间数据水印信息隐藏与加密技术方法[转]

    到目前为止,国内外数字水印技术的研究主要集中在图像.视频和声音等多媒体信息的版权保护上,在GIS空间数据中,通过隐藏水印信息并对其加密.压缩以实现其安全保护的研究还很少,这是数字水印技术应用的一个新领 ...

最新文章

  1. python基础 继承
  2. iOS-生成国际化包-配置App多语言支持
  3. 医疗影像网络PACS系统方案
  4. c++读取txt中每行的数据到数组中
  5. Java高阶部分知识点汇总(三)-内存空间的引用
  6. Mac下Ruby升级与Rails的安装
  7. 线程、进程、多线程、多进程和多任务有啥关系?
  8. 5G的场景、需求、通信速率
  9. Android -- 发送Broadcast、有序无序
  10. 宁夏公安打传销端窝点为春节保平安
  11. 【Flink】Flink各种UDF简介
  12. matlab中取文件名函数,matlab从文件夹名中获取该文件夹下所图像文件名
  13. zz详细讲解Quartz.NET
  14. oracle11g 时间失效,关于oracle11g RAC 的CTSS与ntp时间同步的疑问
  15. 快速构建Windows 8风格应用25-数据绑定
  16. 银行自助填表方案介绍
  17. 在MAME里如何设置组合键
  18. 致远OA自定义函数--正则表达式匹配校验
  19. 央行超级网银8月上线;Win7市场份额突破10%(每日关注2010.4.2)
  20. Linux下useradd命令与adduser命令的区别(adduser更适合初级使用者,useradd比较适合有些高阶经验的使用者)

热门文章

  1. html中定义动画名字和规则,@keyframes
  2. 除法:传统除法、floor和真除法
  3. 闰年 ( The Leap Year) 怎么判断
  4. 含泪整理最优质效果图后期素材素材,你想要的这里都有
  5. 计算机制图英语试题,2017年cad期末考试训练试题「附答案」
  6. 【自考】网络经济与企业管理之初识总结
  7. HTML实现视频直播功能
  8. “任性”华为 | 七十八岁老人的“四渡赤水”(二)
  9. BZOJ2738: 矩阵乘法(整体二分)
  10. Bootstrap笔记(十四) 常用类别 - 间距