用CryptoAPI进行数据加密

CryptoAPI进行数据加密
南京理工大学计算机系
胡静李蔚清
----因为过于复杂的加密算法实现起来非常困难,所以在过去,许多应用程序只能使用非常简单的加密技术,这样做的结果就是加密的数据很容易就可以被人破译。而使用Microsoft提供的加密应用程序接口(即Cryptography API,或称CryptoAPI,就可以方便地在应用程序中加入强大的加密功能,而不必考虑基本的算法。本文将对CryptoAPI及其使用的数据加密原理作一简单的介绍,然后给出了用CryptoAPI编写加密程序的大致步骤,最后以一个文件的加密、解密程序为例演示了CryptoAPI的部分功能。
---- 1. CryptoAPI简介
---- CryptoAPI是一组函数,为了完成数学计算,必须具有密码服务提供者模块(CSP)。Microsoft通过捆绑RSA Base Provider在操作系统级提供一个CSP,使用RSA公司的公钥加密算法,更多的CSP可以根据需要增加到应用中。事实上,CSP有可能与特殊硬件设备(如智能卡)一起来进行数据加密。CryptoAPI接口允许简单的函数调用来加密数据,交换公钥,散列一个消息来建立摘要以及生成数字签名。它还提供高级的管理操作,如从一组可能的CSP中使用一个CSP。此外,CryptoAPI还为许多高级安全性服务提供了基础,包括用于电子商务的SET,用于加密客户机/服务器消息的PCT,用于在各个平台之间来回传递机密数据和密钥的PFX,代码签名等等。CryptoAPI的体系结构如下图:
----目前支持CryptoAPIWindows系统有:Windows 95 OSR2Windows NT SP3及后续版本、Windows 98Windows 2000等。CryptoAPI的配置信息存储在注册表中,包括如下密钥:
HKEY_LOCAL_MACHINE\SOFTWARE\
Microsoft / Cryptography /Defaults
HKEY_CURRENT_USER/ Software / Microsoft
 / Cryptography /Providers
---- 2.数据加密原理
----数据加密的流程如下图:
---- CryptoAPI使用两种密钥:会话密钥与公共/私人密钥对。会话密钥使用相同的加密和解密密钥,这种算法较快,但必须保证密钥的安全传递。公共/私人密钥对使用一个公共密钥和一个私人密钥,私人密钥只有专人才能使用,公共密钥可以广泛传播。如果密钥对中的一个用于加密,另一个一定用于解密。公共/私人密钥对算法很慢,一般只用于加密小批数据,例如用于加密会话密钥。
---- CryptoAPI支持两种基本的编码方法:流式编码和块编码。流式编码在明码文本的每一位上创建编码位,速度较快,但安全性较低。块编码在一个完整的块上(一般为64位)上工作,需要使用填充的方法对要编码的数据进行舍入,以组成多个完整的块。这种算法速度较慢,但更安全。
---- 3.应用举例
----下面以两个文件加密与解密的C程序片断为例,演示一下CryptoAPI的强大功能。这两个程序均为Win32控制台应用,程序省略了出错处理,实际运行时请加入。
----文件加密
#include < windows.h >
#include < stdio.h >
#include < stdlib.h >
#include < wincrypt.h >
 
//确定使用RC2块编码或是RC4流式编码
#ifdef USE_BLOCK_CIPHER
#define ENCRYPT_ALGORITHMCALG_RC2
#define ENCRYPT_BLOCK_SIZE8
#else
   #define ENCRYPT_ALGORITHMCALG_RC4
   #define ENCRYPT_BLOCK_SIZE1
#endif
 
void CAPIDecryptFile(PCHAR szSource,
PCHAR szDestination, PCHAR szPassword);
 
void _cdecl main(int argc, char *argv[])
{
PCHAR szSource= NULL;
PCHAR szDestination = NULL;
PCHAR szPassword= NULL;
 
// 验证参数个数
if(argc != 3 && argc != 4) {
printf("USAGE: decrypt < source file >
 < dest file > [ < password > ]/n");
exit(1);
}
 
//读取参数.
szSource   = argv[1];
szDestination = argv[2];
if(argc == 4) {
szPassword = argv[3];
}
CAPIDecryptFile(szSource, szDestination, szPassword);
}
 
/*szSource为要加密的文件名称,szDestination
为加密过的文件名称,szPassword为加密口令*/
void CAPIEncryptFile(PCHAR szSource, PCHAR
szDestination, PCHAR szPassword)
{
FILE *hSource = NULL;
FILE *hDestination = NULL;
INT eof = 0;
HCRYPTPROV hProv   = 0;
HCRYPTKEY hKey = 0;
HCRYPTKEY hXchgKey = 0;
HCRYPTHASH hHash   = 0;
PBYTE pbKeyBlob = NULL;
DWORD dwKeyBlobLen;
PBYTE pbBuffer = NULL;
DWORD dwBlockLen;
DWORD dwBufferLen;
DWORD dwCount;
 
hSource = fopen(szSource,"rb"));// 打开源文件.
hDestination = fopen(szDestination,"wb") ;
//.打开目标文件
   // 连接缺省的CSP
CryptAcquireContext(&hProv, NULL, NULL,
PROV_RSA_FULL, 0))
if(szPassword == NULL)
//口令为空,使用随机产生的会话密钥加密
// 产生随机会话密钥.
CryptGenKey(hProv, ENCRYPT_ALGORITHM,
CRYPT_EXPORTABLE, &hKey)
// 取得密钥交换对的公共密钥
CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hXchgKey)
// 计算隐码长度并分配缓冲区
CryptExportKey(hKey, hXchgKey, SIMPLEBLOB, 0,
NULL, &dwKeyBlobLen)
pbKeyBlob = malloc(dwKeyBlobLen)) == NULL) ;
// 将会话密钥输出至隐码
CryptExportKey(hKey, hXchgKey, SIMPLEBLOB,
0, pbKeyBlob, &dwKeyBlobLen))
// 释放密钥交换对的句柄
CryptDestroyKey(hXchgKey);
hXchgKey = 0;
// 将隐码长度写入目标文件
fwrite(&dwKeyBlobLen, sizeof(DWORD), 1, hDestination);
//将隐码长度写入目标文件
fwrite(pbKeyBlob, 1, dwKeyBlobLen, hDestination);
} else {
//口令不为空, 使用从口令派生出的密钥加密文件
CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)
// 建立散列表
CryptHashData(hHash, szPassword, strlen(szPassword), 0)
 //散列口令
