1 前言
最近在玩CTF,还没入门,做题的时候遇到一道RSA的题,给了一个pubkey.pem文件和一个flag.enc文件。之前对于rsa的认识停留在ssh的时候生成公钥和私钥文件,但是文件里面具体内容没有关注过,所以打开pubkey.pem文件后不知道怎么处理。
看了网上都是直接用openssl工具直接解,但是这对我理解无益,因此想深入了解一下。bing一下之后知道.pem文件是PEM编码的文件,PEM实际上就是把DER编码的文件的二进制内容用base64编码一下,然后加上-----BEGIN label-----这样的头和-----END label-----这样的尾,中间则是DER文件的Base64编码。
在PEM转DER和DER解码过程中遇到一些问题,部分问题查了很多博客都没解释清楚,研究了很久。现将其整理至此,供大家参考。
2 PEM与DER转换
在写本篇博客的时候突然发现了 这篇博客讲得很详细,记录一下。
2.1 PEM转DER格式
先将 PEM 文件里面首尾的 “----BEGIN xxx----” 和 “----END xxx----” 两行去掉,再将内容合并为一行(去掉换行符),最后将内容进行 Base64 解码,最后结果就是 DER 格式。
2.2 DER转PEM格式
先将 DER 二进制内容进行 Base64 编码,再按每行 64 个字节进行切分,最后在切分后的内容前后加上 “----BEGIN xxx----” 和 “----END xxx----”。
3 DER解码
DER(Distinguished Encoding Rules,可辨别编码规则)是BER(Basic Encoding Rules,基本编码规则)的一个子集。前段时间在研究SNMP协议,BER再熟悉不过了,所以对DER解码一下子来了劲。
对pubkey.pem进行base64解码转换为DER编码后得到
303C300D06092A864886F70D0101010500032B003028022100C2636AE5C3D8E43FFB97AB09028F1AAC6C0BF6CD3D70EBCA281BFFE97FBE30DD0203010001
3.1 初步解码
按BER编码规则进行解码,得到的结果是
DER编码 ; 解码结果(字节数为16进制)
30 3C ; SEQUENCE (3C Bytes)
| 30 0D ; SEQUENCE (D Bytes)
| | | 06 09 ; OBJECT_ID (9 Bytes)
| | | 2A 86 48 86 F7 0D 01 01 01 ; 1.2.840.113549.1.1.1(OID编码规则请自行搜索)
| | 05 00 ; NULL (0 Bytes)
| 03 2B ; BIT_STRING (2B Bytes)
| 00
| 30 28 ; SEQUENCE (28 Bytes)
| 02 21 ; INTEGER (21 Bytes)
| | 00
| | C2 63 6A E5 C3 D8 E4 3F FB 97 AB 09 02 8F 1A AC
| | 6C 0B F6 CD 3D 70 EB CA 28 1B FF E9 7F BE 30 DD
| 02 03 ; INTEGER (3 Bytes)
| 01 00 01
查一下这个OID(1.2.840.113549.1.1.1),结果如下图所示,可以看到这个OID其实就是表明这是一个RSAES-PKCS1-v1_5加密机制。
那另外两个整数(00 C2 63 6A E5 C3 D8 E4 3F FB 97 AB 09 02 8F 1A AC 6C 0B F6 CD 3D 70 EB CA 28 1B FF E9 7F BE 30 DD
和01 00 01
)是什么呢?
这就涉及到PKCS的规范了。
3.2 DER文件结构
PKCS已经发布了15个标准,从PKCS#1到PKCS#15,想具体了解可以看对应的RFC。查了相关的RFC,发现这篇博客总结得不错,借鉴了一下。
PKCS#1规定的RSA公钥文件格式为
-----BEGIN RSA PUBLIC KEY-----
BASE64 ENCODED DATA
-----END RSA PUBLIC KEY-----
其base64编码数据解码对应的DER编码解码后的格式为
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
由于 RSA 并非专门在 X509 和 SSL/TLS 中使用,因此 PKCS#8 中提供了一种更通用的密钥格式用以标识公钥的类型并包含相关数据。
PKCS#8规定的公钥文件格式为
-----BEGIN PUBLIC KEY-----
BASE64 ENCODED DATA
-----END PUBLIC KEY-----
其base64编码数据解码对应的DER编码解码后的格式为
PublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
PublicKey BIT STRING
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
3.3 解码
至此,可以根据RSA公钥文件来确定DER编码解码后的格式了。3.1中的解码结果为
DER编码 ; 解码结果(字节数为16进制)
30 3C ; SEQUENCE (3C Bytes)
| 30 0D ; SEQUENCE (D Bytes)
| | | 06 09 ; OBJECT_ID (9 Bytes)
| | | 2A 86 48 86 F7 0D 01 01 01 ; 1.2.840.113549.1.1.1(algorithm)
| | 05 00 ; NULL (0 Bytes, parameters)
| 03 2B ; BIT_STRING (2B Bytes, PublicKey)
| 00
| 30 28 ; SEQUENCE (28 Bytes)
| 02 21 ; INTEGER (21 Bytes, modulus, n)
| | 00
| | C2 63 6A E5 C3 D8 E4 3F FB 97 AB 09 02 8F 1A AC
| | 6C 0B F6 CD 3D 70 EB CA 28 1B FF E9 7F BE 30 DD
| 02 03 ; INTEGER (3 Bytes, publicExponent, e)
| 01 00 01
关于BIT_STRING和modulus部分数据以0x00开头的说明
上面的解码过程中可以看到BIT_STRING和modulus的content字段都是以0x00开头(highlight部分),看了网上很多博客都没有解释清楚,甚至还有说错了误导人的。
03 2B 00 30 28 02 21 00 C2 63 6A E5 C3 D8 E4 3F FB 97 AB 09 02 8F 1A AC 6C 0B F6 CD 3D 70 EB CA 28 1B FF E9 7F BE 30 DD 02 03 01 00 01
本着求知的精神,我去看了一下标准X.690 : Information technology - ASN.1 encoding rules: Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER)。关于这两个0x00,标准里写得非常清楚。下面给大家解释一下。
- 第一个0x00
标准中8.6.2.2原话是
The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit, the number of
unused bits in the final subsequent octet. The number shall be in the range zero to seven.
意思是BIT_STRING的content字段第一个字节需要指明这个bit string最后补了几个零(规定bit string按8位一组,最后一组不到8位则在末尾补齐0),也就是说,这个字节取值范围为0~7。因为后面都是整数,所以不需要补0,因此BIT_STRING的content字段第一个字节为0x00。
- 第二个0x00
标准中8.3.3原话是
The contents octets shall be a two’s complement binary number equal to the integer value, and consisting of bits 8
to 1 of the first octet, followed by bits 8 to 1 of the second octet, followed by bits 8 to 1 of each octet in turn up to and including
the last octet of the contents octets.
也就是说,整数用二进制补码表示。modulus值为C2636AE5C3D8E43FFB97AB09028F1AAC6C0BF6CD3D70EBCA281BFFE97FBE30DD
,这在RSA算法中其实是一个无符号整数,但是如果以二进制补码来看这是一个负数,因此需要在前面加上0x00以保证这是一个正整数。
Ref
1.https://blog.csdn.net/ttyy1112/article/details/107064407
2.https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem
3.https://www.itu.int/rec/T-REC-X.690-202102-I/en
4.https://blog.csdn.net/fengshenyun/article/details/124596279