David.Turing's blog

 

CryptoAPI第一天

[转]http://www.chinaitpower.com/A200507/2005-07-27/175804.html

一:准备工作
一般必须包含如下头
文件 以及定义
#include
#include
#include
#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)
 当包含wincrypt.h头
文件 时,一般都需要定义#define _WIN32_WINNT 0x(具体的值),否则将得到如下错误:error C2065: undeclared identifier,不同的操作 系统 不同定义如下:
Windows Server 2003 family _WIN32_WINNT>=0x0502
WINVER>=0x0502
Windows XP _WIN32_WINNT>=0x0501
WINVER>=0x0501
Windows 2000 _WIN32_WINNT>=0x0500
WINVER>=0x0500
Windows NT 4.0 _WIN32_WINNT>=0x0400
WINVER>=0x0400
Windows Me _WIN32_WINDOWS=0x0500
WINVER>=0x0500
Windows 98 _WIN32_WINDOWS>=0x0410
WINVER>=0x0410
Windows 95 _WIN32_WINDOWS>=0x0400
WINVER>=0x0400
Internet Explorer 6.0 _WIN32_IE>=0x0600
Internet Explorer 5.6 _WIN32_IE>=0x0560
Internet Explorer 5.01, 5.5 _WIN32_IE>=0x0501
Internet Explorer 5.0, 5.0a, 5.0b _WIN32_IE>=0x0500
Internet Explorer 4.01 _WIN32_IE>=0x0401
Internet Explorer 4.0 _WIN32_IE>=0x0400
Internet Explorer 3.0, 3.01, 3.02 _WIN32_IE>=0x0300

二:了解基本知识
CryptoAPI的配置信息存储在注册表中,包括如下密钥:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft \ Cryptography \Defaults
HKEY_CURRENT_USER\ Software \ Microsoft\ Cryptography \Providers
---- CryptoAPI使用两种密钥:会话密钥与公共/私人密钥对。会话密钥使用相同的加密和解密密钥,这种算法较快,但必须保证密钥的安全传递。公共/私人密钥对使用一个公共密钥和一个私人密钥,私人密钥只有专人才能使用,公共密钥可以广泛传播。如果密钥对中的一个用于加密,另一个一定用于解密。公共/私人密钥对算法很慢,一般只用于加密小批数据,例如用于加密会话密钥。CryptoAPI支持两种基本的编码方法:流式编码和块编码。流式编码在明码文本的每一位上创建编码位,速度较快,但安全性较低。块编码在一个完整的块上(一般为64位)上工作,需要使用填充的方法对要编码的数据进行舍入,以组成多个完整的块。这种算法速度较慢,但更安全。

三:下面进入具体的编程 
一: Creating a Key Container and Generating Keys
  创建一个密钥容器,在进行加密,解密
文件 ,并且签名的时候,必须需要一个公/私钥对,下面我们就来创建默认的密钥容器,要注意的是创建密钥容器并不会自动产生公/私钥对.
  下面是我们
程序 的任务:
  1,假如密钥容器不存在则创建一个。
  2,假如签名密钥不存在则在密钥容器里创建一个。
  3,假如交换密钥不存在则在密钥容器里创建一个。
  4,获取CSP中的一些参数
  下面是具体的步骤:
  1,连接缺省的CSP
BOOL WINAPI CryptAcquireContext(
  HCRYPTPROV* phProv,   //out
  LPCTSTR pszContainer, //in
  LPCTSTR pszProvider,  //in
  DWORD dwProvType,     //in
  DWORD dwFlags         //in
);
第一个参数是返回的CSP句柄,第二个是密钥容器的名字,第三个是A null-terminated string that specifies the name of the CSP to be used.第四个是指定提供的类型。例如:CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0));
如果当前机器的未曾设置过缺省的密钥容器,因此必须为机器创建缺省的密钥容器。
CryptAcquireContext( &hCryptProv, UserName, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)大家有没有看到,只是最后一个参数不同而已,多了一个CRYPT_NEWKEYSET而已。
  2,取得CSP的参数
BOOL WINAPI CryptGetProvParam(
  HCRYPTPROV hProv,
  DWORD dwParam,
  BYTE* pbData,
  DWORD* pdwDataLen,
  DWORD dwFlags
);
第一个参数是CSP的句柄,第二个参数是需要取得的具体参数对象(类型比较多,具体请看MSDN)。
例子:CryptGetProvParam(hCryptProv, PP_CONTAINER, (BYTE *)szUserName, &dwUserNameLen, 0)
  3,函数返回所获取密钥类型的句柄(0表失败,非0表成功)