// 从散列表中派生密钥
CryptDeriveKey(hProv, ENCRYPT_ALGORITHM, hHash, 0, &hKey)
// 删除散列表
CryptDestroyHash(hHash);
hHash = 0;
 }
 
 //计算一次加密的数据字节数,必须为ENCRYPT_BLOCK_SIZE的整数倍
dwBlockLen = 1000 - 1000 % ENCRYPT_BLOCK_SIZE;
//如果使用块编码,则需要额外空间
if(ENCRYPT_BLOCK_SIZE > 1) {
dwBufferLen = dwBlockLen + ENCRYPT_BLOCK_SIZE;
} else {
dwBufferLen = dwBlockLen;
}
//分配缓冲区
pbBuffer = malloc(dwBufferLen)
//加密源文件并写入目标文件
do {
// 从源文件中读出dwBlockLen个字节
dwCount = fread(pbBuffer, 1, dwBlockLen, hSource);
eof = feof(hSource);
//加密数据
CryptEncrypt(hKey, 0, eof, 0, pbBuffer,
&dwCount, dwBufferLen)
// 将加密过的数据写入目标文件
fwrite(pbBuffer, 1, dwCount, hDestination);
} while(!feof(hSource));
printf("OK/n");
……//关闭文件、释放内存
   }
 
文件解密
 
void CAPIDecryptFile(PCHAR szSource, PCHAR
szDestination, PCHAR szPassword)
{
……//变量声明、文件操作同文件加密程序
 
CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0);
 if(szPassword == NULL) {
// 口令为空,使用存储在加密文件中的会话密钥解密
// 读隐码的长度并分配内存
fread(&dwKeyBlobLen, sizeof(DWORD), 1, hSource);
pbKeyBlob = malloc(dwKeyBlobLen)) == NULL)
// 从源文件中读隐码.
fread(pbKeyBlob, 1, dwKeyBlobLen, hSource);
// 将隐码输入CSP
CryptImportKey(hProv, pbKeyBlob,
dwKeyBlobLen, 0, 0, &hKey)
} else {
// 口令不为空, 使用从口令派生出的密钥解密文件
CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)
CryptHashData(hHash, szPassword, strlen(szPassword), 0)
CryptDeriveKey(hProv, ENCRYPT_ALGORITHM,
hHash, 0, &hKey)
CryptDestroyHash(hHash);
hHash = 0;
}
 
dwBlockLen = 1000 - 1000 % ENCRYPT_BLOCK_SIZE;
if(ENCRYPT_BLOCK_SIZE > 1) {
dwBufferLen = dwBlockLen + ENCRYPT_BLOCK_SIZE;
} else {
dwBufferLen = dwBlockLen;
}
pbBuffer = malloc(dwBufferLen)
 
