我们平时进行数字签名时一般使用的是下面的四行代码:
//签名算法包括哈希算法,HashAlgWithSignAlg
Signature signature=Signature.getInstance("SHA256withRSA");
//用于签名的初始化 可用PrivateKey也可用X509Certificate
signature.initSign(privatekey);
signature.update(msg.getBytes());//传入消息明文
byte[] sig=signature.sign()//签名数据
在第四步中,sign()我们一般的认识(以RSA为例):
先对明文做HASH计算,然后用私钥直接对HASH值加密。最近才发现不是那么简单,需要对HASH后的数据进行BER编码再加密。
在有些场景中,产生明文和签名计算是分离的,但是直接传明文又涉及隐私安全,只会传输hash值,因此会有hash签的需求。
而根据上面那张图中体现的流程,得到hash值和最终得到签名值之间还有许多步骤。
一、先以RSA为例:
1.DER encode 添加OID 先上结论(规则比较难懂)
常见的HASH算法在用于RSA签名时的BER数据编码格式为:
MD2 |
1.2.840.113549.2.2 |
30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 02 05 00 04 10 || H. |
MD4 |
1.2.840.113549.2.4 |
30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 04 05 00 04 10 || H. |
MD5 |
1.2.840.113549.2.5 |
30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 05 05 00 04 10 || H |
SHA1 |
1.3.14.3.2.26 |
30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14 || H |
SHA224 |
2.16.840.1.101.3.4.2.4 不确定是否这个OID |
30 2D 30 0d 06 09 60 86 48 01 65 03 04 02 04 05 00 04 1C || H |
SHA256 |
2.16.840.1.101.3.4.2.1 |
30 31 30 0d 06 09 60 86 48 01 65 03 04 02 0105 00 04 20 || H |
SHA384 |
2.16.840.1.101.3.4.2.2 |
30 41 30 0d 06 09 60 86 48 01 65 03 04 02 0205 00 04 30 || H |
SHA512 |
2.16.840.1.101.3.4.2.3 |
30 51 30 0d 06 09 60 86 48 01 65 03 04 02 0305 00 04 40 || H |
SM3 |
1.2.156.197.1.401 |
30 30 30 0c 06 08 2a 81 1C 81 45 01 83 78 05 00 04 20 || H.(待验证---一般用SM3的是SM2,SM2也是256位的所以又不需要填充) |
明文:11 22 33 44 55
通过调用.NET的RSA签名接口,产生基于MD5的签名后数据:56 E1 5E 29 84 D6 BC FB 87 7F 55 93 B4 E1 F3 75 2C 64 A5 BC 04 3A D7 0A DB 84 AD 8B 9C 4D D8 E6 8A 56 85 7B 2C 5E 50 E5 81 EB DC 40 D8 9A 29 64 54 19 5B F0 2B 77 D3 DB CF A2 17 BF 33 3F 19 19 B0 FF 36 53 D3 C2 36 1D 90 43 27 2C 0F 54 34 54 F7 E8 D2 09 75 E4 F1 A0 8B F5 38 EA 66 D6 53 14 E4 C5 B6 5A C7 74 52 6E 0A 16 C6 9B B7 81 0B 06 61 8A E7 41 BB 97 E6 EE 3E 6A 1C 7A E6 32 18 60
用公钥对上面的数据解密后得到:30 20 30 0C 06 08 2A 86 48 86 F7 0D 02 05 05 00 04 10 28 3D 4F EA 5D DE D5 9C F8 37 D3 04 73 28 F5 AF
这是一段TLV格式的数据,解析后
TAG |
名称 |
长度 |
值 |
||
30 |
Sequence组合类型 |
20 |
|||
30 |
Sequence组合类型 |
0C |
|||
06 |
对象标识ObjectID |
08 |
2A 86 48 86 F7 0D 02 05 |
||
05 |
空类型 |
00 |
|||
04 |
字符串类型 |
10 |
28 3D 4F EA 5D DE D5 9C F8 37 D3 04 73 28 F5 AF |
可以看到28 3D 4F EA 5D DE D5 9C F8 37 D3 04 73 28 F5 AF正好就是明文数据11 22 33 44 55的MD5值。
那么上面这段数据的其它内容表示什么意思呢?
这里使用的编码方法是BER(Basic Encoding Rule),BER的数据都是TLV格式的,每种TAG的定义如下:
0x01:BOOL
0x02:INT,整型
0x04:OCTSTR,字符串类型
0x05:NULL,空类型
0x06:OBJID,对象标识ObjectID(在这里就是对应的HASH算法的OID编码)
0x0A:ENUM
0x30:SEQ,Sequence组合类型
0x31:SETOF
0x40:IPADDR
0x41:COUNTER
0x42:GAUGE
0x43:TIMETICKS
0x44:OPAQUE
也就是说,每次基于不同的HASH算法对不同的数据进行签名时,构造的这一段BER数据的基本格式是固定不变的,只是HASH算法的OID和哈希值会变而已。
下面讲一下HASH算法的OID是怎么编码的。
每个算法的OID都是固定的一串十进制数据,是国际权威组织定的。比如MD5的OID 是 1.2.840.113549.2.5 ,表示为"iso(1) member-body (2) US (840) rsadsi(113549) digestAlgorithm (2) md5 (5)", 所以当解码程序看到这个OID时,就知道是MD5散列.
对OID的编码规则如下:前两部分如果定义为x.y, 那么它们将合成一个字40*x + y, 其余部分单独作为一个字节进行编码。每个字首先被分割为最少数量的没有头零数字的7位数字.这些数字以big-endian格式进行组织,并且一个接一个地组合成字节. 除了编码的最后一个字节外,其他所有字节的最高位(位8)都为1。举例: 30331 = 1 * 128^2 + 108 * 128 + 123 分割成7位数字(0x80)后为{1,108,123}设置最高位后变成{129,236,123}.如果该字只有一个7位数字,那么最高为0。
规则不太好懂,还是以MD5举例:
一、将1.2.840.113549.2.5转换成字数组 {42, 840, 113549, 2, 5}(因为前两部分定义为1.2,那么合成一个字40*1+2=42)
二、将每个字分割为带有最高位的7位数字。
42=42,只有一个7位数字,那么最高为0,结果为{0x2A}
840= 6*128^1+72,除最后一个字节外,其他字节的BIT8都置1,结果为{0x86,0x48}
113549=6*128^2+119*128^1+13,除最后一个字节外,其他字节的BIT8都置1,结果为{0x86,0xF7,0x0D}
2=2, 只有一个7位数字,那么最高为0,结果为{0x02}
5=5, 只有一个7位数字,那么最高为0,结果为{0x05}
最终结果为{{0x2A},{0x86,0x48},{0x86,0xF7,0x0D},{0x02},{0x05}}
三、加上TAG和LEN,得到OID编码为 0x06 08 2A 86 48 86 F7 0D 02 05
下面讲Padding:
1.为什么要Padding:
哈希算法 | MD2 | MD5 | SHA1 | SHA256 | SHA384 | SHA512 |
输出长度(位) | 128 | 128 | 160 | 256 | 384 | 512 |
签名算法 | RSA1024 | RSA2048 |
需要位数 | 1024 | 2048 |
2.Padding的方式
RSA加密常用的填充方式有下面3种:
1.RSA_PKCS1_PADDING 填充模式,最常用的模式
要求:
输入:必须 比 RSA 钥模长(modulus) 短至少11个字节, 也就是 RSA_size(rsa) – 11
如果输入的明文过长,必须切割, 然后填充
输出:和modulus一样长
根据这个要求,对于512bit的密钥, block length = 512/8 – 11 = 53 字节
2.RSA_PKCS1_OAEP_PADDING
输入:RSA_size(rsa) – 41
输出:和modulus一样长
3.for RSA_NO_PADDING 不填充
输入:可以和RSA钥模长一样长,如果输入的明文过长,必须切割, 然后填充
输出:和modulus一样长
如果是公钥加密信息(forEncryption=true),密钥长度为1024位,那么输出的密文块长度为128个字节,输入的明文块长度为127-10,即输入的明文块最大是117位,如果输入的明文块小于117位,比如输入的明文块长度为64位,那么会对这个明文块进行补位,在明文块前添加一位的0x02字节(代表公钥加密)然后后面的52位为随机的字节,在补位的最后一位,{即52(117-64-1),从零开始的},添加一位的字节0x00,在补位的后面添加实际的明文块。
这样做的目的就是使得明文块转化成与module差不多的大整数。
如果是私钥加密(forPrivateKey=true),密钥长度为1024位,那么输出 的密文块长度也是128字节,输入的明文块的长度为127-10,即输入的明文块最大是117位,如果输入的明文块小于117位,比如输入的明文块长度为64位,那么对这个明文块进行补位,在明文块前添加一位的0x01字节(代表私钥加密),然后在后面的52位为字节0xff,在最后一位{即52(117-64-1),从零开始),添加一位的字节0x00,在补位的后面添加时间的明文块。
二、SM2的hash签:
SM3的输出位数为256位,符合SM2签名的要求因此不需要进行Padding,但是由于SM2是国密局对ECC的改版,SM2签名的hash值 不是原文的hash值.
以下是SM3WithSM2的签名流程:
设待签名的消息为M,为了获取消息M的数字签名(r,s),作为签名者的用户A应实现以下运算步
骤:
A1:置M=ZA ∥ M;
A2:计算e = Hv(M),按本文本第1部分4.2.3和4.2.2给出的细节将e的数据类型转换为整数;
A3:用随机数发生器产生随机数k ∈[1,n-1];
A4:计算椭圆曲线点(x1,y1)=[k]G,按本文本第1部分4.2.7给出的细节将x1的数据类型转换为整
数;
A5:计算r=(e+x1) modn,若r=0或r+k=n则返回A3;
A6:计算s = ((1 + dA)−1 · (k − r · dA)) modn,若s=0则返回A3;
A7:按本文本第1部分4.2.1给出的细节将r、s的数据类型转换为字节串,消息M 的签名为(r,s)。
其中ZA=H256(ENTLA ∥ IDA ∥ a ∥ b ∥ xG ∥yG ∥ xA ∥ yA)。
IDA:用户A具有长度为entlenA比特的可辨别标识
ENTLA :由整数entlenA转换而成的两个字节
a,b:椭圆曲线方程参数a,b转换为的比特串
xG、yG :椭圆曲线方程参数基点G的x,y转换为的比特串
xA、yA:公钥P(xA,yA)的xA,yA转换为的比特串
可以看到SM2签名计算hash值时是需要公钥的,并且需要两次hash,第一次hash必须为消息摘要长度为256比特的密码杂凑函数H256 第二次则不限长度
不过官方推荐直接使用SM3(你懂的)。
参考链接:1.https://www.cnblogs.com/lzl-sml/p/3501447.html
2.https://www.cnblogs.com/jintianhu/p/5051169.html
3.http://blog.csdn.net/qq_21794823/article/details/53084585