现在国内银行大量在网上银行中使用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。
为完成本文的示例,读者需要在操作系统中安装有“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。
下面,我们可以开始使用“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):
在这段代码中,我们做了什么?
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的原理,请参阅其他相应文档。