目录
大家好,这里是快乐的肌肉。
今天聊聊关于ECC密钥格式的一些小知识。
1.问题现象
Muscle v01完善ECC签名验签算法时,需要对公私钥进行解读,发现了很有意思的现象。
使用python dump出来的私钥private和公钥(x,y)数据如下:
但用Openssl进行prikey.pem解读,得到的数据如下:
在上图中,我们可以发现EC的私钥pem文件里包含了私钥和公钥的数据。
这是第一个问题:为什么EC私钥pem里会有公钥的内容?
接着看里面具体内容,发现了Openssl读出来的私钥数据长度为33Bytes,可我曲线选的是NIST P-256,它比python库 dump出来的数据在最高位多了一个字节0x00;同时公钥数据也多了一个字节0x04。
这是第二个问题:这里多出来的一个byte是干什么的呢?
要找答案,还是需要从标准开始入手。
2.EC密钥结构文档解读
2.1 EC私钥pem里有公钥的内容?
废话不多说,直接上查到关于密钥结构的相关协议:
- RFC 5915:Elliptic Curve Private Key Structure
- RFC 3447:Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.1
- RFC 5480:Elliptic Curve Cryptography Subject Public Key Information
- RFC 5208:Public-Key Cryptography Standards (PKCS) #8:Private-Key Information Syntax Specification Version 1.2
- SEC 1: Elliptic Curve Cryptography
- RFC 4648:The Base16, Base32, and Base64 Data Encodings
首先我们从尊贵的RFC 5915开始,EC 私钥信息格式使用ASN.1类型,如下图:
其中,
- version:指定ec 私钥结构的语法版本,对于当前版本使用整数“1”来表示;
- privateKey:私钥内容,类型是OCTEX STRING(字节串类型);但我们知道EC私钥是一个[1,N-1]的大整数,所以就有标准RFC3447来实现 Integer-to-Octet-String- Primitive ;
- parameters:指定私钥关联的椭圆曲线参数,这些参数信息在RFC 5480中找寻;
- publicKey:包含与私钥相关的椭圆曲线公钥,同样的,公钥格式在RFC 5480中定义;虽然我们看到上面该项是一个OPTIONAL可选项,但该标准推荐在实现时尽量包含该项:原因在于给定私钥和曲线参数,公钥是可以很容易计算,因此也方便了用户使用。
那么我们来看一个pem用ASN.1描述应该长什么样子,如下图:
可以看到,该格式与上述描述非常一致,包含了4个元素:version =1, privateKey = C5.....,parameters = NIST P-256,publicKey = bit string 。
所以,第一个问题就迎刃而解了,我使用的这个pyca/cryptography在生成ecc密钥对时默认把publicKey填充了。
2.2 EC公私钥多出一个Byte?
首先我们看私钥hex数据为:
C5...以后是实际密钥数据,0x00是从哪里来的?
首先根据 RFC 5915描述,privateKey有标准RFC3447来实现 Integer-to-Octet-String的转换。如下图所示:
但是这里没有找到任何首字节为0的描述,为此转向SEC1 ECC里寻找相关描述:
这里面有个很关键的信息是要求整数转字节串时输入必须为非负整数,那么在ASN.1里面非负整数是如何表述的?
整数值编码为TLV(Tag、Length、Value)三元组,其中Tag为02表示整数,L表示长度,V很关键,如果整数是整数,且最高位设置为1,就必须添加前导字节0x00,表示该数是一个整数,如上图私钥为0xC5...,转为二进制显示b1100 0101,因此需要添加0x00。
为了验证这个想法,又重新生成了多个密钥,最终效果可以看到,生成私钥密钥为0x39....,转为二进制b0011 1001,通过Openssl读取的就没有前导字节0x00,因为最高位为0,如下图:
这是私钥的解析,感觉基本可以结题。
但是对于公钥来说,始终是有一个前导字节0x04,这是干啥的呢?
根据RFC 5480 第二章 Subject Public Key Information Fields的描述,我们可以看到该公钥格式为:
第一个元素为算法标识符,这个很容易识别:
第二个元素为公钥,具体内容如下:
在从bit串转到字节串后,第一个字节应该指定key是否被压缩,0x04表示未压缩,0x02、0x03表示压缩,因此我们可以看到转为字节串后,都有0x04这个前导字节。
OpenSSL诚不欺我,我们再具体来看看 :
上图一行为15个bytes,总计65Bytes: 520bits,除开前导字节0x04,剩下64bytes平均分给坐标(x,y)作为公钥,如下图所示:
3.小结
基本上EC密钥格式问题到这告一段落,最终在Muscle v01呈现的效果如下:
接下来就继续完善导入PEM、输入P\X\Y、不同曲线的签名验签算法等功能。