基于VC++2010实现哈希签名与验证

 

数字签名即如何给一个计算机文件进行签字。数字签字可以用对称算法实现,也可以用公钥算法实现。但前者除了文件签字者和文件接受者双方,还需要第三方认证,较麻烦;通过公钥加密算法的实现方法,由于用秘密密钥加密的文件,需要靠公开密钥来解密,因此这可以作为数字签名,签名者用秘密密钥加密一个签名(可以包括姓名、证件号码、短信息等信息),接收人可以用公开的、自己的公开密钥来解密,如果成功,就能确保信息来自该公开密钥的所有人。
  
  公钥密码体制实现数字签名的基本原理很简单,假设A要发送一个电子文件给B,A、B双方只需经过下面三个步骤即可:
  
  1. A用其私钥加密文件,这便是签字过程
  
  2. A将加密的文件送到B
  
  3. B用A的公钥解开A送来的文件
  
  这样的签名方法是符合可靠性原则的。即:
  
  签字是可以被确认的,
  签字是无法被伪造的,
  签字是无法重复使用的,
  文件被签字以后是无法被篡改的,
  签字具有无可否认性,
  数字签名就是通过一个单向函数对要传送的报文进行处理得到的用以认证报文来源并核实报文是否发生变化的一个字母数字串。用这几个字符串来代替书写签名或印章,起到与书写签名或印章同样的法律效用。国际社会已开始制定相应的法律、法规,把数字签名作为执法的依据。
  
  数字签名的实现方法
  
  实现数字签名有很多方法,目前数字签名采用较多的是公钥加密技术,如基于RSA Data Security公司的PKCS(Public Key Cryptography Standards)、DSA(Digital Signature Algorithm)、x.509、PGP(Pretty Good Privacy)。1994年美国标准与技术协会公布了数字签名标准(DSS)而使公钥加密技术广泛应用。同时应用散列算法(Hash)也是实现数字签名的一种方法。
  
  非对称密钥密码算法进行数字签名
  
  算法的含义:
  
  非对称密钥密码算法使用两个密钥:公开密钥和私有密钥,分别用于对数据的加密和解密,即如果用公开密钥对数据进行加密,只有用对应的私有密钥才能进行解密;如果用私有密钥对数据进行加密,则只有用对应的公开密钥才能解密。
  
  使用公钥密码算法进行数字签名通用的加密标准有: RSA,DSA,Diffie-Hellman等。
  
  签名和验证过程:
  
  发送方(甲)首先用公开的单向函数对报文进行一次变换,得到数字签名,然后利用私有密钥对数字签名进行加密后附在报文之后一同发出。
  
  接收方(乙)用发送方的公开密钥对数字签名进行解密交换,得到一个数字签名的明文。发送方的公钥可以由一个可信赖的技术管理机构即认证中心(CA)发布的。
  
  接收方将得到的明文通过单向函数进行计算,同样得到一个数字签名,再将两个数字签名进行对比,如果相同,则证明签名有效,否则无效。
  
  这种方法使任何拥有发送方公开密钥的人都可以验证数字签名的正确性。由于发送方私有密钥的保密性,使得接受方既可以根据结果来拒收该报文,也能使其无法伪造报文签名及对报文进行修改,原因是数字签名是对整个报文进行的,是一组代表报文特征的定长代码,同一个人对不同的报文将产生不同的数字签名。这就解决了银行通过网络传送一张支票,而接收方可能对支票数额进行改动的问题,也避免了发送方逃避责任的可能性。
  
  对称密钥密码算法进行数字签名
  
  算法含义
  
  对称密钥密码算法所用的加密密钥和解密密钥通常是相同的,即使不同也可以很容易地由其中的任意一个推导出另一个。在此算法中,加、解密双方所用的密钥都要保守秘密。由于计算机速度而广泛应用于大量数据如文件的加密过程中,如RD4和DES,用IDEA作数字签名是不提倡的。
  
  使用分组密码算法数字签名通用的加密标准有:DES,Tripl-DES,RC2,RC4,CAST等。
  
  签名和验证过程
  
  Lamport发明了称为Lamport-Diffle的对称算法:利用一组长度是报文的比特数(n)两倍的密钥A,来产生对签名的验证信息,即随机选择2n个数B,由签名密钥对这2n个数B进行一次加密交换,得到另一组2n个数C。
  
  发送方从报文分组M的第一位开始,依次检查M的第I位,若为0时,取密钥A的第i位,若为1则取密钥A的第i+1位;直至报文全部检查完毕。所选取的n个密钥位形成了最后的签名。
  
  接受方对签名进行验证时,也是首先从第一位开始依次检查报文M,如果M的第i位为0时,它就认为签名中的第i组信息是密钥A的第i位,若为1则为密钥A的第i+1位;直至报文全部验证完毕后,就得到了n个密钥,由于接受方具有发送方的验证信息C,所以可以利用得到的n个密钥检验验证信息,从而确认报文是否是由发送方所发送。
  
  这种方法由于它是逐位进行签名的,只有有一位被改动过,接受方就得不到正确的数字签名,因此其安全性较好,其缺点是:签名太长(对报文先进行压缩再签名,可以减少签名的长度);签名密钥及相应的验证信息不能重复使用,否则极不安全。
  
  结合对称与非对称算法的改进
  
  对称算法与非对称算法各有利弊,所以结合各自的优缺点进行改进,可以用下面的模块进行说明:
  
  Hash算法进行数字签名
  
  Hash算法也称作散列算法或报文摘要,Hash算法将在数字签名算法中详细说明。
  
  Hash算法数字签字通用的加密标准有: SHA-1,MD5等。
  
  数字签名算法  
  
  数字签名的算法很多,应用最为广泛的三种是: Hash签名、DSS签名、RSA签名。这三种算法可单独使用,也可综合在一起使用。数字签名是通过密码算法对数据进行加、解密变换实现的,常用的HASH算法有MD2、MD5、SHA-1,用DES算法、RSA算法都可实现数字签名。但或多或少都有缺陷,或者没有成熟的标准。
  
  Hash签名
  
  Hash签名是最主要的数字签名方法,也称之为数字摘要法(digital digest)、数字指纹法(digital finger print)。它与RSA数字签名是单独的签名不同,该数字签名方法是将数字签名与要发送的信息紧密联系在一起,它更适合于电子商务活动。将一个商务合同的个体内容与签名结合在一起,比合同和签名分开传递,更增加了可信度和安全性。下面我们将详细介绍Hash签名中的函数与算法。
  
  单向函数
  
  单向函数的概念是公开密钥密码的核心。尽管它本身并不是一个协议,但对大多数协议来说却是一个基本结构模块。
  
  单向函数的概念是计算起来相对容易,但求逆却非常困难。也就是说,已知x,我们很容易计算f(x)。但已知f(x),却难于计算出x。在这里,"难"定义成:即使世界上所有的计算机都用来计算,从f(x)计算出x也要花费数百万年的时间。
  
  打碎盘子就是一个很好的单向函数的例子。把盘子打碎成数千片碎片是很容易的事情,然而,要把所有这些碎片再拼成为一个完整的盘子,却是非常困难的事情。
  
  这听起来很好,但事实上却不能证实它的真实性。如果严格地按数学定义,我们不能证明单向函数的存在性,同时也还没有实际的证据能够构造出单向函数。即使这样,还是有很多函数看起来和感觉像单向函数:我们能够有效地计算它们,且至今还不知道有什么办法能容易地求出它们的逆。例如,在有限域中x2是很容易计算的,但计算x1/2却难得多。所以我们假定也尽量构造单向函数存在。
  
  陷门单向函数是有一个秘密陷门的一类特殊单向函数。它在一个方向上易于计算而反方向却难于计算。但是,如果你知道那个秘密,你也能很容易在另一个方向计算这个函数。也就是说, 已知x,易于计算f(x),而已知f(x),却难于计算x。然而,有一些秘密信息y,一旦给出f(x)和y,就很容易计算x。
  
  拆开表是很好的单向陷门函数的例子。很容易把表拆成数百片小片,把这些小片组装成能够工作的表是非常困难的。然而,通过秘密信息(表的装配指令),就很容易把表还原。
  
  单向Hash函数
  
  单向Hash函数有很多名字:压缩函数、缩短函数、消息摘要、指纹、密码校验和、信息完整性检验(DIC)、操作检验码(MDC)。不管你怎么叫,它是现代密码学的中心。单向Hash函数是许多协议的另一个结构模块。
  
  Hash函数长期以来一直在计算机科学中使用,无论从数学上或别的角度看,Hash函数就是把可变输入长度串(叫做预映射,Pre-image)转换成固定长度(经常更短)输出串(叫做hash值)的一种函数。简单的Hash函数就是对预映射的处理,并且返回由所有输入字节异或组成的一字节。
  
  这儿的关键就是采集预映射的指纹:产生一个值,这个值能够指出候选预映射是否与真实的预映射有相同的值。因为Hash函数是典型的多到一的函数,我们不能用它们来确定两个串一定相同,但我们可用它来得到准确性的合理保证。
  
  单向Hash函数是在一个方向上工作的Hash函数,从预映射的值很容易计算其Hash值,但要产生一个预映射的值

 

