一、加密算法
1、概述
数据加密的基本过程就是对原来为明文的文件或数据按某种算法进行处理,使其成为不可读的一段代码为“密文”,使其只能在输入相应的密钥之后才能显示出原容,通过这样的途径来达到保护数据不被非法人窃取、阅读的目的。 该过程的逆过程为解密,即将该编码信息转化为其原来数据的过程。
在如今的信息安全领域,有各种各样的加密算法凝聚了计算机科学家门的智慧。从宏观上来看,这些加密算法可以归结为三大类:对称加密算法、非对称加密算法、哈希算法。
2、对称加密和非对称加密
对称加密和非对称加密是两种常用的加密算法。
对称加密(Symmetric Encryption)使用相同的密钥来进行加密和解密。常见的对称加密算法有:DES(Data Encryption Standard)
、3DES(Triple DES)
、AES(Advanced Encryption Standard)
和RC4(Rivest Cipher 4)
等。在对称加密中,发送者和接收者需要事先共享同一个密钥,并且使用该密钥进行加密和解密操作。对称加密算法的优点是加解密速度快,适合对大量数据进行加密处理。但是,对称加密的缺点是密钥管理较为困难,因为发送者和接收者需要事先共享密钥,并确保密钥的安全传递。
非对称加密(Asymmetric Encryption)使用一对不同但相关的密钥:公钥(Public Key)和私钥(Private Key)。公钥可以被公开传播,而私钥则必须保持私密。常见的非对称加密算法有:RSA(Rivest, Shamir, Adleman)
、DSA(Digital Signature Algorithm)
、ECC(Elliptic Curve Cryptography)
等。在非对称加密中,公钥用于加密数据,而私钥用于解密数据或者进行数字签名。非对称加密算法的优点是密钥管理较为方便,不需要事先共享密钥,并且可以实现数字签名和身份验证等功能。但是,非对称加密的缺点是加解密速度较慢,不适合对大量数据进行加密处理。
区别:
- 密钥数量:对称加密使用相同的密钥进行加密和解密,而非对称加密使用不同的密钥进行加密和解密,因此非对称加密需要的密钥数量是对称加密的两倍。
- 密钥管理:对称加密需要事先共享密钥,并确保密钥的安全传递,而非对称加密的公钥可以公开传播,私钥需要保持私密,密钥管理较为方便。
- 加解密速度:对称加密的加解密速度通常较快,适合对大量数据进行加密处理,而非对称加密的加解密速度较慢。
- 功能:非对称加密可以实现数字签名、身份验证等功能,而对称加密只能实现简单的加密和解密操作。
- 安全性:非对称加密相对于对称加密具有更高的安全性,因为私钥需要保持私密,不需要在网络中传播,而对称加密的密钥在传播过程中容易被窃取。
4、非对称加密算法
RSA
、DSA
、ECC
都是公钥加密算法和数字签名算法,用于实现信息的加密和数字签名。但其基本原理、安全性和性能等方面存在区别。在选择加密算法和数字签名算法时,应根据具体的安全需求、性能要求和资源限制进行合理选择。它们的主要区别如下:
-
RSA(Rivest, Shamir, Adleman)
:原理:RSA是一种基于大数分解难题的公钥加密算法。它使用一对密钥,包括公钥和私钥,其中公钥用于加密数据,私钥用于解密数据。RSA的安全性基于大数分解的困难性,即将一个大数分解成其素因数的困难性。
区别:RSA是一种广泛使用的公钥加密算法,应用广泛,但其加密和签名速度较慢,尤其在处理大数据时性能可能较低。 -
DSA(Digital Signature Algorithm)
:原理:
DSA
是一种基于离散对数问题的数字签名算法。它使用一对密钥,包括私钥和公钥,其中私钥用于生成数字签名,公钥用于验证数字签名。DSA的安全性基于离散对数问题的困难性,即在离散对数问题上计算的困难性。
区别:DSA
主要用于数字签名,不适用于加密数据。DSA
的签名速度相对较快,但其密钥长度较长,需要较大的存储空间,且不支持数据加密。 -
ECC(Elliptic Curve Cryptography)
:原理:
ECC
是一种基于椭圆曲线数学问题的公钥加密和数字签名算法。与RSA
和DSA
相比,ECC
使用更短的密钥长度,提供相当于更长密钥长度的安全性。这使得ECC
在资源有限的环境中具有优势,例如移动设备和物联网设备。
区别:ECC
使用较短的密钥长度,因此在相同安全性要求下,ECC
的加密和签名速度较快,且占用较少的存储空间。然而,ECC
的实现较为复杂,需要对椭圆曲线的选择和参数进行谨慎管理。
5、对称加密算法
DES(Data Encryption Standard)
、3DES(Triple Data Encryption Standard)
和AES(Advanced Encryption Standard)
都是对称加密算法,用于保护数据的机密性。但其密钥长度、安全性和性能等方面存在区别。AES
目前被广泛应用于各种安全领域,而DES
和3DES
则逐渐被淘汰或仅在特定场景中使用。在选择加密算法时,应根据具体的安全需求和性能要求进行权衡和选择。它们的原理和区别如下:
-
DES(Data Encryption Standard)
:原理:
DES
是一种对称加密算法,使用56位密钥对64位的数据块进行加密。它使用Feistel
网络结构,包括16轮的加密和解密运算,每轮包括数据块的分割、替代和置换操作。
区别:DES
的密钥长度较短,只有56位,因此存在被暴力破解的风险。DES
已被认为不够安全,不适合用于现代安全要求较高的应用。 -
3DES(Triple Data Encryption Standard)
:原理:
3DES
是对DES
进行了改进的算法,采用了多次DES
的操作来增加安全性。3DES
使用3个56位的密钥对数据进行3次加密和3次解密运算,提供更高的安全性。
区别:3DES
的密钥长度较长,达到168位,相较于DES
更为安全。然而,3DES
的加密速度较慢,因为需要进行3次加密和3次解密运算,导致性能较低。 -
AES(Advanced Encryption Standard)
:原理:
AES
是一种对称加密算法,使用固定长度的密钥(128位、192位或256位)对数据进行加密和解密。AES
使用代替、置换和混淆等操作来实现高强度的加密。
区别:AES
的密钥长度可以选择128位、192位或256位,相较于DES
和3DES
更为安全。AES
的加密速度较快,因为它使用了高度优化的算法,适用于各种安全应用。
二、SM算法
SM系列国密算法是由中国国家密码管理局于2007年颁布的一组密码算法标准,是中国自主研发的一种新一代密码技术。SM系列算法分为SM1
、SM2
、SM3
、SM4
、SM7
、SM9
,分别用于对称加密、公钥密码学、哈希算法和消息认证码。
其中SM1
、SM4
、SM7
、祖冲之密码(ZUC
)是对称算法;SM2
、SM9
是非对称算法;SM3
是哈希算法。SM2算法是国密椭圆曲线密码算法,类似于RSA、ECC; SM3算法是国密Hash算法,类似于SHA256; SM4算法是国密分组密码算法,类似于AES。
-
SM1是一种对称加密算法,主要用于加密小数据量。它的密钥长度为128位,分组长度为128位,采用分组密码的加密方式,即将明文分为若干个长度相同的分组,每个分组进行加密运算,最后合并为密文。
-
SM2是一种公钥加密算法,用于实现机密通信和数字签名。SM2采用椭圆曲线密码学,其私钥长度为256位,公钥长度为512位,可以提供与1024位RSA算法相当的安全性。
-
SM3是一种哈希算法,可用于数字签名、消息摘要等领域。SM3采用
Merkle–Damgård
结构,消息长度可以达到2^64-1比特,安全性可达到256位。 -
SM4是一种对称加密分组密码算法,用于加密大数据量。它的密钥长度为128位,分组长度为128位,具有高效性和安全性。
-
SM7是一种分组密码算法,分组长度为128比特,密钥长度为128比特。SM7适用于非接触式IC卡,应用包括身份识别类应用(门禁卡、工作证、参赛证),票务类应用(大型赛事门票、展会门票),支付与通卡类应用(积分消费卡、校园一卡通、企业一卡通等)。
-
SM9标识密码算法
为了降低公开密钥系统中密钥和证书管理的复杂性,以色列科学家、RSA算法发明人之一Adi Shamir在1984年提出了标识密码(Identity-Based Cryptography)的理念。标识密码将用户的标识(如邮件地址、手机号码、QQ号码等)作为公钥,省略了交换数字证书和公钥过程,使得安全系统变得易于部署和管理,非常适合端对端离线安全通讯、云端数据加密、基于属性加密、基于策略加密的各种场合。2008年标识密码算法正式获得国家密码管理局颁发的商密算法型号:SM9(商密九号算法),为我国标识密码技术的应用奠定了坚实的基础。
SM9算法不需要申请数字证书,适用于互联网应用的各种新兴应用的安全保障。如基于云技术的密码服务、电子邮件安全、智能终端保护、物联网安全、云存储安全等等。这些安全应用可采用手机号码或邮件地址作为公钥,实现数据加密、身份认证、通话加密、通道加密等安全应用,并具有使用方便,易于部署的特点,从而开启了普及密码算法的大门。
-
ZUC祖冲之算法:祖冲之序列密码算法是中国自主研究的流密码算法,是运用于移动通信4G网络中的国际标准密码算法,该算法包括祖冲之算法(ZUC)、加密算法(128-EEA3)和完整性算法(128-EIA3)三个部分。目前已有对ZUC算法的优化实现,有专门针对128-EEA3和128-EIA3的硬件实现与优化。
三、混合加密
由于以上加密算法都有各自的缺点(RSA加密速度慢、AES密钥存储问题、MD5加密不可逆),因此实际应用时常将几种加密算法混合使用。
例如:RSA+AES:
采用RSA加密AES的密钥,采用AES对数据进行加密,这样集成了两种加密算法的优点,既保证了数据加密的速度,又实现了安全方便的密钥管理。
那么,采用多少位的密钥合适呢?一般来讲密钥长度越长,安全性越高,但是加密速度越慢。所以密钥长度也要合理的选择,一般RSA建议采用1024位的数字,AES建议采用128位即可。
四、MD5算法
1、概述
MD5消息摘要算法,属Hash算法一类。MD5算法对输入任意长度的消息进行运行,产生一个128位的消息摘要(32位的数字字母混合码)。
MD5具有不可逆特点,相同数据的MD5值肯定一样,不同数据的MD5值不一样
一个MD5理论上的确是可能对应无数多个原文的,因为MD5是有限多个的而原文可以是无数多个。比如主流使用的MD5将任意长度的“字节串映射为一个128bit的大整数。也就是一共有2128种可能,大概是3.4*1038,这个数字是有限多个的,而但是世界上可以被用来加密的原文则会有无数的可能性
虽说MD5有不可逆的特点,但是由于某些MD5激活成功教程网站,专门用来查询MD5码,其通过把常用的密码先MD5处理,并将数据存储起来,然后跟需要查询的MD5结果匹配,这时就有可能通过匹配的MD5得到明文,所以有些简单的MD5码是反查到加密前原文的。为了让MD5码更加安全,涌现了很多其他方法,如加盐。 盐要足够长足够乱 得到的MD5码就很难查到。
MD5的性质:
- 压缩性:任意长度的数据,算出的MD5值长度都是固定的(相当于超损压缩)。
- 容易计算:从原数据计算出MD5值很容易。
- 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
- 弱抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
- 强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。
MD5用途:
-
防止被篡改:
- 比如发送一个电子文档,发送前,我先得到MD5的输出结果a。然后在对方收到电子文档后,对方也得到一个MD5的输出结果b。如果a与b一样就代表中途未被篡改。
- 比如我提供文件下载,为了防止不法分子在安装程序中添加木马,我可以在网站上公布由安装文件得到的MD5输出结果。
- SVN在检测文件是否在CheckOut后被修改过,也是用到了MD5.
-
防止直接看到明文:
现在很多网站在数据库存储用户的密码的时候都是存储用户密码的MD5值。这样就算不法分子得到数据库的用户密码的MD5值,也无法知道用户的密码。(比如在UNIX系统中用户的密码就是以MD5(或其它类似的算法)经加密后存储在文件系统中。当用户登录的时候,系统把用户输入的密码计算成MD5值,然后再去和保存在文件系统中的MD5值进行比较,进而确定输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这不但可以避免用户的密码被具有系统管理员权限的用户知道,而且还在一定程度上增加了密码被激活成功教程的难度。)
-
防止抵赖(数字签名):
这需要一个第三方认证机构。例如A写了一个文件,认证机构对此文件用MD5算法产生摘要信息并做好记录。若以后A说这文件不是他写的,权威机构只需对此文件重新产生摘要信息,然后跟记录在册的摘要信息进行比对,相同的话,就证明是A写的了。这就是所谓的“数字签名”。
2、原理
MD5算法的原理可简要的叙述为:MD5码以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。
1、数据填充
对消息进行数据填充,使消息的长度对512取模得448,设消息长度为X,即满足X mod 512=448。根据此公式得出需要填充的数据长度。
填充方法:在消息后面进行填充,填充第一位为1,其余为0。
(此时消息长度为N*512+448)
2、添加消息长度
在第一步结果之后再填充上原消息的长度,可用来进行的存储长度为64位。如果消息长度大于264,则只使用其低64位的值,即(消息长度 对 264取模)。
在此步骤进行完毕后,最终消息长度就是512的整数倍。
(此时消息长度为(N+1)*512 )
3、数据处理
首先需要用到4个常数(四个32位变量初始化,经过研究所得,为固定值):
A = 0×01234567
B = 0×89ABCDEF
C = 0xFEDCBA98
D = 0×76543210
它们称为链接变量(chaining variable),上述四个值为标准的幻数的(物理顺序)
如果在程序中(小端模式)定义应该是:
A = 0x67452301
B = 0xEFCDAB89
C = 0x98BADCFE
D = 0x10325476
然后需要用到4个非线性函数(&是与, |是或, ~是非, ^是异或)
F(X,Y,Z)=(X & Y) | ((~X) & Z);
G(X,Y,Z)=(X & Z) | (Y & (~Z));
H(X,Y,Z)=X ^ Y ^ Z;
I(X,Y,Z)=Y ^ (X | (~Z));
然后是4种操作
FF(a,b,c,d,Mi,s,tj) 表示 a=b+((a+(F(b,c,d)+Mi+tj)<<< s)
GG(a,b,c,d,Mi,s,tj) 表示 a=b+((a+(G(b,c,d)+Mi+tj)<<< s)
HH(a,b,c,d,Mi,s,tj) 表示 a=b+((a+(H(b,c,d)+Mi+tj)<<< s)
II(a,b,c,d,Mi,s,tj) 表示 a=b+((a+(I(b,c,d)+Mi+tj)<<< s)
其中Mi表示消息的第i个子分组(从0到15,共16个),<<< s表示循环左移s位
常数tj为: 在第j步中,tj是4294967296*abs(sin(j))的整数部分,i的单位是弧度。(4294967296是2的32次方)
亦可用 0x100000000UL * abs(sin((double)j)) 计算
x循环左移s位:( s << x ) | ( s >> (32 – x) )
4.MD5运算:
由类似的64次循环构成,分成4轮,每轮16次。每轮使用FF,GG,HH,II中的一种操作;
一轮中,a,b,c,d的使用顺序轮转;
例如第一轮:
第一次计算 FF(a,b,c,d,M0,s,t1)
a = a+(F(b,c,d)+M0+t1);
a = ( s <<a ) | ( s >> (32 – a) );
a = a + b;
第二次计算 FF(b,c,d,a,M1,s,t2)
b = b+(F(c,d,a)+M1+t2);
b = ( s <<b ) | ( s >> (32 – b) );
b = b + c;
这4轮共64步是:
(初次使用的a,b,c,d为A,B,C,D的值,而Mi,s,tj根据下面的数值进行使用,可认为是常量,)
第一轮:
FF(a,b,c,d,M0,7,0xd76aa478) FF(d,a,b,c,M1,12,0xe8c7b756) FF(c,d,a,b,M2,17,0×242070db) FF(b,c,d,a,M3,22,0xc1bdceee) FF(a,b,c,d,M4,7,0xf57c0faf) FF(d,a,b,c,M5,12,0×4787c62a) FF(c,d,a,b,M6,17,0xa8304613) FF(b,c,d,a,M7,22,0xfd469501) FF(a,b,c,d,M8,7,0×698098d8) FF(d,a,b,c,M9,12,0×8b44f7af) FF(c,d,a,b,M10,17,0xffff5bb1) FF(b,c,d,a,M11,22,0×895cd7be) FF(a,b,c,d,M12,7,0×6b901122) FF(d,a,b,c,M13,12,0xfd987193) FF(c,d,a,b,M14,17,0xa679438e) FF(b,c,d,a,M15,22,0×49b40821)
第二轮
GG(a,b,c,d,M1,5,0xf61e2562) GG(d,a,b,c,M6,9,0xc040b340) GG(c,d,a,b,M11,14,0×265e5a51) GG(b,c,d,a,M0,20,0xe9b6c7aa) GG(a,b,c,d,M5,5,0xd62f105d) GG(d,a,b,c,M10,9,0×02441453) GG(c,d,a,b,M15,14,0xd8a1e681) GG(b,c,d,a,M4,20,0xe7d3fbc8) GG(a,b,c,d,M9,5,0×21e1cde6) GG(d,a,b,c,M14,9,0xc33707d6) GG(c,d,a,b,M3,14,0xf4d50d87) GG(b,c,d,a,M8,20,0×455a14ed) GG(a,b,c,d,M13,5,0xa9e3e905) GG(d,a,b,c,M2,9,0xfcefa3f8) GG(c,d,a,b,M7,14,0×676f02d9) GG(b,c,d,a,M12,20,0×8d2a4c8a)
第三轮
HH(a,b,c,d,M5,4,0xfffa3942) HH(d,a,b,c,M8,11,0×8771f681) HH(c,d,a,b,M11,16,0×6d9d6122) HH(b,c,d,a,M14,23,0xfde5380c) HH(a,b,c,d,M1,4,0xa4beea44) HH(d,a,b,c,M4,11,0×4bdecfa9) HH(c,d,a,b,M7,16,0xf6bb4b60) HH(b,c,d,a,M10,23,0xbebfbc70) HH(a,b,c,d,M13,4,0×289b7ec6) HH(d,a,b,c,M0,11,0xeaa127fa) HH(c,d,a,b,M3,16,0xd4ef3085) HH(b,c,d,a,M6,23,0×04881d05) HH(a,b,c,d,M9,4,0xd9d4d039) HH(d,a,b,c,M12,11,0xe6db99e5) HH(c,d,a,b,M15,16,0×1fa27cf8) HH(b,c,d,a,M2,23,0xc4ac5665)
第四轮
II(a,b,c,d,M0,6,0xf4292244) II(d,a,b,c,M7,10,0×432aff97) II(c,d,a,b,M14,15,0xab9423a7) II(b,c,d,a,M5,21,0xfc93a039) II(a,b,c,d,M12,6,0×655b59c3) II(d,a,b,c,M3,10,0×8f0ccc92) II(c,d,a,b,M10,15,0xffeff47d) II(b,c,d,a,M1,21,0×85845dd1) II(a,b,c,d,M8,6,0×6fa87e4f) II(d,a,b,c,M15,10,0xfe2ce6e0) II(c,d,a,b,M6,15,0xa3014314) II(b,c,d,a,M13,21,0×4e0811a1) II(a,b,c,d,M4,6,0xf7537e82) II(d,a,b,c,M11,10,0xbd3af235) II(c,d,a,b,M2,15,0×2ad7d2bb) II(b,c,d,a,M9,21,0xeb86d391)
消息分以512位为一分组进行处理,每一个分组进行上述4轮共64次计算后,将A,B,C,D分别加上计算得到的a,b,c,d。当做新的A,B,C,D,并将这4个变量赋值给a,b,c,d再进行下一分组的运算。由于填充后的消息长度为(N+1)*512,则共需计算N+1个分组。计算所有数据分组后,这4个变量为最后的结果,即MD5值。
3、实现
-
实现一:
-
MD5.h
#ifndef _MD5H_ #define _MD5H_ #include <math.h> #include <Windows.h> #include <iostream> #include <string.h> #include <stdlib.h> void ROL(unsigned int &s, unsigned short cx); //32位数循环左移实现函数 void ltob(unsigned int &i); //B\L互转,接受UINT类型 unsigned int* MD5(const char* mStr); //MD5加密函数,并执行数据填充 unsigned int* MD5_2(const char* mStr); //MD5加密函数,并执行数据填充,更优 #endif
-
MD5.cpp
#include "MD5.h" //4组计算函数 inline unsigned int F(unsigned int X, unsigned int Y, unsigned int Z) { return (X & Y) | ((~X) & Z); } inline unsigned int G(unsigned int X, unsigned int Y, unsigned int Z) { return (X & Z) | (Y & (~Z)); } inline unsigned int H(unsigned int X, unsigned int Y, unsigned int Z) { return X ^ Y ^ Z; } inline unsigned int I(unsigned int X, unsigned int Y, unsigned int Z) { return Y ^ (X | (~Z)); } //32位数循环左移(或称右移)实现函数 void ROL(unsigned int &s, unsigned short cx) { if (cx > 32)cx %= 32; s = (s << cx) | (s >> (32 - cx)); return; } //B\L互转,接收UINT类型 void ltob(unsigned int &i) { unsigned int tmp = i;//保存副本 byte *psour = (byte*)&tmp, *pdes = (byte*)&i; pdes += 3;//调整指针,准备左右调转 for (short j = 3; j >= 0; --j) { CopyMemory(pdes - j, psour + j, 1); } return; } //MD5循环计算函数,label=第几轮循环(1<=label<=4),lGroup数组=4个种子副本,M=数据(16组32位数指针) void AccLoop(unsigned short label, unsigned int *lGroup, void *M) { unsigned int *i1, *i2, *i3, *i4, TAcc, tmpi = 0; //定义:4个指针; T表累加器; 局部变量 typedef unsigned int(*clac)(unsigned int X, unsigned int Y, unsigned int Z); //定义函数类型 //循环左移-位数表 const unsigned int rolarray[4][4] = { { 7, 12, 17, 22 }, { 5, 9, 14, 20 }, { 4, 11, 16, 23 }, { 6, 10, 15, 21 } }; //数据坐标表 const unsigned short mN[4][16] = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, { 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12 }, { 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2 }, { 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 } }; const unsigned int *pM = static_cast<unsigned int*>(M);//转换类型为32位的Uint TAcc = ((label - 1) * 16) + 1; clac clacArr[4] = { F, G, H, I }; //定义并初始化计算函数指针数组 //一轮循环开始(16组->16次) for (short i = 0; i < 16; ++i) { /*进行指针自变换*/ i1 = lGroup + ((0 + i) % 4); i2 = lGroup + ((1 + i) % 4); i3 = lGroup + ((2 + i) % 4); i4 = lGroup + ((3 + i) % 4); if(0 == i%2) { //计算开始: A+F(B,C,D)+M[i]+T[i+1] tmpi = (*i1 + clacArr[label - 1](*i2, *i3, *i4) + pM[(mN[label - 1][i])] + (unsigned int)(0x100000000UL * abs(sin((double)(TAcc + i))))); //循环左移 ROL(tmpi, rolarray[label - 1][i % 4]); //相加并赋值到下一个种子 *i1 = *i2 + tmpi; } else { tmpi = (*i3 + clacArr[label - 1](*i4, *i1, *i2) + pM[(mN[label - 1][i])] + (unsigned int)(0x100000000UL * abs(sin((double)(TAcc + i))))); //循环左移 ROL(tmpi, rolarray[label - 1][i % 4]); //相加并赋值到下一个种子 *i3 = *i4 + tmpi; } } return; } //加密函数 unsigned int* MD5(const char* mStr) { //计算缓冲区长度,并进行数据填充 unsigned int mLen = strlen(mStr); if (mLen <= 0) { return 0; } unsigned int FillSize = 448 - ((mLen * 8) % 512); //计算需填充的bit数 unsigned int FSbyte = FillSize / 8; //以字节表示的填充数 //预留512-448=64bit,填充原消息的长度 unsigned int BuffLen = mLen + 8 + FSbyte; //缓冲区长度 unsigned char *md5Buff = new unsigned char[BuffLen]; //分配缓冲区 CopyMemory(md5Buff, mStr, mLen); //复制字符串到缓冲区 //数据填充 md5Buff[mLen] = 0x80; //第一个bit填充1 ZeroMemory(&md5Buff[mLen + 1], FSbyte - 1); //其它bit填充0 unsigned long long lenBit = mLen * 8ULL; //计算字符串长度,准备填充后64bit CopyMemory(&md5Buff[mLen + FSbyte], &lenBit, 8); //数据运算 unsigned int LoopNumber = BuffLen / 64; //以16个字为一分组,计算分组数量 unsigned int A = 0x67452301, B = 0xEFCDAB89, C = 0x98BADCFE, D = 0x10325476;//初始4个种子,小端类型 unsigned int *lGroup = new unsigned int[4]; lGroup[0] = A; lGroup[1] = B; lGroup[2] = C; lGroup[3] = D; for (unsigned int Bcount = 0; Bcount < LoopNumber; ++Bcount) //分组大循环开始 { //进入4次计算的小循环,共4*16次 for (unsigned short Lcount = 0; Lcount < 4;) { AccLoop(++Lcount, lGroup, &md5Buff[Bcount * 64]); } //数据相加作为下一轮的种子或者最终输出 A = (lGroup[0] += A); B = (lGroup[1] += B); C = (lGroup[2] += C); D = (lGroup[3] += D); } //转换内存中的布局后才能正常显示 ltob(lGroup[0]); ltob(lGroup[1]); ltob(lGroup[2]); ltob(lGroup[3]); delete[] md5Buff; return lGroup; } /* MD5循环计算函数,label=第几轮循环(1<=label<=4),lGroup数组=4个种子副本,M=数据(16组32位数指针) 种子数组排列方式: --A--D--C--B--,即 lGroup[0]=A; lGroup[1]=D; lGroup[2]=C; lGroup[3]=B; */ void AccLoop_2(unsigned short label, unsigned int *lGroup, void *M) { unsigned int *i1, *i2, *i3, *i4, TAcc, tmpi = 0; //定义:4个指针; T表累加器; 局部变量 typedef unsigned int(*clac)(unsigned int X, unsigned int Y, unsigned int Z); //定义函数类型 const unsigned int rolarray[4][4] = { { 7, 12, 17, 22 }, { 5, 9, 14, 20 }, { 4, 11, 16, 23 }, { 6, 10, 15, 21 } };//循环左移-位数表 const unsigned short mN[4][16] = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, { 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12 }, { 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2 }, { 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 } };//数据坐标表 const unsigned int *pM = static_cast<unsigned int*>(M);//转换类型为32位的Uint TAcc = ((label - 1) * 16) + 1; //根据第几轮循环初始化T表累加器 clac clacArr[4] = { F, G, H, I }; //定义并初始化计算函数指针数组 /*一轮循环开始(16组->16次)*/ for (short i = 0; i < 16; ++i) { /*进行指针自变换*/ i1 = lGroup + ((0 + i) % 4); i2 = lGroup + ((3 + i) % 4); i3 = lGroup + ((2 + i) % 4); i4 = lGroup + ((1 + i) % 4); /*第一步计算开始: A+F(B,C,D)+M[i]+T[i+1] 注:第一步中直接计算T表*/ tmpi = (*i1 + clacArr[label - 1](*i2, *i3, *i4) + pM[(mN[label - 1][i])] + (unsigned int)(0x100000000UL * abs(sin((double)(TAcc + i))))); ROL(tmpi, rolarray[label - 1][i % 4]);//第二步:循环左移 *i1 = *i2 + tmpi;//第三步:相加并赋值到种子 } return; } /*接口函数,并执行数据填充*/ unsigned int* MD5_2(const char* mStr) { unsigned int mLen = strlen(mStr); //计算字符串长度 if (mLen < 0) return 0; unsigned int FillSize = 448 - ((mLen * 8) % 512); //计算需填充的bit数 unsigned int FSbyte = FillSize / 8; //以字节表示的填充数 unsigned int BuffLen = mLen + 8 + FSbyte; //缓冲区长度或者说填充后的长度 unsigned char *md5Buff = new unsigned char[BuffLen]; //分配缓冲区 CopyMemory(md5Buff, mStr, mLen); //复制字符串到缓冲区 /*数据填充开始*/ md5Buff[mLen] = 0x80; //第一个bit填充1 ZeroMemory(&md5Buff[mLen + 1], FSbyte - 1); //其它bit填充0,另一可用函数为FillMemory unsigned long long lenBit = mLen * 8ULL; //计算字符串长度,准备填充 CopyMemory(&md5Buff[mLen + FSbyte], &lenBit, 8); //填充长度 /*数据填充结束*/ /*运算开始*/ unsigned int LoopNumber = BuffLen / 64; //以16个字为一分组,计算分组数量 unsigned int A = 0x67452301, B = 0x0EFCDAB89, C = 0x98BADCFE, D = 0x10325476;//初始4个种子,小端类型 //unsigned int *lGroup = new unsigned int[4]{ A, D, C, B}; //种子副本数组,并作为返回值返回 unsigned int *lGroup = new unsigned int[4]; lGroup[0] = A; lGroup[1] = D; lGroup[2] = C; lGroup[3] = B; for (unsigned int Bcount = 0; Bcount < LoopNumber; ++Bcount) //分组大循环开始 { /*进入4次计算的小循环*/ for (unsigned short Lcount = 0; Lcount < 4;) { AccLoop_2(++Lcount, lGroup, &md5Buff[Bcount * 64]); } /*数据相加作为下一轮的种子或者最终输出*/ A = (lGroup[0] += A); B = (lGroup[3] += B); C = (lGroup[2] += C); D = (lGroup[1] += D); } /*转换内存中的布局后才能正常显示*/ ltob(lGroup[0]); ltob(lGroup[1]); ltob(lGroup[2]); ltob(lGroup[3]); delete[] md5Buff; //清除内存并返回 return lGroup; }
-
main.cpp
#include "MD5.h" int main() { char tmpstr[256], buf[4][10]; std::cout << "请输入要加密的字符串:"; std::cin >> tmpstr; //char buf[4][10]; //char tmpstr[] ="admin"; //MD5值:21232F297A57A5A743894A0E4A801FC3 unsigned int* tmpGroup = MD5(tmpstr); sprintf_s(buf[0], "%8X", tmpGroup[0]); //A sprintf_s(buf[1], "%8X", tmpGroup[1]); //B sprintf_s(buf[2], "%8X", tmpGroup[2]); //C sprintf_s(buf[3], "%8X", tmpGroup[3]); //D std::cout <<"1-MD5:"<< buf[0] << buf[1] << buf[2] << buf[3] << std::endl; tmpGroup = MD5_2(tmpstr); sprintf_s(buf[0], "%8X", tmpGroup[0]); //A sprintf_s(buf[1], "%8X", tmpGroup[3]); //B sprintf_s(buf[2], "%8X", tmpGroup[2]); //C sprintf_s(buf[3], "%8X", tmpGroup[1]); //D std::cout <<"2-MD5:"<< buf[0] << buf[1] << buf[2] << buf[3] << std::endl; delete[] tmpGroup; return 0; }
-
-
实现二:
#include <string.h> #include <math.h> #include <stdio.h> #include <iostream> #include <string.h> #include <stdlib.h> /*********************************** * 非线性函数 * (&是与,|是或,~是非,^是异或) * * 这些函数是这样设计的: * 如果X、Y和Z的对应位是独立和均匀的, * 那么结果的每一位也应是独立和均匀的。 * * 函数F是按逐位方式操作:如果X,那么Y,否则Z。 * 函数H是逐位奇偶操作符 **********************************/ #define F(x,y,z) ((x & y) | (~x & z)) #define G(x,y,z) ((x & z) | (y & ~z)) #define H(x,y,z) (x^y^z) #define I(x,y,z) (y ^ (x | ~z)) /************************************** *向左换移(右环移)n个单位 * ************************************/ #define ROTATE_LEFT(x,n) ((x << n) | (x >> (32-n))) /**************************************************** * 每次操作对a,b,c和d中的其中三个作一次非线性函数运算 * F(b,c,d) G(b,c,d) H(b,c,d) I(b,c,d) * * 然后将所得结果加上 第四个变量(a), * F(b,c,d)+a * * 文本的一个子分组(x) * F(b,c,d)+a+x * * 和一个常数(ac)。 * F(b,c,d)+a+x+ac * * 再将所得结果向右环移一个不定的数(s), * ROTATE_LEFT( F(b,c,d)+a+x+ac , s ) * * 并加上a,b,c或d中之一(b)。 * ROTATE_LEFT( F(b,c,d)+a+x+ac , s )+b * * 最后用该结果取代a,b,c或d中之一(a)。 * a=ROTATE_LEFT( F(b,c,d)+a+x+ac , s )+b * * ***************************************************/ #define FF(a,b,c,d,x,s,ac) { a += F(b,c,d) + x + ac; a = ROTATE_LEFT(a,s); a += b; } #define GG(a,b,c,d,x,s,ac) { a += G(b,c,d) + x + ac; a = ROTATE_LEFT(a,s); a += b; } #define HH(a,b,c,d,x,s,ac) { a += H(b,c,d) + x + ac; a = ROTATE_LEFT(a,s); a += b; } #define II(a,b,c,d,x,s,ac) { a += I(b,c,d) + x + ac; a = ROTATE_LEFT(a,s); a += b; } //储存一个MD5 text信息 typedef struct { unsigned int count[2]; //记录当前状态,其数据位数 unsigned int state[4]; //4个数,一共32位 记录用于保存对512bits信息加密的中间结果或者最终结果 unsigned char buffer[64]; //一共64字节,512位 }MD5_CTX; //第一位1 其后若干个0,用于MD5Final函数时的补足 unsigned char PADDING[]={0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //函数声明 void MD5Init(MD5_CTX *context); void MD5Update(MD5_CTX *context,unsigned char *input,unsigned int inputlen); void MD5Final(MD5_CTX *context,unsigned char digest[16]); void MD5Transform(unsigned int state[4],unsigned char block[64]); void MD5Encode(unsigned char *output,unsigned int *input,unsigned int len); void MD5Decode(unsigned int *output,unsigned char *input,unsigned int len); /************************ * 函数功能:初始化一个MD5 text * 函数参数:MD5 text 指针 * ***********************/ //初始化 void MD5Init(MD5_CTX *context) { context->count[0] = 0; context->count[1] = 0; //分别赋固定值 context->state[0] = 0x67452301; context->state[1] = 0xEFCDAB89; context->state[2] = 0x98BADCFE; context->state[3] = 0x10325476; } /************************************************ * 函数功能:对一个MD5 text,把输入的数据进行分组,并进行加密 * 未用到的数据把其储存在MD5 text中。 * * 参数分析: * MD5_CTX *context :一个MD5 text * unsigned char *input :新添加的数据 * unsigned int inputlen :新添加数据的长度(字节) * ***********************************************/ void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen) { unsigned int i = 0,index = 0,partlen = 0; //index:当前状态的位数对64取余,其单位是字节 //也可以写作: index=(context->count[0]/8)%64 index = (context->count[0] >> 3) & 0x3F; //partlen:可以补齐64字节的字节数 partlen = 64 - index; //下面代码是解决一个unsignde int 无法储存极大数据导致溢出的问题 //当前位数加上新添加的位数,由于inputlen是以字节为单位,所以其转换为位数 //相当于context->count[0] += inputlen*8; context->count[0] += inputlen << 3; //当其出现溢出的情况时,通过以下操作把两个16位的数连在一块,生成一个 //32位的二进制数串,从而扩大其储存范围 if(context->count[0] < (inputlen << 3)) { context->count[1]++; } //该语句可替换为 context->count[1]+=(inputlen<<3)>>32; //便于理解 context->count[1] += inputlen >> 29; //当其输入字节数的大于其可以补足64字节的字节数,进行补足 if(inputlen >= partlen) { //向buffer中补足partlen个字节,使其到达64字节 memcpy(&context->buffer[index], input, partlen); //buffer达到64字节512位,则把其作为一组进行运算 MD5Transform(context->state, context->buffer); //如果输入的数据还可以组成多个64字节,则把其可以组成 //的作为若干组进行运算 for(i = partlen;i+64 <= inputlen;i+=64) MD5Transform(context->state, &input[i]); //恢复0值,照应 下面 把输入 剩余字节(不能组成64字节组) 储存的操作 index = 0; } //否则,把输入的数据按顺序放在原来数据后面 else { i = 0; } //放置剩余数据 memcpy(&context->buffer[index],&input[i],inputlen-i); } /************************************************* * 函数功能:对数据进行补足,并加入数据位数信息,并进一步加密 * * 参数分析: * MD5_CTX *context :一个MD5 text * unsigned char digest[16] :储存加密结果的数组 *************************************************/ void MD5Final(MD5_CTX *context,unsigned char digest[16]) { unsigned int index = 0,padlen = 0; //bits: 8个字节,64位 unsigned char bits[8]; //index:对64取余结果 index = (context->count[0] >> 3) & 0x3F; //因为要填充满足使其位长对512求余的结果等于448(56位) //所以当其所剩余的数小于56字节,则填充56-index字节, //否则填充120-index字节 //这里padlen代表其所需填充的字节 padlen = (index < 56)?(56-index):(120-index); //然后,在这个结果后面附加一个以64位二进制表示的填充前数据长度。 //把填充前数据数据长度转换后放到bit字符数组中 MD5Encode(bits,context->count,8); //根据已经存储好的数组PADDING,在信息的后面填充一个1和无数个0, //直到满足上面的条件时才停止用0对信息的填充 //其填充后进行了一系列的加密操作,其定剩余48个字节 MD5Update(context,PADDING,padlen); //在最后添加进8个字节的数据长度信息,最后凑成一组,进行一次加密处理 MD5Update(context,bits,8); //把最终得到的加密信息变成字符输出,共16字节 MD5Encode(digest,context->state,16); } /********************************************************** * 函数功能:利用位操作,按1->4方式把数字分解成字符 * * 参数分析: * unsigned char *output :输出的字符的数组 * unsigned int *input :输入数字的数组 * unsigned int len : 输入数字数组的长度(单位:位) * *********************************************************/ void MD5Encode(unsigned char *output,unsigned int *input,unsigned int len) { unsigned int i = 0,j = 0; while(j < len) { //这里& 0xFF为取后8位 //i代表数字数组下标 //j代表字符数组下标 //把数字的8、8-16、16-24、24-32分别赋值给字符 output[j] = input[i] & 0xFF; output[j+1] = (input[i] >> 8) & 0xFF; output[j+2] = (input[i] >> 16) & 0xFF; output[j+3] = (input[i] >> 24) & 0xFF; i++; j+=4; } } /********************************************************** * 函数功能:利用位操作,按4->1方式把字符合成数字 * * 参数分析: * unsigned int *output :输出的数字的数组 * unsigned char *input :输入字符的数组 * unsigned int len : 输入字符的长度 (单位:位) * *********************************************************/ void MD5Decode(unsigned int *output,unsigned char *input,unsigned int len) { unsigned int i = 0,j = 0; while(j < len) { //利用位操作,把四个单位为1字节的字符,合成一个单位为4字节的数字 //因为FF GG HH II和非线性函数都只能对数字进行处理 //第一个字符占前8位,第二个占8-16位,第三个占16-24位,第四个占 //24-32位。 //i代表数字数组下标 //j代表字符数组下标 output[i] = (input[j]) | (input[j+1] << 8) | (input[j+2] << 16) | (input[j+3] << 24); i++; j+=4; } } /************************************************************** * 函数功能:对512位的block数据进行加密,并把加密结果存入state数组中 * 对512位信息(即block字符数组)进行一次处理,每次处理包括四轮 *state[4]:md5结构中的state[4],用于保存对512bits信息加密的中间结果或者最终结果 * block[64]:欲加密的512bits信息或其中间数据 ***************************************************************/ void MD5Transform(unsigned int state[4], unsigned char block[64]) { //a b c d继承上一个加密的结果,所以其具有继承性 unsigned int a = state[0]; unsigned int b = state[1]; unsigned int c = state[2]; unsigned int d = state[3]; //这里只需用到16个,我把原来的unsiged int x[64] 改为了 x[16] unsigned int x[16]; //把字符转化成数字,便于运算 MD5Decode(x,block,64); //具体函数方式固定,不再赘述 /*************第一轮******************/ FF(a, b, c, d, x[ 0], 7, 0xd76aa478); FF(d, a, b, c, x[ 1], 12, 0xe8c7b756); FF(c, d, a, b, x[ 2], 17, 0x242070db); FF(b, c, d, a, x[ 3], 22, 0xc1bdceee); FF(a, b, c, d, x[ 4], 7, 0xf57c0faf); FF(d, a, b, c, x[ 5], 12, 0x4787c62a); FF(c, d, a, b, x[ 6], 17, 0xa8304613); FF(b, c, d, a, x[ 7], 22, 0xfd469501); FF(a, b, c, d, x[ 8], 7, 0x698098d8); FF(d, a, b, c, x[ 9], 12, 0x8b44f7af); FF(c, d, a, b, x[10], 17, 0xffff5bb1); FF(b, c, d, a, x[11], 22, 0x895cd7be); FF(a, b, c, d, x[12], 7, 0x6b901122); FF(d, a, b, c, x[13], 12, 0xfd987193); FF(c, d, a, b, x[14], 17, 0xa679438e); FF(b, c, d, a, x[15], 22, 0x49b40821); /*************第二轮*****************/ GG(a, b, c, d, x[ 1], 5, 0xf61e2562); GG(d, a, b, c, x[ 6], 9, 0xc040b340); GG(c, d, a, b, x[11], 14, 0x265e5a51); GG(b, c, d, a, x[ 0], 20, 0xe9b6c7aa); GG(a, b, c, d, x[ 5], 5, 0xd62f105d); GG(d, a, b, c, x[10], 9, 0x2441453); GG(c, d, a, b, x[15], 14, 0xd8a1e681); GG(b, c, d, a, x[ 4], 20, 0xe7d3fbc8); GG(a, b, c, d, x[ 9], 5, 0x21e1cde6); GG(d, a, b, c, x[14], 9, 0xc33707d6); GG(c, d, a, b, x[ 3], 14, 0xf4d50d87); GG(b, c, d, a, x[ 8], 20, 0x455a14ed); GG(a, b, c, d, x[13], 5, 0xa9e3e905); GG(d, a, b, c, x[ 2], 9, 0xfcefa3f8); GG(c, d, a, b, x[ 7], 14, 0x676f02d9); GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); /*************第三轮*****************/ HH(a, b, c, d, x[ 5], 4, 0xfffa3942); HH(d, a, b, c, x[ 8], 11, 0x8771f681); HH(c, d, a, b, x[11], 16, 0x6d9d6122); HH(b, c, d, a, x[14], 23, 0xfde5380c); HH(a, b, c, d, x[ 1], 4, 0xa4beea44); HH(d, a, b, c, x[ 4], 11, 0x4bdecfa9); HH(c, d, a, b, x[ 7], 16, 0xf6bb4b60); HH(b, c, d, a, x[10], 23, 0xbebfbc70); HH(a, b, c, d, x[13], 4, 0x289b7ec6); HH(d, a, b, c, x[ 0], 11, 0xeaa127fa); HH(c, d, a, b, x[ 3], 16, 0xd4ef3085); HH(b, c, d, a, x[ 6], 23, 0x4881d05); HH(a, b, c, d, x[ 9], 4, 0xd9d4d039); HH(d, a, b, c, x[12], 11, 0xe6db99e5); HH(c, d, a, b, x[15], 16, 0x1fa27cf8); HH(b, c, d, a, x[ 2], 23, 0xc4ac5665); /*************第四轮******************/ II(a, b, c, d, x[ 0], 6, 0xf4292244); II(d, a, b, c, x[ 7], 10, 0x432aff97); II(c, d, a, b, x[14], 15, 0xab9423a7); II(b, c, d, a, x[ 5], 21, 0xfc93a039); II(a, b, c, d, x[12], 6, 0x655b59c3); II(d, a, b, c, x[ 3], 10, 0x8f0ccc92); II(c, d, a, b, x[10], 15, 0xffeff47d); II(b, c, d, a, x[ 1], 21, 0x85845dd1); II(a, b, c, d, x[ 8], 6, 0x6fa87e4f); II(d, a, b, c, x[15], 10, 0xfe2ce6e0); II(c, d, a, b, x[ 6], 15, 0xa3014314); II(b, c, d, a, x[13], 21, 0x4e0811a1); II(a, b, c, d, x[ 4], 6, 0xf7537e82); II(d, a, b, c, x[11], 10, 0xbd3af235); II(c, d, a, b, x[ 2], 15, 0x2ad7d2bb); II(b, c, d, a, x[ 9], 21, 0xeb86d391); //更换原来的结果 state[0] += a; state[1] += b; state[2] += c; state[3] += d; } int main(int argc, char *argv[]) { MD5_CTX md5; //定义一个MD5 text MD5Init(&md5);//初始化 int i; //unsigned char encrypt[] ="admin";//要加密内容 //加密结果:21232f297a57a5a743894a0e4a801fc3 unsigned char encrypt[1000];//要加密内容 printf("请输入要加密的字符串:"); gets((char *)encrypt); unsigned char decrypt[16]; //加密结果 MD5Update(&md5, encrypt, strlen((char *)encrypt));//进行初步分组加密 MD5Final(&md5,decrypt); //进行后序的补足,并加密 printf("加密前:%s\n加密后16位:",encrypt); for(i=4;i<12;i++) { printf("%02x",decrypt[i]); } printf("\n加密前:%s\n加密后32位:",encrypt); for(i=0;i<16;i++) { printf("%02x",decrypt[i]); } printf("\n"); return 0; }
五、SM3
六、SM2
1、概述
SM2是非对称加密算法,它是基于椭圆曲线密码(Elliptic Curve Cryptography,简称ECC)的公钥密码算法标准,其秘钥长度256bit,包合数字签名、密钥交换和公钥加密,用于替换RSA、DH、ECDSA、ECDH等国际算法。可以满足电子认证服务系统等应用需求,由国家密码管理局于2010年12月17号发布。
SM2采用的是ECC 256位的一种,其安全度高于RSA 2048位,且运算速度快于RSA。随着密码技术和计算技术的发展,目常用的1024位RSA算法面临严重的安全威胁,我们国家密码管理部门经过研究,决定采用SM2椭圆曲线算法替换RSA算法。SM2算法在安全性、性能上都具有优势。
可先简单了解椭圆曲线数学理论:SM2国密算法/椭圆曲线密码学ECC之数学原理
2、获取公私钥
SM2 算法基础是椭圆曲线,公式:{(x,y) ∉ R² | y² = x³ + ax + b, (4a³ + 27b² ≠ 0)}
-
首选一条椭圆曲线,即固定 a、b 的值,以上图曲线
y² = x³ - x
为例。 -
随机选择一个点 P 为基点,曲线做切线,经过 Q 点,切点 R1。
-
基于 x 轴做 R1 的对称点 R,则 SM2 定义加法为 P + Q = R,这就是椭圆曲线加法。
-
求 2 倍点,当 P = Q 时,即 P + P = R = 2P,则 R 是 P 的 2 倍点。
-
求 3 倍点,3P = P + 2P = P + R,经过 P、R 做直线,交于椭圆曲线点 M1, 基于 x 轴对称点 M 则是 3 倍点,依次类推。
-
求 d 倍点,假设我们同样次数为 d,运算倍点为 Q。
-
d 为私钥,Q 为公钥。所以私钥是一个大整数,公钥是一个点坐标。
上面的几何推理是为了方便理解,实际取值都是在质数有限域上。密码专家们经过推理和运算,已经为我们选择了质数有限域上的最优椭圆曲线,除非有特殊需要,否则不需要自定义曲线。
SM2椭圆曲线公钥密码算法推荐曲线参数:
推荐使用素数域256位椭圆曲线
椭圆曲线方程:y² = x³ + ax + b
曲线参数:
p= FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF
a= FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC
b= 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
n= FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
Gx= 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7
Gy= BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0
3、SM2算法符号定义
A,B:使用公钥密码系统的两个用户。
dA:用户A的私钥。
PA:用户A的公钥。
IDA:用户A的可辨别标识。
ZA:关于用户A的可辨别标识、部分椭圆曲线系统参数和用户A公钥的杂凑值。
Fq:包含q个元素的有限域。
E(Fq): Fq上椭圆曲线E 的所有有理点(包括无穷远点O)组成的集合。
q:有限域Fq中元素的数目。
a,b: Fq中的元素,它们定义Fq上的一条椭圆曲线E。
O:椭圆曲线上的一个特殊点,称为无穷远点或零点,是椭圆曲线加法群的单位元。
G:椭圆曲线的一个基点,其阶为素数。
[k]P:椭圆曲线上点P的k倍点,即, [k]P= P + P + · · · + P(k个P, k是正整数)。
Hv():消息摘要长度为v比特的密码杂凑函数。
M:待签名的消息。
M′:待验证消息。
e:密码杂凑函数作用于消息M的输出值。
e′:密码杂凑函数作用于消息M ′的输出值。
modn:模n运算。
n:基点G的阶(n是#E(Fq)的素因子)。
(r,s):发送的签名。
(r′,s′):收到的签名。
x ∥ y: x与y的拼接,其中x、 y可以是比特串或字节串。
[x,y]:大于或等于x且小于或等于y的整数的集合。
数字签名算法由一个签名者对数据产生数字签名,并由一个验证者验证签名的可靠性。每个签名者有一个公钥和一个私钥,其中私钥用于产生签名,验证者用签名者的公钥验证签名。在签名的生成程之前,要用密码杂凑算法对 M(包含和待签消息 M)进行压缩;在验证过程之前,要用密码杂凑算法对 M’(包含和待验证消息 M’)进行压缩。作为签名者的用户 A 具有长度为entlenA
比特的可辨别标识IDA,记ENTLA
是由整数entlenA
转换而成的两个字节,在本部分规定的椭圆曲线数字签名算法中,签名者和验证者都需要用密码杂凑算法求得用户 A 的杂凑值。按 GB/T32918.1—2016
中4.2.6和4.2.5给出的方法,将椭圆曲线方程参数a、b、G(Gx,Gy)、P(Px,Py),ZA = H256( ENTLA ∥ IDA ∥ a ∥ b ∥ Gx ∥ Gy ∥ Px ∥ Py ) 的数据类型转换为比特串。
4、签名
设待签名的消息为 M,为了获取消息 M 的数字签名(r,s),作为签名者的用户 A 应实现以下运算步骤:
- 计算 e = hash( ZA ∥ M ) => 获取消息散列值
- 用随机数发生器产生随机数k ∈ [1,n-1],n为循环子群的阶 => 同一个消息,每次签名结果不同
- 计算椭圆曲线点P (Px,Py) = kG (Gx,Gy)
- 计算r = ( e + Px ) mod n, 若 r = 0 或者 r + k = n, 继续第2步
- s = ((1 + dA)-1 ∗ (k − r ∗ dA )) mod n,若 s = 0,继续第2步
- (r,s) 为签名信息
5、验签
M’为明文,(r’,s’) 为签名结果,用户公钥P,实现步骤:
- 计算 e’ = hash( ZA ∥ M’ )
- t = ( r’ + s’ ) mod n,若 t=0则验证不通过
- 计算椭圆曲线点F (Fx,Fy) = [s’]G + [t]PA
- R=(e’+Fx) mod n,判断R=r’是否成立,若成立则验证通过
验证原理:
[s’]G + [t]PA = s’G + (r’ + s’)PA
= s’G + (r’ + s’)dAG
= s’G + s’dAG + r’dAG
= (1 + dA)s’G + r’dAG
= (1 + dA)(1 + dA)-1 * (k − r’dA)G + r’dAG
= (k − rdA)G + rdAG
= kG − rdAG + rdAG
= kG = (Px,Py)
6、加密
发送方为A,M为明文,M长度为klen,具体加密步骤:
- 选择随机数k ∈ [1,n-1]
- 计算椭圆曲线点 C1= [k]G = (x1,y1) ,将 (x1,y1) 表示为比特串
- 计算椭圆曲线点 S = [h]PB,h 为余因子,若S为无穷远点,则报错并退出
- 计算椭圆曲线点 ( x2, y2) = [ k ] PB
- 计算 t = KDF ( x2 ∥ y2, klen ) ,KDF是sm2的密钥派生函数,若t为全0的比特串,则继续第一步
- 计算 C2 = M ⊕ t
- 计算 C3 = Hash ( x1 ∥ M ∥ y1 )
- 输出密文 C = C1 ∥ C2 ∥ C3
在SM2算法中,加密后的密文C由三个部分组成:
-
C1:这是一个椭圆曲线上的点,表示加密过程中生成的临时公钥点Q’。这个点的坐标可以用压缩或非压缩编码表示。
-
C2:这是一个椭圆曲线上的点,表示加密后的数据。它与明文数据M进行异或运算后得到。C2 = M ⊕ (Q’ * Pb),其中Pb是接收方的公钥点。
-
C3:这是一个哈希值,表示摘要数据,用于验证密文的完整性。H是对C2点和临时私钥与C1点的运算结果进行哈希运算得到的。
最开始的国密标准的结果是按C1C2C3顺序的,新标准的是按C1C3C2顺序存放的
7、解密
C为密文字符串,klen为密文中C2的长度
- 从 C 里面获取比特串C1,将C1表示为托亚运曲线上的点,验证C1是否满足椭圆曲线,不满足则报错并退出
- 计算椭圆曲线点 S = [ h ] C1, 若S为无穷远点,则报错并退出
- 计算 ( x2, y2) = [ dB ] C1,将坐标 x2, y2表示为比特串
- 计算 t = KDF ( x2 ∥ y2, klen ) ,若t为全0的比特串,则报错并退出
- 从C中取出比特串C2,计算 M’ = C2 ⊕ t
- 计算 u = Hash ( x2 ∥ M‘ ∥ y2 ) ,从C中取出C3,若u ≠ C3,则报错并退出
- 输出铭文 M’
8、密钥交换
密钥交换(Elliptic Curve Diffie-Hellman,ECDH)简单来说,就是A和B通过公开交互一些信息就可以协商出来一个只有他们两个人知道的密钥,别的人通过公开的信息,求解出这个密钥是计算不可行的;这个交换得到的密钥特别适合于用作对称加密的密钥。ECDH的基本原理如下:
- 有限域上的椭圆曲线基点为G
- A的私钥是dA,公钥是 HA = dAG
- B的私钥是dB,公钥是 HB = dBG
- A和B相互交换公钥,他们可以求出相同的密钥S: S = dAHB = dA(dBG) = dB(dAG) = dBHA
一般认为已知椭圆曲线的参数从HA和HB求解出S的难度等同于ECDLP,显然这个问题并不会比ECDLP(椭圆曲线离散对数难题,elliptic curve discrete logarithm problem)更困难,因为求解出ECDLP必然能求出S。
9、Python国密SM2签名及加解密
from gmssl import sm2 as SM2
from gmssl import func as GMFunc
from random import SystemRandom
from base64 import b64encode, b64decode
class CurveFp:
def __init__(self, A, B, P, N, Gx, Gy, name):
self.A = A
self.B = B
self.P = P
self.N = N
self.Gx = Gx
self.Gy = Gy
self.name = name
class SM2Key:
sm2p256v1 = CurveFp(
name="sm2p256v1",
A=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC,
B=0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93,
P=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF,
N=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123,
Gx=0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7,
Gy=0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
)
@staticmethod
def multiply(a, n, N, A, P):
return SM2Key.fromJacobian(SM2Key.jacobianMultiply(SM2Key.toJacobian(a), n, N, A, P), P)
@staticmethod
def add(a, b, A, P):
return SM2Key.fromJacobian(SM2Key.jacobianAdd(SM2Key.toJacobian(a), SM2Key.toJacobian(b), A, P), P)
@staticmethod
def inv(a, n):
if a == 0:
return 0
lm, hm = 1, 0
low, high = a % n, n
while low > 1:
r = high // low
nm, new = hm - lm * r, high - low * r
lm, low, hm, high = nm, new, lm, low
return lm % n
@staticmethod
def toJacobian(Xp_Yp):
Xp, Yp = Xp_Yp
return Xp, Yp, 1
@staticmethod
def fromJacobian(Xp_Yp_Zp, P):
Xp, Yp, Zp = Xp_Yp_Zp
z = SM2Key.inv(Zp, P)
return (Xp * z ** 2) % P, (Yp * z ** 3) % P
@staticmethod
def jacobianDouble(Xp_Yp_Zp, A, P):
Xp, Yp, Zp = Xp_Yp_Zp
if not Yp:
return 0, 0, 0
ysq = (Yp ** 2) % P
S = (4 * Xp * ysq) % P
M = (3 * Xp ** 2 + A * Zp ** 4) % P
nx = (M ** 2 - 2 * S) % P
ny = (M * (S - nx) - 8 * ysq ** 2) % P
nz = (2 * Yp * Zp) % P
return nx, ny, nz
@staticmethod
def jacobianAdd(Xp_Yp_Zp, Xq_Yq_Zq, A, P):
Xp, Yp, Zp = Xp_Yp_Zp
Xq, Yq, Zq = Xq_Yq_Zq
if not Yp:
return Xq, Yq, Zq
if not Yq:
return Xp, Yp, Zp
U1 = (Xp * Zq ** 2) % P
U2 = (Xq * Zp ** 2) % P
S1 = (Yp * Zq ** 3) % P
S2 = (Yq * Zp ** 3) % P
if U1 == U2:
if S1 != S2:
return 0, 0, 1
return SM2Key.jacobianDouble((Xp, Yp, Zp), A, P)
H = U2 - U1
R = S2 - S1
H2 = (H * H) % P
H3 = (H * H2) % P
U1H2 = (U1 * H2) % P
nx = (R ** 2 - H3 - 2 * U1H2) % P
ny = (R * (U1H2 - nx) - S1 * H3) % P
nz = (H * Zp * Zq) % P
return nx, ny, nz
@staticmethod
def jacobianMultiply(Xp_Yp_Zp, n, N, A, P):
Xp, Yp, Zp = Xp_Yp_Zp
if Yp == 0 or n == 0:
return (0, 0, 1)
if n == 1:
return (Xp, Yp, Zp)
if n < 0 or n >= N:
return SM2Key.jacobianMultiply((Xp, Yp, Zp), n % N, N, A, P)
if (n % 2) == 0:
return SM2Key.jacobianDouble(SM2Key.jacobianMultiply((Xp, Yp, Zp), n // 2, N, A, P), A, P)
if (n % 2) == 1:
mv = SM2Key.jacobianMultiply((Xp, Yp, Zp), n // 2, N, A, P)
return SM2Key.jacobianAdd(SM2Key.jacobianDouble(mv, A, P), (Xp, Yp, Zp), A, P)
class PrivateKey:
def __init__(self, curve=SM2Key.sm2p256v1, secret=None):
self.curve = curve
self.secret = secret or SystemRandom().randrange(1, curve.N)
def PublicKey(self):
curve = self.curve
xPublicKey, yPublicKey = SM2Key.multiply((curve.Gx, curve.Gy), self.secret, A=curve.A, P=curve.P, N=curve.N)
return PublicKey(xPublicKey, yPublicKey, curve)
def ToString(self):
return "{}".format(str(hex(self.secret))[2:].zfill(64))
class PublicKey:
def __init__(self, x, y, curve):
self.x = x
self.y = y
self.curve = curve
def ToString(self, compressed=True):
return '04' + {
True: str(hex(self.x))[2:],
False: "{}{}".format(str(hex(self.x))[2:].zfill(64), str(hex(self.y))[2:].zfill(64))
}.get(compressed)
class SM2Util:
def __init__(self, pub_key=None, pri_key=None):
self.pub_key = pub_key
self.pri_key = pri_key
self.sm2 = SM2.CryptSM2(public_key=self.pub_key, private_key=self.pri_key)
def Encrypt(self, data):
info = self.sm2.encrypt(data.encode())
return b64encode(info).decode()
def Decrypt(self, data):
info = b64decode(data.encode())
return self.sm2.decrypt(info).decode()
def Sign(self, data):
random_hex_str = GMFunc.random_hex(self.sm2.para_len)
sign = self.sm2.sign(data.encode(), random_hex_str)
return sign
def Verify(self, data, sign):
return self.sm2.verify(sign, data.encode())
@staticmethod
def GenKeyPair():
pri = PrivateKey()
pub = pri.PublicKey()
return pri.ToString(), pub.ToString(compressed=False)
def main():
"""
主函数
:return:
"""
import random
vs = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+'
data = ''.join([vs[random.randint(0, len(vs) - 1)] for i in range(500)])
print('原数据:{}'.format(data))
e = SM2Util.GenKeyPair()
# e = ('', '')
print('私钥:{} 公钥:{}'.format(e[0], e[1]))
sm2 = SM2Util(pri_key=e[0], pub_key=e[1][2:])
sign = sm2.Sign(data)
print('签名:{} 验签:{}'.format(sign, sm2.Verify(data, sign)))
cipher = sm2.Encrypt(data)
print('加密:{}\n解密:{}'.format(cipher, sm2.Decrypt(cipher)))
if __name__ == '__main__':
main()