如何使用USB Key中的证书对数据进行签名

 

    现在国内银行大量在网上银行中使用USB Key来加强网络银行的安全性,从技术角度来看,USB Key使用的PKI架构无疑比单纯的用户名/密码、动态密码以及文件证书来的安全很多。

   在本文中,我将演示如何使用USB Key来对一段信息(文件或者消息)进行签名。

   本文使用Gemalto(原为Gemplus)的Gemsafe解决方案作为示例,Gemsafe可以支持众多智能卡读卡器+智能卡卡片以及USB Key,本文使用的是GemeSeal USB Key。目前,国内有多家主流银行OEM使用Gem eSeal USB Key作为网上银行的安全方案。下图为两种在中国市场上可以看到的Gem eSeal USB Key。

如何使用USB Key中的证书对数据进行签名_第1张图片

   为完成本文的示例,读者需要在操作系统中安装有“Gemplus GemSAFE Card CSP”。CSP(Crypto Service Provider)是微软定义的一套安全API集合,开发人员可以通过这套API来抽象不同的安全实体(如USB Key,只能卡设备,安全存储设备驱动,生物识别设备等等),而不需要关心提供安全保障设备的具体细节。“Gemplus GemSAFE Card CSP”可以从单独的Gemsafe解决方案中安装,也可以从国内各个银行OEM的设备驱动中安装。目前,GemSAFE的主流版本为5.X,这个版本支持主流Windows操作系统以及Linux和Mac OS操作系统。

   如何检查GemSAFE CSP是否已经安装?在注册表“HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Cryptography/Defaults/Provider” 下可以看到目前操作系统上已经安装和操作系统内置的CSP。需要注意的是,Windows 2000,XP和2003内置了一个较低版本的“Gemplus GemSAFE Card CSP v1.0”,这个CSP只能支持智能卡设备和实现有限的功能,读者需要注意区别其与“Gemplus GemSAFE Card CSP”之间的区别。安装“Gemplus GemSAFE Card CSP”之后,这个较低版本的CSP会被移除。

   此外,为了能进行签名操作,还需要在USB Key上加载一个PKI证书,证书可以从不同的CA平台上生成和获得(如各个国内银行、政府机构和CA提供商),下面附有一个示例用途的证书。http://dl2.csdn.net/down4/20070729/29200708459.pfx

   一般而言,网络银行系统会将证书直接签发到USB Key上,USB Key通过内置的COS(Card OS)来保证证书中的机密信息(即证书的私钥)不能被导出,任何使用私钥的运算必须在USB Key内部完成,并需要用户校验USB Key的PIN码。USB Key PIN会在多次失败尝试后自动锁定以防止外界攻击。因此,相比使用软件证书的网络银行系统而言,使用USB Key的网络银行系统能更好的保证安全性。

   目前Gem eSeal USB Key可以支持主流的512bit、1024bit和2048bit长度的RSA算法密钥以及相应的证书,下图为加载了上文的示例用途的证书后的Gem eSeal USB Key。

如何使用USB Key中的证书对数据进行签名_第2张图片

 

   下面,我们可以开始使用“Gemplus GemSAFE Card CSP”来进行开发。以Microsoft Visual C++ 6.0为例,一般而言,此类程序可以使用任何可以调用Windows API的开发环境来进行开发,例如MS Visual Studio 6.0/2003/2005, Delphi,Java,Dev C++等。

   首先新建一个窗口程序,下面为具体代码

//  CSDNDemo.cpp : 演示使用Gem eSeal进行签名操作的窗口程序代码
//

#include 
" stdafx.h "

#include 
< stdlib.h >
#include 
< stdio.h >
#include 
< conio.h >
#include 
< ctype.h >
#include 
< windows.h >
#include 
< tchar.h >
#include 
< stdio.h >

#include 
< wincrypt.h >     // 需要引入Windows Crypto API的头文件

