为PEM溯源一波~
首先我们要知道PEM文件是什么,是一个以——-BEGIN
而将Base64编码转换成二进制字符串后,这样的二进制表示其实是一串遵循ASN.1的DER编码。
下面进行分析:
先用Pycrypto生成一个PEM文件(以RSA公私钥为例)。
1 | from Crypto.PublicKey import RSA |
得到私钥的PEM文件
1 | -----BEGIN RSA PRIVATE KEY----- |
现在来探索一下它是怎么形成的。
因为使用Pycrypto生成此PEM,我们寻找这个相关文件,使用pip show pycryptodome查找Pycrpto相关路径,进入…/site-packages/Crypto/PublicKey/RSA.py。
代码溯源:
找到关于私钥生成的主要功能在
RsaKey类的export_key函数有用的部分大概长这样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28class RsaKey(object):
... ...
def export_key(self, format='PEM', passphrase=None, pkcs=1,
protection=None, randfunc=None):
... ...
# DER format is always used, even in case of PEM, which simply
# encodes it into BASE64.
if self.has_private():
binary_key = DerSequence([0,
self.n,
self.e,
self.d,
self.p,
self.q,
self.d % (self.p-1),
self.d % (self.q-1),
Integer(self.q).inverse(self.p)
]).encode()
if pkcs == 1:
key_type = 'RSA PRIVATE KEY'
... ...
... ...
if format == 'PEM':
from Crypto.IO import PEM
pem_str = PEM.encode(binary_key, key_type, passphrase, randfunc)
return tobytes(pem_str)
... ...直接看到最后,使用PEM.encode()生成pem_str。
寻找PEM.encode(),位置是
.../site-packages/Crypto/IO/PEM.py的encode函数1
2
3
4
5
6
7
8
9
10
11
12def encode(data, marker, passphrase=None, randfunc=None):
... ...
out = "-----BEGIN %s-----\n" % marker
... ...
# Each BASE64 line can take up to 64 characters (=48 bytes of data)
# b2a_base64 adds a new line character!
chunks = [tostr(b2a_base64(data[i:i + 48]))
for i in range(0, len(data), 48)]
out += "".join(chunks)
out += "-----END %s-----" % marker
return out这个函数的作用就是将每48个bytes编码成一行Base64,加BEGIN和END,但这里出现的data不知道来自哪
寻找data来自哪
由于上面PEM.encode()函数在data位置传递了binary_key参数,而binary_key是由
DerSequence()以[0, n, e, d, ...]的顺序生成的,这样的顺序也恰好是openssl读取RSA私钥时的输出顺序。我们显然不知道
DerSequence()是干嘛的。寻找DerSequence,在
.../site-packages/Crypto/Util/asn1.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class DerSequence(DerObject):
def encode(self):
"""
Return this DER SEQUENCE, fully encoded as abinary string.
Raises:
ValueError: if some elements in the sequence are neither integznor byte strings.
"""
self.payload = b''
for item in self._seq:
if byte_string(item):
self.payload += item
elif _is_number(item):
self.payload += DerInteger(item).encode()
else:
self.payload += item.encode()
return DerObject.encode(self)这里不改变字节串,但通过DerInteger()改变了数字,且最后返回使用未知函数DerObject.encode()
发现未知函数DerInterger,DerObject
寻找DerInterger
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class DerInteger(DerObject):
... ...
def encode(self):
"""Return the DER INTEGER, fully encoded as a
binary string."""
number = self.value
self.payload = b''
while True:
self.payload = bchr(int(number & 255)) + self.payload
if 128 <= number <= 255:
self.payload = bchr(0x00) + self.payload
if -128 <= number <= 255:
break
number >>= 8
return DerObject.encode(self)就是最数字 进行了一个转换,最后使用了DerObject.encode()
这下我们不得不看一下DerObject.encode()是什么了
寻找DerObject
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class DerObject(object):
... ...
def encode(self):
"""Return this DER element, fully encoded as a binary byte string."""
# Concatenate identifier octets, length octets,
# and contents octets
output_payload = self.payload
... ...
return (bchr(self._tag_octet) +
self._definite_form(len(output_payload)) +
output_payload)return了
+ + ,其中 tag是ASN.1的类型标签,length即payload的长度,还发现_definite_form对长度做格式化寻找
_definite_form1
2
3
4
5
6
7
8def _definite_form(length):
"""Build length octets according to BER/DER
definite form.
"""
if length > 127:
encoding = long_to_bytes(length)
return bchr(len(encoding) + 128) + encoding
return bchr(length)这就是一个DER的长度编码方式:
- 短格式:当长度 < 128 时,直接返回
- 长格式:当长度 ≥ 128 时,最高比特位设为 1,剩下的 7 位表示用于存储长度的字节数(即长度值本身占几个字节)
比如0x0100(即长度为256) 需要用 2 个字节存储(因为 1 个字节最大存 255)。
使用长格式将最高位置为 1,剩余 7 位为
0b00000010(即 2,表示后面 2 个字节存长度) → 得到0x82。最终编码为0x820100
完成溯源,开始手撕。
首先需要了解到在ASN.1的类型标签中,0x30是指序列(Sequence),0x02指整数(Integer)等
也就是tag是02,30这些值。
而长度就是按第7步中的编码方式,比如0x8180就被显示成8180
我们再看一眼这个序列的生成规律
1 | RSAPrivateKey ::= SEQUENCE { |
第一步:将Base64转化为二进制字符串
1 | with open('./priv.pem', 'r') as f: |
第二步:分析
首先30就是Sequence的tag,82最高位置1后转换为十六进制为8,2就是说接下来后两个bytes是这个Sequence的长度,即0x025d个bytes。
接着02是整数的tag,01是这个整数占1byte,00是value。后同。
得到
1 | 3082025d # Begin Sequence: len=0x025d |