BOOL WINAPI CryptGetUserKey(
  HCRYPTPROV hProv,
  DWORD dwKeySpec,
  HCRYPTKEY* phUserKey
);
  参数比较简单,只谈谈第二次参数,它可以是AT_KEYEXCHANGE(交换密钥) or AT_SIGNATURE(签名密钥),例如:
CryptGetUserKey(hCryptProv,AT_KEYEXCHANGE,&hKey)
  4,产生一个随机的交换密钥或者公/私钥对
BOOL WINAPI CryptGenKey(
  HCRYPTPROV hProv,
  ALG_ID Algid,
  DWORD dwFlags,
  HCRYPTKEY* phKey
);
ALG_ID 表明产生私钥所使用的算法。有如下参数:
微软 提供的基本算法
CALG_MD2,CALG_MD5,CALG_SHA,CALG_SHA1,CALG_MAC,CALG_HMAC,CALG_SSL3_SHAMD5,CALG_MD2,CALG_MD2
CALG_RSA_SIGN,CALG_RSA_KEYX,CALG_RC2,CALG_RC4,CALG_DES
微软 提供的增强型算法:
CALG_MD2,CALG_MD5,CALG_SHA,CALG_SHA1
CALG_MAC,CALG_HMAC ,CALG_SSL3_SHAMD5,CALG_RSA_SIGN,CALG_RSA_KEYX,CALG_RC2,CALG_RC4,CALG_DES,CALG_3DES_112,CALG_3DES
使用DH的CSP有如下两个参数,CALG_DH_EPHEM,CALG_DH_SF
使用公开密钥算法:AT_KEYEXCHANGE,AT_SIGNATURE
dwFlags,,表示密钥使用的长度,参数可以为0,采用默认的密钥长度。或者是进行如下几个参数的或:
CRYPT_ARCHIVABLE:表示在句柄在关闭之前都能够被导出
CRYPT_CREATE_SALT:表示密钥按照一个salt value来随机产生。
CRYPT_EXPORTABLE:表示密钥可以从CSP中导出到BLOB,因为会话密钥产生是必须可导出的,所以必须设置
CRYPT_NO_SALT:表示没有SALT VALUE获取allocated for a forty-bit symmetric key
CRYPT_PREGEN:表示在DH或者DSS密钥产生必须有个初始化。
例如:CryptGenKey(hCryptProv,AT_KEYEXCHANGE,0,&hKey)
  5,释放CSP句柄
BOOL WINAPI CryptReleaseContext(
  HCRYPTPROV hProv,
  DWORD dwFlags //保留字,现在必须为0
);
  6,为CSP增加一个reference count(用来跟踪COM对象的整数值,当对象创建,值为1。每次对对象的操作都将增加,而对对象的关闭将减少,当值为0是,对象释放,所以与对象相关操作将无效)
BOOL WINAPI CryptContextAddRef(
  HCRYPTPROV hProv,
  DWORD* pdwReserved,  //保留字,必须为NULL
  DWORD dwFlags        //保留字,必须为0
);

  二:Deriving a Session Key from a Password
  1,连接CSP
  2,使用CryptCreateHash产生一个空的HASH对象
  3,对密码进行HASH处理
  4,释放HASH以及密码对象
  5,释放CSP
  下面是具体的步骤:
  1,CryptCreateHash初始化一个HASH对象
BOOL WINAPI CryptCreateHash(
  HCRYPTPROV hProv,  //in
  ALG_ID Algid,      //in
  HCRYPTKEY hKey,    //in
  DWORD dwFlags,     //in保留字,必须为0
  HCRYPTHASH* phHash //out
);
第二个参数是指定HASH算法,有CALG_HMAC,CALG_MAC,CALG_MD2,CALG_MD5,CALG_SHA,CALG_SHA1,CALG_SSL3_SHAMD5。第三个参数对于那些keyed hash,例如HMAC,MAC算法。但是nonkeyed算法,必须设置为0。
  2,CryptHashData对数据使用HASH
BOOL WINAPI CryptHashData(
  HCRYPTHASH hHash,  //in,HASH对象句柄
  BYTE* pbData,      //in,待HASH的数据
  DWORD dwDataLen,   //in,待HASH数据的长度,当dwFlags为CRYPT_USERDATA为0时,必须为0
  DWORD dwFlags      //in,一般为0,或者为CRYPT_USERDATA(用在用户进入
系统 时需要输入PIN)
);
  3,CryptDeriveKey从某一数据产生会话密钥,他有点类似CryptGenKey,但是他产生的会话密钥来自固定数据,而CryptGenKey是随机产生的。并且不能产生公/私钥对