int  main( int  argc,  char *  argv[])
{
   DWORD i;
   
char *   szFileName[ 256 ];
   
   BOOL  rslt;            
// 存放函数返回
   FILE   * fp = NULL;
   BYTE  Buffer[
512 ];
   DWORD ulLen;
   
   HCRYPTPROV   hProv;            
// CSP句柄
   HCRYPTKEY    hKey;              // 证书私钥句柄
   HCRYPTHASH   hHash;             // 哈希对象句柄
   
   BYTE         
* pbHashSize;       // 指向哈希值大小的指针
   DWORD        dwHashLen  =   sizeof (DWORD);
   
   BYTE         
* pbHash;           // 指向哈希值的指针
   
   BYTE         
* pbSignature;      // 指向签名值的指针
   DWORD        dwSigLen;
      
   printf(
" Insert your Smart Card in the reader and press Enter... " );
   getch();

   
//  获得存放需要签名数据的文件的文件名
   printf( " Please enter the name of file to be signed:  " );
   scanf(
" %s " , szFileName); 
   
   SetLastError(
0 );       
   
  
// 使用默认证书的模式调用GemSAFE CSP
  
// 如果USB Key上存有多个证书,直接使用默认证书进行签名操作
  
// 如许指定某个证书,可以通过证书key container name来进行指定
   printf( " Acquiring GemSafe CSP context on Default Key Container... " );
   rslt 
=  CryptAcquireContext( & hProv,
                               NULL,  
//  使用默认证书模式
                " Gemplus GemSAFE Card CSP " // GemSafe CSP名称
            PROV_RSA_FULL, 
           
0 );

   
if ( ! rslt) {
      printf(
" CryptAcquireContext error %d(%08X) " , rslt, GetLastError());
      getch();
     
return   1 ;
   }
      
   
// 尝试获得证书私钥对象
   printf( " Retrieving AT_SIGNATURE key... " );
   rslt 
=  CryptGetUserKey(hProv, AT_SIGNATURE,  & hKey);
   
if ( ! rslt) {
      printf(
" CryptGetUserKey error %d(%08X) " , rslt, GetLastError());
      getch();
      
return   1 ;
   }
   printf(
" AT_SIGNATURE key retrieved " );
 
   
// 创建哈希对象,使用CALG_SHA1算法
   rslt  =  CryptCreateHash(hProv, CALG_SHA1,  0 0 & hHash); 
   
if ( ! rslt) {
      printf(
" CryptCreateHash error %d(%08X) " , rslt, GetLastError());
      getch();
      
return   1 ;
   }

   
// 打开存放需要签名数据的文件
    if ((fp  =  fopen(( const   char   * )szFileName,  " rb " ))  ==  NULL)
   {
      fprintf(stderr, 
" Error: Could not open %s " , szFileName);
      
return   1 ;
   }

   
// 读取数据到系统哈希操作缓冲中
    while ((ulLen  =  fread(Buffer,  1 sizeof (Buffer), fp))  !=   0 )
   {  
      rslt 
=  CryptHashData(hHash, Buffer, ulLen,  0 );
      
if ( ! rslt) {
         printf(
" CryptHashData error %d(%08X) " , rslt, GetLastError());
         getch();
         
return   1 ;
      }
   }
   fclose(fp);

   
// 分配接受数据缓冲的内存
    if ( ! (pbHashSize  = (BYTE  * ) malloc(dwHashLen)))
      printf(
" Memory allocation failed. " );


   
// 获得哈希的长度
   rslt  =  CryptGetHashParam(hHash, HP_HASHSIZE, pbHashSize,  & dwHashLen,  0 );
   
if ( ! rslt) {
       printf(
" CryptGetHashParam error %d(%08X) " , rslt, GetLastError());
       getch();
       
return   1 ;
   }
       
   
// 获得哈希值的长度
   rslt  =  CryptGetHashParam(hHash, HP_HASHVAL, NULL,  & dwHashLen,  0 );
   
if ( ! rslt) {
      printf(
" CryptGetHashParam error %d(%08X) " , rslt, GetLastError());
      getch();
      
return   1 ;
   }

   
// 分配内存
    if ( ! (pbHash  =  (BYTE * )malloc(dwHashLen)))
   printf(
" Allocation failed. " );

   
// 获得哈希值
   rslt  =  CryptGetHashParam(hHash, HP_HASHVAL, pbHash,  & dwHashLen,  0 );
   
if ( ! rslt) {
      printf(
" CryptGetHashParam error %d(%08X) " , rslt, GetLastError());
      getch();
      
return   1 ;
   }

   
//  打印哈希值
   printf( " Hash of %s: " , szFileName);
   
for (i  =   0  ; i  <  dwHashLen ; i ++
   {
      printf(
" %02X%c " , pbHash[i],  ' : ' );
   }
   printf(
" " );
   free(pbHash);

   
// 进行签名操作
   dwSigLen =   0 ;
   
   
// 取得签名数据长度
   rslt  =  CryptSignHash(hHash, AT_SIGNATURE, NULL,  0 , NULL,  & dwSigLen);
   
if ( ! rslt) {
      printf(
" CryptHashData error %d(%08X) " , rslt, GetLastError());
      getch();
      
return   1 ;
   }
   
   
// 分配内存
    if (pbSignature  =  (BYTE  * )malloc(dwSigLen))
      printf(
" " );
   
else
      printf(
" Out of memory! " );

   
// 使用类型为AT_SIGNATURE的密钥对上文获得的哈希值进行签名
   rslt  =  CryptSignHash(hHash, AT_SIGNATURE, NULL,  0 , pbSignature,  & dwSigLen);
   
   
if ( ! rslt) {
      printf(
" CryptSignHash error %d(%08X) " , rslt, GetLastError());
      getch();
      
return   1 ;
   }
   
   
// 销毁哈希对象
    if (hHash) 
      CryptDestroyHash(hHash);

   
// 打印签名值
   printf( " Signature of %s: " , szFileName);
   
for  (i  =   0 ; i  <  dwSigLen; i ++ )
   {
      printf(
" %02X%c " , pbSignature[i], (((i + 1 ) % 16   &&  (i != dwSigLen - 1 )) ?   ' : '  :  ' ' ));
   }
   printf(
" " );
   
  
// 释放CSP句柄
  printf( " Releasing GemSafe CSP context... " );
   rslt 
=  CryptReleaseContext(hProv,  0 );
   
if ( ! rslt) {
      printf(
" CryptSignHash error %d(%08X) " , rslt, GetLastError());
      getch();
      
return   1 ;
   }

  printf(
" Press Enter to exit... " );
  getch();
    
  
return   0 ;
}

  整个可编译的工程:http://dl2.csdn.net/down4/20070729/29205358201.zip

  示例用的数据文件:http://dl2.csdn.net/down4/20070729/29205557510.txt

  运行结果(其中CSP会提示用户输入Gem eSeal USB Key的PIN):如何使用USB Key中的证书对数据进行签名_第3张图片

  在这段代码中,我们做了什么?

1.调用了GemSAFE CSP,生成相应的句柄;

2.读取源数据文件,然后对其取SHA1的哈希。之所以不直接将原数据进行RSA加密的原因在于RSA运算消耗大量计算时间,而我们的目的在于对数据进行签名而非加密,所以,我们只要取源数据的哈希然后将哈希值加密即可。如果攻击者意图篡改原文,其哈希值就会发生变化,而攻击者无法获得证书私钥来伪造加密的哈希值;

3.由于USB Key中内置的CPU和数字和处理器的运算能力的限制,推荐开发人员使用如2所述的签名方法而不是将大量数据送入USB Key中进行RSA加密;

4.我们获得源数据文件哈希值之后,调用GemSAFE CSP,将哈希值传入Gem eSeal USB Key,在其内部使用私钥进行RSA加密运算,获得RSA加密后的签名数据。

  验证这个签名,只需要进行如下步骤:

1.读取源数据文件,然后对其取SHA1的哈希;

2.使用公开的证书公钥解密RSA加密后的签名数据,将之与原文的哈希值比较。

 

  关于RSA算法中公钥和私钥的相互关系、使用方法和RSA的原理,请参阅其他相应文档。

你可能感兴趣的:(PKI技术)