关于Hash签名与认证的Vc++实现,请参考下列代码与代码注释

 

 

 

#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0400 #endif #include <stdio.h> #include <windows.h> #include <wincrypt.h> #define MY_ENCODING_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING) //函数申明 void HandleError(char *s); HCRYPTPROV GetCryptProv(); void main(void) { //------------------------------------------------------------------- // 变量申明及初始化 HCRYPTPROV hProv; BYTE *pbBuffer= (BYTE *)"The data that is to be hashed and signed."; DWORD dwBufferLen = strlen((char *)pbBuffer)+1; HCRYPTHASH hHash; HCRYPTKEY hKey; HCRYPTKEY hPubKey; BYTE *pbKeyBlob; BYTE *pbSignature; DWORD dwSigLen; DWORD dwBlobLen; LPTSTR szDescription = "Test Data Description"; //------------------------------------------------------------------- // 获取CSP句柄 hProv = GetCryptProv(); //------------------------------------------------------------------- // 获取密钥对,其中的私钥用于创建数字签名,公钥将用于验证签名。 if(CryptGenKey( hProv, AT_SIGNATURE, 0, &hKey)) { printf("签名密钥对已经被获取./n"); } else { HandleError("创建签名密钥出错./n"); } //------------------------------------------------------------------- // 导出密钥对的公钥,确定密钥块数据长度 if(CryptExportKey( hKey, NULL, PUBLICKEYBLOB, 0, NULL, &dwBlobLen)) { printf("公钥块的数据长度已经确定. /n"); } else { HandleError("计算密钥数据块长度出错."); } //------------------------------------------------------------------- // 分配空间 if(pbKeyBlob = (BYTE*)malloc(dwBlobLen)) { printf("已经为此数据块分配了内存空间. /n"); } else { HandleError("所需内存不够. /n"); } //------------------------------------------------------------------- // 导出密钥对的公钥 if(CryptExportKey( hKey, NULL, PUBLICKEYBLOB, 0, pbKeyBlob, &dwBlobLen)) { printf("公钥块中的数据已经写入到导出块中. /n"); } else { HandleError("Error during CryptExportKey."); } //------------------------------------------------------------------- // 创建哈希句柄 if(CryptCreateHash( hProv, CALG_MD5, 0, 0, &hHash)) { printf("哈希句柄已经创建. /n"); } else { HandleError("Error during CryptCreateHash."); } //------------------------------------------------------------------- // 计算输入数据的哈希值 if(CryptHashData( hHash, pbBuffer, dwBufferLen, 0)) { printf("缓冲区中的数据已经进行了哈希处理./n"); } else { HandleError("Error during CryptHashData."); } //------------------------------------------------------------------- // 确定签名信息的长度 dwSigLen= 0; if(CryptSignHash( hHash, AT_SIGNATURE, szDescription, 0, NULL, &dwSigLen)) { printf("签名信息的长度 %d 找到./n",dwSigLen); } else { HandleError("Error during CryptSignHash."); } //------------------------------------------------------------------- // 分配空间 if(pbSignature = (BYTE *)malloc(dwSigLen)) { printf("给此签名消息分配了内存空间./n"); } else { HandleError("所需内存不够."); } //------------------------------------------------------------------- // 对哈希数据进行数字签名 if(CryptSignHash( hHash, AT_SIGNATURE, szDescription, 0, pbSignature, &dwSigLen)) { printf("对哈希数据已经进行了数字签名./n"); } else { HandleError("Error during CryptSignHash."); } //------------------------------------------------------------------- // 销毁哈希句柄 if(hHash) CryptDestroyHash(hHash); printf("哈希句柄已经被销毁./n"); printf("这个程序的签名阶段已经完成./n/n"); //------------------------------------------------------------------- // 下面是程序的第二阶段:验证数字签名。 // 一般的,这段程序应该是另一用户在另外一个应用程序中执行, // 其哈希值、数字签名消息、公钥都应该从文件或网络中获取。 // 这里仅是演示如何进行签名验证。 //------------------------------------------------------------------- // 从密钥块数据中导入公钥到CSP。 if(CryptImportKey( hProv, pbKeyBlob, dwBlobLen, 0, 0, &hPubKey)) { printf("公钥已经被导入./n"); } else { HandleError("导入公钥失败."); } //------------------------------------------------------------------- // 创建哈希句柄 if(CryptCreateHash( hProv, CALG_MD5, 0, 0, &hHash)) { printf("哈希句柄已经被重新创建. /n"); } else { HandleError("Error during CryptCreateHash."); } //------------------------------------------------------------------- // 计算输入数据的哈希值 if(CryptHashData( hHash, pbBuffer, dwBufferLen, 0)) { printf("新的哈希值已经计算出./n"); } else { HandleError("Error during CryptHashData."); } //------------------------------------------------------------------- // 验证数字签名 if(CryptVerifySignature( hHash, pbSignature, dwSigLen, hPubKey, szDescription, 0)) { printf("此数字签名已经被验证./n"); } else { printf("签名未生效!/n"); } //------------------------------------------------------------------- // 释放内存 if(pbSignature) free(pbSignature); if(hHash) CryptDestroyHash(hHash); if(hProv) CryptReleaseContext(hProv, 0); } // End of main //获取加密提供者句柄 HCRYPTPROV GetCryptProv() { HCRYPTPROV hCryptProv; // 加密服务提供者句柄 //获取加密提供者句柄 if(CryptAcquireContext( &hCryptProv, // 加密服务提供者句柄 NULL, // 密钥容器名,这里使用登陆用户名 MS_DEF_PROV, // 加密服务提供者,"Microsoft Base Cryptographic Provider v1.0" PROV_RSA_FULL, // 加密服务提供者类型,可以提供加密和签名等功能 0)) // 标志 { printf("加密服务提供者句柄获取成功!/n"); } else { //重新建立一个新的密钥集 if(!CryptAcquireContext(&hCryptProv, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET)) { HandleError("重新建立一个新的密钥集出错!"); } } return hCryptProv; } // HandleError:错误处理函数,打印错误信息,并退出程序 void HandleError(char *s) { printf("程序执行发生错误!/n"); printf("%s/n",s); printf("错误代码为: %x./n",GetLastError()); printf("程序终止执行!/n"); exit(1); }

 

本文作者专著《Visual C++2010开发权威指南》即将推出,敬请关注,Visual C++2010最近技术,Windows7开发最新技术!

你可能感兴趣的:(算法,加密,解密,vc++,encoding,2010)