//解密源文件并写入目标文件
do {
dwCount = fread(pbBuffer, 1, dwBlockLen, hSource);
eof = feof(hSource);
// 解密数据
CryptDecrypt(hKey, 0, eof, 0, pbBuffer, &dwCount)
// 将解密过的数据写入目标文件
fwrite(pbBuffer, 1, dwCount, hDestination);
} while(!feof(hSource));
printf("OK/n");
……//关闭文件、释放内存
}
----以上代码在Windows NT4.0Visual C++6.0环境中编译通过。
----除直接用于加密数据外,CryptoAPI还广泛用于产生并确认数字签名,这里就不一一举例说明了,有兴趣的读者可以参考MSDN文档。

CryptoAPI进行数据加密》原文的作者忽略了一个重要的问题。如果你的机器曾运行过这种数据加密程序,那当然可以正确地运行。如果是第一次才运行此程序,那将会出现问题,错误就在 CryptAcquireContext的调用上。
  在调用CryptEncrypt进行数据加密之前,需要取得当前机器缺省的密钥容器,而CryptAcquireContext(hProv,NULL,NULL,PROV_RSA_FULL,0)就是用于连接缺省的CSP的,如果当前机器未曾设置过缺省的密钥容器,CryptAcquireContext 调用就会出错。因此必须为机器创建缺省的密钥容器。
  创建缺省的密钥容器也需要调用CryptoAPI进行。下面介绍一种创建缺省的密钥容器的控制台程序InitUser.c。本程序的功能为:用你的机器名称UserName创建一个缺省的密钥容器。在密钥容器中创建一个数字签名密钥对。在密钥容器中创建一个密钥交换对。
  本程序在一台机器上只须执行一次,执行之后,你将会看到在系统注册表的HKEY_CURRENT_USER/Software/microsoft下多了一个主键: Cryptography/UserKeys,并填入一些十六进制的值。之后,你就可以顺利地使用数据加密程序了,甚至可以使用数字签名了。
  程序清单:
#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>
#define _WIN32_WINNT 0x0400
#include <wincrypt.h>
int main(int argc, char* argv[])
{
 //
主程序不需要任何参数
 HCRYPTPROV hProv;
 HCRYPTKEY hKey;
 unsigned char szUserName[100];
 DWORD dwUserNameLen = 100;
 //
试图获取缺省的密钥容器,若失败,则创建一个缺省容器
 if(!CryptAcquireContext(&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL, 0))
 {
  //
若获取缺省密钥容器发生错误,就创建一个缺省密钥容器, 使用参数 CRYPT_NEWKEYSET
  if(!CryptAcquireContext (&hProv,NULL,MS_DEF_PROV,PROV_RSA_FULL,CRYPT_NEWKEYSET))
  {
   printf("
创建缺省密钥容器发生错误!/n");
   exit(1);
  }
  //
取得缺省密钥容器名
  if(!CryptGetProvParam(hProv, PP_CONTAINER, szUserName, &dwUserNameLen, 0))
  {
   //
出错误时容器名置空
   szUserName[0] = 0;
  }
  printf("Create key container '%s'/n",szUserName);
 }
 //
试图获取签名密钥的名柄
 if(!CryptGetUserKey(hProv, AT_SIGNATURE, &hKey))
 {
  if(GetLastError() == NTE_NO_KEY)
  {
   //
创建数字签名密钥对
   printf("Create signature key pair/n");
   if(!CryptGenKey(hProv,AT_SIGNATURE,0,&hKey))
   {
    printf("
错误代码:%x 创建数字签名密钥对发生错误!/n", GetLastError());
    exit(1);
   }
   else
   {
    CryptDestroyKey(hKey);
   }
  }
  else
  {
   printf("
错误代码: %x during CryptGetUserKey!/n", GetLastError());
   exit(1);
  }
 }
 //
试图取得交换密钥的句柄,没有交换密钥时,创建交换密钥
 if(!CryptGetUserKey(hProv,AT_KEYEXCHANGE,&hKey))
 {
  if(GetLastError()==NTE_NO_KEY)
  {
   //
没有交换密钥时,创建交换密钥对
   printf("
创建交换密钥/n");
    if(!CryptGenKey(hProv,AT_KEYEXCHANGE,0,&hKey))
    {
     printf("Error %x during CryptGenKey!/n", GetLastError());
     exit(1);
     }
    else
    {
     CryptDestroyKey(hKey);
     }
  }
  else
  {
   printf("Error %x duringCryptGetUserKey!/n", GetLastError());
   exit(1);
  }
 }
 CryptReleaseContext(hProv,0);
 //
释放句柄
 printf("OK/n");
// exit(0);
 return 0;
 }
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值