RSA私钥签名时要基于某个HASH算法,比如MD5或者SHA1等。之前我一直认为签名的过程是:先对明文做HASH计算,然后用私钥直接对HASH值加密。最近才发现不是那么简单,需要对HASH后的数据进行BER编码再加密。
先看一个例子。
公钥模:89 54 E6 61 C1 52 DB ED 07 57 50 04 AD B3 D2 A7 A9 8F E8 D8 20 5B 01 B2 E5 E4 7A 7B EE 80 E3 C0 13 11 D2 F9 AD C3 CC 5F 1D 96 AC B2 AB BE 9C 14 9E 76 31 06 B2 E6 FA 01 52 A7 2E 53 C2 1D 3B 7B 9B 68 05 D2 5E 35 31 98 0E 02 93 E0 D9 0C 38 2D 3D EE 10 E6 87 53 79 DF B2 1E 12 D9 9E EF 89 6D 01 59 0D 13 94 DB 05 B7 09 34 D3 5B AB ED 7C FE 0E BE 87 EE E8 DD 01 39 3A CA 3A A7 17 B8 AA E3
公钥指数:01 00 01
私钥指数:01 FE B1 BA 09 CC E2 54 F7 1E 55 93 3B D2 B8 E4 A6 99 E8 8F FB 28 57 45 FA 00 EF A6 8D 38 62 16 90 30 5A 18 36 65 F9 BA 07 FC 00 56 38 18 74 BB F7 F1 4F 95 01 54 49 9D 6B 4D F2 66 55 13 87 A1 A6 95 74 72 6A D8 3A EA 34 A8 F8 40 5F 27 11 30 4F 96 3A 2E 7B E6 B6 47 3C 3B 4D 24 E8 FA 51 19 59 FB 52 E0 9B D2 24 B3 B5 8A 36 BF 34 20 E9 2A AB 5D 55 9B 60 01 D5 04 81 E8 E7 EC B2 5F 81 41
私钥P:BF 36 08 66 63 74 6A 79 D0 77 64 21 73 6D 1A B9 13 BB 35 13 BE A6 73 84 C8 7D 83 67 BE C2 F5 0C 3A 7F 5F EF 6E 73 E2 BC 31 D2 0C 78 06 D7 38 85 7E F5 06 40 62 A6 1D 53 CC 97 34 30 58 EE E2 05
私钥Q:B7 DD 46 99 58 B2 52 4B 87 FB E1 F1 09 44 AB 9A AD D1 93 90 9C 40 E0 2F 36 63 F4 7F 49 CB 36 E3 2C DA 85 5C 6E CE 41 AC CB 09 6C 27 B6 44 2B D8 26 5F D5 63 DF 2A C8 60 57 3B 23 13 2B 5F 65 C7
私钥DP:A6 EF C4 9B A7 9E DE CA E5 2F 27 33 71 33 C3 0D EC 65 18 2C D9 D9 36 A7 A9 E6 B2 CF E3 A3 10 10 12 0E 5C B2 8C 2B 0E BC 21 7E F2 35 E4 3B 08 74 BC 67 AD 82 8E DD DA 62 EC 0E E2 98 87 3C 60 05
私钥DQ:B6 A0 8B A7 75 7A 6A 53 AB D6 7D 2E 35 CE 87 C5 34 31 9F 29 5C 8A F4 22 F1 1B 87 97 87 6C DA 2F FC 35 71 91 C6 5E 08 CD E1 3E 92 B7 3F 4B A7 61 23 7C BD 30 5E 52 D8 85 19 20 1C 4E C6 1E 13 B1
私钥InvQ:B4 12 D6 05 1C 2C 2B 6F B5 73 99 F3 B7 A7 08 6F A3 E8 2D 6F 33 A6 AE E5 BE 7B 89 86 7F 48 3B DD BC 4A 07 BF A4 A1 BB 96 BD 0E 46 F1 43 FA FB DE A0 1B AB 38 7D 49 59 45 EE 8C F9 3D 89 CF EB AC
明文: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
RSA验签时的步骤:先用公钥解密,解析TLV数据从中得到HASH算法的OID和HASH值,根据OID选择相应的HASH算法对明文进行计算,最后比对HASH值。
常见的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 01 05 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 02 05 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 03 05 00 04 40 || H |
SM3 |
1.2.156.197.1.504 不确定是否这个OID |
30 30 30 0c 06 08 2a 81 1C 81 45 01 83 78 05 00 04 20 || H. |