BOOL WINAPI CryptDeriveKey(
  HCRYPTPROV hProv,      //in,CSP句柄
  ALG_ID Algid,          //in,指定的算法,类似CryptGenKey
  HCRYPTHASH hBaseData,  //in,HASH对象的句柄
  DWORD dwFlags,         //in,指定产生密钥的类型
  HCRYPTKEY* phKey       //in,out产生的密钥句柄地址
);
例如:CryptDeriveKey(hCryptProv, CALG_RC2, hHash, CRYPT_EXPORTABLE, &hKey)
  4,CryptDestroyHash(hHash);
  5, CryptDestroyKey(hKey);
  6, 在这里发现一个不错的函数,就是那种提示输入密码的命令行(屏幕只会出现***)
  void GetConsoleInput(char* strInput, int intMaxChars)
{
 char ch;
 char minChar = ' ';
 minChar++;
 ch = getch();
 while (ch != '\r')
 {
  if (ch == '\b' && strlen(strInput) > 0)
  {
   strInput[strlen(strInput)-1]   = '\0';
   printf("\b \b");
  }
  else if (ch >= minChar && strlen(strInput) < intMaxChars)
  {
   strInput[strlen(strInput)+1] = '\0';
   strInput[strlen(strInput)]   = ch;
   putch('*');
  }
  ch = getch();
 }
 putch('\n');
}

  三:Duplicating,setting and getting Session key
  1,连接CSP
  2,使用CryptGenKey产生一个会话密钥
  3,CryptDuplicateKey复制会话密钥
  4,CryptSetKeyParam改变密钥产生的过程
  5,CryptGenRandom产生随机数
具体过程。
1,CryptDuplicateKe复制会话密钥
BOOL WINAPI CryptDuplicateKey(
  HCRYPTKEY hKey,      //in 会话密钥句柄
  DWORD* pdwReserved,  //in 保留字,必须为NULL
  DWORD dwFlags,       //in 保留字,必须为0
  HCRYPTKEY* phKey     //out 新的会话密钥
);
2,CryptSetKeyParam定制会话密钥的参数
BOOL WINAPI CryptSetKeyParam(
  HCRYPTKEY hKey,
  DWORD dwParam,  //in 很多,具体请看MSDN
  BYTE* pbData,
  DWORD dwFlags  //in  只有在dwParam=KP_ALGID才被使用
);
例如CryptSetKeyParam(hOriginalKey, KP_MODE, (BYTE*)&dwMode, 0)(dwMode = CRYPT_MODE_ECB)
3,CryptGenRandom为空间产生随机字节
BOOL WINAPI CryptGenRandom(
  HCRYPTPROV hProv,
  DWORD dwLen,    //需要产生的随机比特数
  BYTE* pbBuffer  //需要返回数据的空间,这个pbBuffer可以等于CryptSetKeyParam的pbData
);
例如:CryptGenRandom(hCryptProv, 8, pbData)
4,CryptGetKeyParam获取密钥的一些参数
BOOL WINAPI CryptGetKeyParam(
  HCRYPTKEY hKey,
  DWORD dwParam,     //in,参数众多
  BYTE* pbData,      //out,获取BYTE数据的指针
  DWORD* pdwDataLen, //out,获取BYTE数据的长度
  DWORD dwFlags      //关键字,必须为0
);
例如:CryptGetKeyParam(hKey, KP_IV, pbData, &dwCount, 0)


  四:Exporting a Session Key
  1,连接CSP
  2,CryptGetUserKey获取公/私钥对和交换密钥,公私钥用来签名,而交换密钥用来导出会话密钥
  3,CryptGenKey产生会话密钥
  4,CryptExportKey创建简单包含有会话密钥的key BLOB
  5,释放处理:
  具体过程:
