数字签名算法(RSA)
题目:数字签名算法(RSA)
一、课题名称
实现数字签名,其中加密算法使用RSA。
二、课题内容和要求
1、主要任务与目标
1)被发送文件采用某种算法对原始消息进行运算,得到一个固定长度的数字串,称为消息摘要(MD),不同的消息得到的消息摘要各异,但是对相同的消息它的消息摘要却是唯一的;
2)发送方生成消息的消息摘要,用自己的私钥对摘要进行加密来形成发送方的数字签名;
3)这个数字签名将作为消息的附件和消息一同用接收方的公钥进行加密,将加密后的密文一起发送给接收方;
4)接收方首先把接收到的密文用自己的私钥解密,得到原始消息和数字签名,再用发送方的公钥解密数字签名,随后用同样的算法计算出消息摘要;
5)如果计算出来的消息摘要和发送方发送给他的消息摘要(通过解密数字签名得到的)是相同的,这样接收方就能确认数字签名确实是发送方的,否则就认为收到的消息是伪造的或是中途被篡改的。
数字签名通过认证技术来辨认真伪。认证技术主要包括数字签名认证、身份认证以及公开密钥证明等。数字签名认证机制提供了一种对数字签名进行鉴别的方法;身份认证机制提供了辨别和确认通信双方真实身份的方法;公开密钥证明机制则对密钥进行验证。网络时代中,人们验证数字签名来确定你正在和谁打交道,验证你的文件是否已被黑客篡改。数据的安全性和真实性已成为网络安全中至关重要的一部分。
数字签名类似手书签名,它具有以下的性质:
1)能够验证签名产生者的身份,以及产生签名的日期和时间;
2)能用于证实被签消息内容;
3)数字签名可由第三方验证,从而能够解决通信双方的争议。
为了实现数字签名的以上性质,它就应满足下列要求:
1)签名是可信的:任何人都可以验证签名的有效性;
2)签名是不可伪造的:除了合法的签名者外,任何人伪造其签名是困难的;
3)签名是不可复制的:对一个消息的签名不能通过复制变为另一个消息的签名。如
果一个消息的签名是从别处复制得到的,则任何人都可以发现消息与签名之间的不一致性,从而可以拒绝签名的消息;
4)签名的消息是不可改变的:经签名的消息不能篡改,一旦签名的消息被篡改,任何人都可以发现消息与签名之间的不一致性;
5)签名是不可抵赖的:签名者事后不能否认自己的签名。可以由第三方或仲裁方来确认双方的信息,以做出仲裁。
2、各模块功能描述
模块一创建创建密钥容器,得到CSP句柄
微软的CryptoAPI是PKI推荐使用的加密API。其功能是为应用程序开发者提供在Win32环境下使用加密、验证等安全服务时的标准加密接口。CryptoAPI处于应用程序和CSP之间(见图一)。
图一
从图一可以看到,每个CSP有一个密钥库,密钥库用于存储密钥。而每个密钥库包括一个或多个密钥容器(Key Containers)。每个密钥容器中含属于一个特定用户的所有密钥对。每个密钥容器被赋予一个唯一的名字。在销毁密钥容器前CSP将永久保存每一个密钥容器,包括保存每个密钥容器中的公/私钥对。
在这个模块中,实现创建密钥容器,得到CSP密钥句柄的作用,并且它被绑定到以UserName为名的密钥容器上。
模块二计算Hash值,签名
在这个模块中,实现实现对原始报文的签名。首先,对原始报文进行散列值的计算。此处通过CryptoAPI中的函数,直接调用实现。然后调用CryptSignHash(),对散列值进行签名。过程如图二所示。
计算Hash
对Hash进行签名
图二
模块三验证签名
负责验证签名的人在收到签名者发来的公钥、数据及签名后,先用CryptImportKey()将签名者的公钥导入密钥容器中,验证者通过对原文的Hash计算,并与收到的Hash值对比,验证是否是发送方发送的消息,并可验证其正确性。过程如图三所示。
导入公钥
计算Hash
验证签名
图三
三、概要设计
1、函数CryptAcquireContext(),获得指定CSP容器的句柄。主要参数表如下:
PhProv()CSP句柄指针
PszContainer()密钥容器名称,指向密钥容器的字符串指针
Pszprovider()指向CSP名称的字符串指针
这个函数用来实现取得指定CSP句柄密钥容器,以后任何的加密都是针对这个CSP 句柄而言。
2、函数CryptGenKey(),用来随即产生密钥。主要参数如下:
hCryptProv, CSP句柄
AT_SIGNATURE, 创建的密钥对类型为signature key pair
0, key类型,这里用默认值
&hKey 创建成功返回新创建的密钥对的句柄
3、函数CryptCreateHash(),用来创建Hash对象。参数如下:HCRYPTPROV hProv, CSP句柄
ALG_ID Algid, 选择hash算法,比如CALG_MD5等
HCRYPTKEY hKey, HMAC 和MAC算法时有用
DWORD dwFlags, 保留,传入0即可
HCRYPTHASH* phHash 返回hash句柄
4、函数CryptHashData(),用来Hash数据。参数如下:
HCRYPTHASH hHash, hash对象
BYTE* pbData, 被hash的数据
DWORD dwDataLen, 数据的长度
DWORD dwFlags 微软的CSP这个值会被忽略
5函数CryptDeriveKey(),用于调用CryptDeriveKey获取对话密码,参数如下:
hCryptProv, CSP句柄
CALG_RC2, 一个ALG_ID结构,用来指定对称密钥生成的算法
hHash, 哈希对象
CRYPT_EXPORTABLE, 指定生成密钥的类型,CRYPT_EXPORTABLE意味着这个
程序生成的密钥可以被其它程序调用,而不是仅仅限于这个程序当中。但是它不能用于非对称密码中。
&hKey 指向生成的密钥
5、总体设计
发送方验证方
四、详细设计
1、RSA算法描述
RSA算法是一种公钥密码算法,实现RSA算法包括生成RSA密钥,加密和解密数据。RSA算法是第一个能同时用于加密和数字签名的算法,也易于理解和操作。RSA是被研究得最广泛的公钥算法,从提出到现在已近二十年,经历了各种攻击的考验,逐渐为人们接受,普遍认为是目前最优秀的公钥方案之一。RSA的安全性依赖于大数的因子分解,但并没有从理论上证明破译RSA的难度与大数分解难度等价。即RSA的重大缺陷是无法从理论上把握它的保密性能如何,而且密码学界多数人士倾向于因子分解不是NP-C问题。RSA 的缺点主要有:A)产生密钥很麻烦,受到素数产生技术的限制,因而难以做到一次一密。
B)分组长度太大,为保证安全性,n 至少也要600 bits。
RSA算法的实现原理:
1) 随机选择两个不同的素数p和q,它们的宽度是密钥宽度的二分之一。
2) 计算出p和q的乘积n 。
3) 在2和Φ(n)之间随机选择一个数e , e 必须和Φ(n)互素,整数e用做加密密钥(其中Φ(n)=(p-1)*(q-1))。
4) 从公式ed ≡ 1 mod Φ(n)中求出解密密钥d 。
5) 得公钥(e ,n ), 私钥(d , n) 。
6) 公开公钥,但不公开私钥。
7) 将明文P (假设P是一个小于n的整数)加密为密文C,计算方法为:
C = P^e mod n;
8) 将密文C解密为明文P,计算方法为:
P=C^d mod n;
然而只根据n和e(不是p和q)要计算出d是不可能的。因此,任何人都可对明文进行加密,但只有授权用户(知道d)才可对密文解密。
2、创建密钥容器,得到CSP句柄
此处调用函数CryptAcquireContext(),创建密钥容器,当Flag values值为零,说明以UserName为名的密钥容器存在,那么我们已经得到了CSP的句柄
具体实现代码如下:
if(CryptAcquireContext(
&hCryptProv, // 返回CSP句柄
UserName, // 密码容器名
NULL, // NULL时使用默认CSP名(微软RSA Base Provider)PROV_RSA_FULL, // CSP类型
0)) // Flag values
{
printf("得到了CSP句柄", UserName);
}
如果密钥容器不存在,我们需要重新创建这个密钥容器:
if(CryptAcquireContext(
&hCryptProv,
UserName,
NULL,
PROV_RSA_FULL,
CRYPT_NEWKEYSET)) //创建以UserName为名的密钥容器
{
//创建密钥容器成功,并得到CSP句柄
printf("一个新的密钥容器被创建\n");
}
else
{
HandleError("创建失败.\n");
}
}
此时就已经创建了密钥容器,并得到了CSP的句柄。也可以这样理解,我们得到了一个CSP的句柄,并且它被绑定到以UserName为名的密钥容器上。
可以如下删除密钥容器。
CryptAcquireContext(&hCryptProv,userName,NULL,PROV_RSA_FULL,
CRYPT_DELETEKEYSET);
3、RSA算法描述
1)被发送文件采用某种算法对原始消息进行运算,得到一个固定长度的数字串,称为消
息摘要(MD),不同的消息得到的消息摘要各异,但是对相同的消息它的消息摘要却是唯一的;
2)发送方生成消息的消息摘要,用自己的私钥对摘要进行加密来形成发送方的数字签名;3)这个数字签名将作为消息的附件和消息一同用接收方的公钥进行加密,将加密后的密文一起发送给接收方;
4)接收方首先把接收到的密文用自己的私钥解密,得到原始消息和数字签名,再用发送方的公钥解密数字签名,随后用同样的算法计算出消息摘要;
5)如果计算出来的消息摘要和发送方发送给他的消息摘要(通过解密数字签名得到的)是相同的,这样接收方就能确认数字签名确实是发送方的,否则就认为收到的消息是伪造的或是中途被篡改的。
具体程序实现如下:
1、读取原文
void CGenRsaKey::OnSOpenButton()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
char szFilter[] = "全部文件(*.*)|*.*||";
CFileDialog OpenFile(TRUE, NULL, "*.*",
OFN_HIDEREADONLY|OFN_FILEMUSTEXIST|OFN_NOCHANGEDIR, szFilter, NULL);
if(OpenFile.DoModal() != IDOK)
{
return;
}
m_strSSrcPath = OpenFile.GetPathName();
UpdateData(FALSE);
return;
}
//产生随机密钥
void CGenRsaKey::OnVerifySaveButton()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
static char szFilter[] = "全部文件(*.*)|*.*||";
CFileDialog saveFileDlg(FALSE, NULL, "*.*",
OFN_HIDEREADONLY|OFN_FILEMUSTEXIST|OFN_NOCHANGEDIR,szFilter,NULL) ;
if(saveFileDlg.DoModal() != IDOK)
{
return;
}
m_strVDestPath = saveFileDlg.GetPathName();
UpdateData(FALSE);
return;
}
void CGenRsaKey::OnRsaSign()
{
// TODO: Add your control notification handler code here
if(m_strSSrcPath == "" && m_strVDestPath == "" )
{
MessageBox("请选择待签名/待验证文件路径!");
return;
}
//读取签名原文失败
CFile fpSrcFile;
if(fpSrcFile.Open(m_strVDestPath, CFile::modeRead) == 0)
{
MessageBox("读取签名原文失败!");
return;
}
nSrcLen = fpSrcFile.GetLength();
pbSrcData = new unsigned char [nSrcLen+1];
memset(pbSrcData, 0x00, nSrcLen+1);
fpSrcFile.Read(pbSrcData, nSrcLen);
fpSrcFile.Close();
2、获得CSP句柄
int CKeyOperation::CRYPTAPI_RSA Verify(unsigned char *pbSrcData, int nSrcLen,
unsigned char *pbDestData, int nDestLen)
{
if(!CryptAcquireContext(&m_hProv, "ASYSIGN", MS_ENHANCED_PROV, PROV_RSA_FULL, 0))
return -1;
if(!CryptGetUserKey(m_hProv, AT_SIGNATURE, &m_hKey))
{
if(m_hProv)
CryptReleaseContext(m_hProv, 0);
return -2;
}
3、创建Hash对象并Hash数据
HCRYPTHASH hHash;
if(!CryptCreateHash(m_hProv, CALG_SHA, 0, 0, &hHash)) //创建HASH对象
{
if(m_hProv)
CryptReleaseContext(m_hProv, 0); //释放CSP句柄
return -3;
}
if(!CryptHashData(hHash, pbSrcData, (unsigned long)nSrcLen, 0))//hash数据
{
if(m_hProv)
CryptReleaseContext(m_hProv, 0); //释放CSP句柄
if(hHash)
CryptDestroyHash(hHash); //释放Hash句柄
return -3;
}
4、签名数据
//获得签名数据长度
if(!CryptSignHash(hHash, AT_SIGNATURE, NULL, 0, NULL, (unsigned long *)pnDestLen))
{
if(m_hProv)
CryptReleaseContext(m_hProv, 0); //释放CSP句柄
if(hHash)
CryptDestroyHash(hHash); //释放Hash句柄
return -4;
}
//签名HASH数据
if(!CryptSignHash(hHash, AT_SIGNATURE, NULL, 0, pbDestData, (unsigned long *)pnDestLen))
{
if(m_hProv)
CryptReleaseContext(m_hProv, 0); //释放CSP句柄
if(hHash)
CryptDestroyHash(hHash); //释放Hash句柄
return -5;
}
if(hHash)
CryptDestroyHash(hHash); //释放Hash句柄
if(m_hProv)
CryptReleaseContext(m_hProv, 0); //释放CSP句柄
return 0;
}
5、读取签名数据
CFile fpDestFile;
if(fpDestFile.Open(m_strSSrcPath, CFile::modeRead) == 0)
{
MessageBox("读取签名原文失败!");
return;
}
nDestLen = fpDestFile.GetLength();
fpDestFile.Read(pbDestData, nDestLen);
fpDestFile.Close();
CFile fpDestFile;
if(fpDestFile.Open(m_strSSrcPath, CFile::modeRead) == 0)
{
MessageBox("读取签名原文失败!");
return;
}
nDestLen = fpDestFile.GetLength();
fpDestFile.Read(pbDestData, nDestLen);
fpDestFile.Close();
6、Hash待验证数据
//创建HASH对象
if(!CryptCreateHash(m_hProv, CALG_SHA, 0, 0, &hHash))
{
if(m_hProv)
CryptReleaseContext(m_hProv, 0); //释放CSP句柄return -2;
}
//HASH待验证数据
if(!CryptHashData(hHash, pbSrcData, nSrcLen, 0))
{
if(m_hProv)
CryptReleaseContext(m_hProv, 0); //释放CSP句柄if(hHash)
CryptDestroyHash(hHash); //释放Hash句柄return -3;
}
7、验证签名数据
//验证签名数据
CKeyOperation obj_Verify;
int r = obj_Verify.CRYPTAPI_RSA Verify(pbSrcData, nSrcLen, pbDestData, nDestLen);
if(r != 0)
{
MessageBox("验证签名失败!");
return;
}
delete [] pbSrcData;
pbSrcData = NULL;
MessageBox("验证签名成功!");
return;
}
if(!CryptVerifySignature(hHash, pbDestData, nDestLen, m_hKey, NULL, 0)) {
if(m_hProv)
CryptReleaseContext(m_hProv, 0); //释放CSP句柄
if(hHash)
CryptDestroyHash(hHash); //释放Hash句柄
return -4;
}
if(hHash)
CryptDestroyHash(hHash);
if(m_hProv)
CryptReleaseContext(m_hProv, 0); //释放CSP句柄
return 0;
}
五、测试数据及其结果分析
测试时,在签名原文中输入数据:1234567890
生成RSA密钥对,并导出公钥
程序显示产生RSA密钥对成功,说明在签名时产生密钥对运行没有问题。
对签名原文进行签名,程序运行如下:
程序运行显示签名数据成功,签名后数据长度为128字节,签名后的数据存放在.txt 文档中,便于在下一步进行验证比较。
签名后的数据显示如下:
记事本显示为乱码,因为输出为16进制,在记事本中的显示就为乱码,当放入控制台时,显示就为16进制数。发现数据长度为128字节,签名正确。
对签名进行验证,程序运行如下:
待验证文件选择为签名后的数据,签名原文选择原文件。按下验证签名按钮,产生对话框,说明验证签名成功,程序运行无误。
六、软件设计总结
本次的课程设计,主要是熟悉了数字签名的具体过程,并了解了RSA算法的具体实现步骤,更重要的是学会了CryptoAPI的使用,通过调用其中的函数,很方便的实现了加密以及签名的功能。再次总结一下收获。
1、关于CSP
CSP是真正执行加密工作的独立的模块。物理上一个CSP由两部分组成:一个动态链接库,一个签名文件。若加密算法用硬件实现,则CSP还包括硬件装置。
其决定了以下因素:
(1)有且仅有一个密钥交换算法;
(2)有且仅有一个签名算法;
(3)特定的Key Blob格式;
(4)特定的数字签名格式;
(5)特定的密钥推导模式;
(6)特定的密钥长度;
(7)特定的分组加密算法的缺省模式。
2、几个误区
通过本次数字签名的实验,我通过上网查阅资料等方式,发现存在着这样几个误
区。
误区一:大家对公钥私钥区分得太死板了。其实当你把公钥保密不公开的时候,公钥就是私钥了;当你把私钥公开的时候,私钥也是公钥了。没必要记得这么死的。
密钥与私钥只是相对的概念,RSA中产生的e与d,当把e公布出去时,e就是公钥,d就是私钥。相反的e就变成了私钥。
误区二:对RSA算法的原理理解不够,实际上所谓的公钥和私钥只是RSA算法(说穿了RSA就是个数学方程式)的参数(未知数),比如X+Y+M=Z,X就可以说是私钥,Y就可以说是公钥,M就是需要加密的内容,Z就是加密后的密文,当然RSA 中不可能只有X和Y两个未知数的,所以就经常有朋友问,到底X是私钥还是Y是钥。其实这个就要取决于你用在时候,什么地方了。
误区三:对Security.Cryptography命名空间不熟悉。“数字签名”一般的做法是:A先计算出文件M的HASH码,再对HASH码进行加密(这个步骤就是签名),再把M(文件M不要加密,第三方可以查阅)和加密后的HASH码传送给B,B再用A的公钥来解密刚才得到的加密HASH码,如果能解密,那就说明这个文件是A发的,具有法律效应。再计算出得到的文件M的HASH码,再和刚才解密出来的HASH码比较(这个步骤叫验证签名),如果一致就说明文件M在传输过程中没有被修改。但是需要解密RSA,就必须提供公钥和私钥,当然这和我们的现实不符,因为A不可能把他的私钥给B。许多人就是在这里难住了。其实Security.Cryptography命名空间中有RSAPKCS1SignatureFormatter 和RSAPKCS1SignatureDeformatter 两个方法或者说是对象。前者用来对HASH进行加密(签名),后者用来验证签名,用这个验证就只需要A的公钥就行了。签名就是这个两个专用的方法,不是用RSA普通的加密解密。
3、关于CryptoAPI的使用。
由于本次实验,我是运用CryptoAPI解决了加密及签名的功能。而没有按照标准的RSA 算法一步一步实现对Hash的加密,这样在程序设计及实现的时候都趋于简单明了,但这要求对CryptoAPI的使用规则较为熟悉,深刻理解解密钥容器的概念,并在使用前,首先创建密钥容器,在结束时要销毁没要容器CSP。其实CSP是真正实行加密的独立模块,他既可以由软件实现也可以由硬件实现。但是他必须符合CryptoAPI接口的规范。
每个CSP都有一个名字和一个类型。每个CSP的名字是唯一的,这样便于CryptoAPI 找到对应的CSP。目前已经有9种CSP类型,并且还在增长。它们支持的密钥交换算法、签名算法、对称加密算法和Hash算法。在本次实验中,使用的Hash算法是SHA,这也是CSP所支持的。