1,CryptExportKey函数导出密钥
BOOL WINAPI CryptExportKey(
  HCRYPTKEY hKey,     //需要导出的密钥句柄
  HCRYPTKEY hExpKey,  //将待导出密钥用交换密钥进行加密,假如是公开的BLOG当然就设置为0
  DWORD dwBlobType,   //指定导出的密钥BLOB类型。六个参数见MSDN
  DWORD dwFlags,      //CRYPT_DESTROYKEY,CRYPT_SSL2_FALLBACK,CRYPT_OAEP
  BYTE* pbData,       //导出的数据指针,以后就可以将这个数据写如磁盘或者别的任务。
  DWORD* pdwDataLen   //导出的数据长度
);
例如:CryptExportKey(hKey, hXchgKey, SIMPLEBLOB, 0, pbKeyBlob, &dwBlobLen)
2,CryptImportKey将密钥从BLOB转换到CSP中
BOOL WINAPI CryptImportKey(
  HCRYPTPROV hProv,  //CSP句柄
  BYTE* pbData,      //待转换的BLOB数据
  DWORD dwDataLen,   //待转换的数据长度
  HCRYPTKEY hPubKey, //对BLOB解密的公钥,譬如上面是用交换密钥密钥加密的,就用交换密钥解密
  DWORD dwFlags,     //目前还只应用在当一对公/私钥从PRIVATEKEYBLOB中加入CSP中这种情况。
  HCRYPTKEY* phKey   //out导入的密钥
);
例如:CryptImportKey(hProv,pbKeyBlob,dwBlobLen,0,0,&hPubKey)


  五:Encoding and Decoding Messages
  编码的处理过程
  1,将待编码的数据转化为合适的格式,使用
  2,调用CryptMsgOpenToEncode,passing the necessary argument;
  3, 调用CryptMsgUpdate函数多次,最后一次调用时,将final参数设置为true
  4, 调用CryptMsgGetParam来获取一个需要得到的参数。
  5, 调用CryptMsgClose来关闭消息
  解码的处理过程
  1,检查申请的放编码后数据的空间,利用函数CryptMsgCalculateEncodedLength.
  2,调用函数CryptMsgOpenToDecode,passing the necessary argument;
  3,调用CryptMsgUpdate一次,这将导致合适的动作去处理信息,以来于信息的格式
  4,一些额外的处理,例如额外的解密或者是验证,调用CryptMsgControl,
  5,调用CryptMsgGetParam来获取需要得到的参数
  6,调用CryptMsgClose来关闭消息
  具体的函数介绍:
  1,CryptMsgCalculateEncodedLength计算所需要的存储编码的最大空间值
DWORD WINAPI CryptMsgCalculateEncodedLength(
  DWORD dwMsgEncodingType,//指定编码类型。一般为X509_ASN_ENCODING|PKCS_7_ASN_ENCODING
  DWORD dwFlags,
  DWORD dwMsgType,
  const void* pvMsgEncodeInfo, //in 指向待编码的数据,数据类型依赖于dwMsgType
  LPSTR pszInnerContentObjID,
  DWORD cbData                 //in 比特数的容量
);
第二个参数:CMSG_BARE_CONTENT_FLAG,CMSG_DETACHED_FLAG,CMSG_CONTENTS_OCTETS_FLAG,CMSG_CMS_ENCAPSULATED_CONTENT_FLAG
第三个参数:CMSG_DATA,CMSG_SIGNED,CMSG_ENVELOPED,CMSG_SIGNED_AND_ENVELOPED,CMSG_HASHED,CMSG_ENCRYPTED
第五个参数:szOID_RSA_data,szOID_RSA_signedData,szOID_RSA_envelopedData,szOID_RSA_signEnvData,szOID_RSA,digestedData ,
szOID_RSA_encryptedData,SPC_INDIRECT_DATA_OBJID,NULL
返回值:返回需要的一个加密信息所需要的长度
  2,CryptMsgOpenToEncode打开一个消息以便进行编码,返回打开消息的句柄
  HCRYPTMSG WINAPI CryptMsgOpenToEncode(
  DWORD dwMsgEncodingType,      //指定编码类型。一般为X509_ASN_ENCODING|PKCS_7_ASN_ENCODING
  DWORD dwFlags,               
  DWORD dwMsgType,             
  const void* pvMsgEncodeInfo,
  LPSTR pszInnerContentObjID,    //和CryptMsgCalculateEncodedLength一样
  PCMSG_STREAM_INFO pStreamInfo //当流没被使用时,该参数为NULL
);
第二个参数:CMSG_BARE_CONTENT_FLAG,CMSG_DETACHED_FLAG,CMSG_CONTENTS_OCTETS_FLAG,CMSG_CMS_ENCAPSULATED_CONTENT_FLAG,
CMSG_CRYPT_RELEASE_CONTEXT_FLAG
第三个参数:
CMSG_DATA(Not used),CMSG_SIGNED,CMSG_SIGNED_ENCODE_INFO,CMSG_ENVELOPED,CMSG_ENVELOPED_ENCODE_INFO
CMSG_SIGNED_AND_ENVELOPED(Not currently implemented),CMSG_HASHED
  3,CryptMsgOpenToDecode打开一个消息以便进行解码,返回打开消息的句柄
  CRYPTMSG WINAPI CryptMsgOpenToDecode(
  DWORD dwMsgEncodingType,     //指定编码类型。一般为X509_ASN_ENCODING|PKCS_7_ASN_ENCODING
  DWORD dwFlags,   //CMSG_DETACHED_FLAG,CMSG_CRYPT_RELEASE_CONTEXT_FLAG
  DWORD dwMsgType, //CMSG_DATA,CMSG_ENVELOPED,CMSG_HASHED,CMSG_SIGNED,CMSG_SIGNED_AND_ENVELOPED
  HCRYPTPROV hCryptProv,    //指定使用HASHING的句柄,一般设置为0
  PCERT_INFO pRecipientInfo,//保留字,必须为NULL
  PCMSG_STREAM_INFO pStreamInfo//假如流没被使用,必须为NULL
);
  4,CryptMsgUpdate增加内容到加密信息中
BOOL WINAPI CryptMsgUpdate(
  HCRYPTMSG hCryptMsg, //待更新的加密信息句柄
  const BYTE* pbData,  //待编码/解码的数据
  DWORD cbData,        // pbData 的数据长度
  BOOL fFinal         
);
第四个参数:当CMSG_DETACHED_FLAG没有设置,并且信息由CryptMsgOpenToDecode或CryptMsgOpenToEncode打开,那么fFinal被设置为TRUE,并且CryptMsgUpdate只被调用一次。当CMSG_DETACHED_FLAG被设置,并且信息由 CryptMsgOpenToEncode打开,那么仅在最后一次调用CryptMsgUpdate才被设置为TRUE。当CMSG_DETACHED_FLAG被设置,并且信息由CryptMsgOpenToDecode打开,那么仅在信息头单独被处理时CryptMsgUpdate才被设置为TRUE。
  5,CryptMsgGetParam在数据编码/解码后获取参数
  BOOL WINAPI CryptMsgGetParam(
  HCRYPTMSG hCryptMsg,  //in 信息句柄
  DWORD dwParamType,   //in 参数众多,参见MSDN
  DWORD dwIndex,       //in 可适用的返回参数句柄,假如参数没有被获取,则被忽略或则为0
  void* pvData,        //out 获取的数据指针
  DWORD* pcbData      //in,out数据长度
);
   6,CryptMsgClose关闭信息句柄
BOOL WINAPI CryptMsgClose(
  HCRYPTMSG hCryptMsg
);
具体的例子:
cbEncodedBlob = CryptMsgCalculateEncodedLength(
             MY_ENCODING_TYPE,       // Message encoding type
             0,                      // Flags
             CMSG_DATA,              // Message type
             NULL,                   // Pointer to structure
             NULL,                   // Inner content object ID
             cbContent))             // Size of content
hMsg = CryptMsgOpenToEncode(
          MY_ENCODING_TYPE,        // Encoding type
          0,                       // Flags
          CMSG_DATA,               // Message type
          NULL,                    // Pointer to structure
          NULL,                    // Inner content object ID
          NULL))                   // Stream information (not used)
CryptMsgUpdate(
        hMsg,         // Handle to the message
        pbContent,    // Pointer to the content
        cbContent,    // Size of the content
        TRUE))        // Last call
{
CryptMsgGetParam(
               hMsg,                      // Handle to the message
               CMSG_BARE_CONTENT_PARAM,   // Parameter type
               0,                         // Index
               pbEncodedBlob,             // Pointer to the BLOB
               &cbEncodedBlob))           // Size of the BLOB
CryptMsgClose(hMsg);
hMsg = CryptMsgOpenToDecode(
               MY_ENCODING_TYPE,      // Encoding type.
               0,                     // Flags.
               CMSG_DATA,             // Look for a data message.
               NULL,                  // Cryptographic provider.
               NULL,                  // Recipient information.
               NULL))                 // Stream information.

posted on 2006-07-05 23:18 david.turing 阅读(3451) 评论(1)  编辑  收藏 所属分类: CryptoAPI

评论

# re: CryptoAPI第一天 2008-03-26 16:39 haityang

请教您一个问题,假如我现在有一个pkcs#7 envelope,假设它是由三个证书加密的,但我没有任何一个证书的私钥,现在我想从这个信封中删除一个证书.应该怎么实现?  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

导航

统计

常用链接

留言簿(110)

我参与的团队

随笔分类(126)

随笔档案(155)

文章分类(9)

文章档案(19)

相册

搜索

积分与排名

最新随笔

最新评论

阅读排行榜

评论排行榜