RSA密钥文件格式
前言
各类证书密钥虽然都是采用RSA算法,但存储的文件格式也是多种多样,常见的有PEM,OpenSSH;而编码格式又有BER,DER;各类名词SSH,SSL,OpenSSH,OpenSSL,PKCS也容易让人混淆;本文将尝试把这些概念一一缕清~~
名词解释
- PKCS:公钥加密标准(Public Key Cryptography Standards, PKCS)
- PKIX:公钥基础设施 (Public-Key Infrastructure X.509)
- ASN.1:一种结构化的描述语言(Abstract Syntax Notation One)
- BER:基本编码规则(Basic Encoding Rules)
- CER:规范编码规则(Canonical Encoding Rules)
- DER:可视化编码规则(Distinguished Encoding Rules)
- XER:XML编码规则(XML Encoding Rules)
- JER:Json编码规则(JSON Encoding Rules)
- PEM:隐私增强的邮件(Privacy Enhanced Mail)
- SSL:安全套接层(Secure Sockets Layer)
- TLS:安全传输层协议(Transport Layer Security)
- SSH:安全命令行链接(Secure Shell
- OpenSSH:SSH协议的免费开源实现(OpenBSD Secure Shell)
- OID:对象标识符(Object Identifier)
格式介绍
简介
- ASN.1 是一种结构化的描述语言,,具有类型(type)和符号(notation);
- BER和DER都是二进制的编码规则,使用《Type,Length,Value》的三元组标识一组数据,使用ASN.1来描述编码;
- DER是BER的子集,具有更多的规则限制;
- DER格式文件是二进制存储,而PEM是文本形式;
- 实际上,PEM就是把DER格式的数据用base64编码后,然后再在头尾各加上一段“-----”开始的标记而已。
ASN.1
ASN.1 和 DER:https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/
ASN.1解码器:https://lapo.it/asn1js/
格式和符号(tag)
ASN.1的数据结构类似C语言的结构体,支持将对象和数组等序列化为ASN.1格式的二进制,并反序列化为变量
// ASN.1
Point ::= SEQUENCE {
x INTEGER,
y INTEGER,
label UTF8String
}
// C语言结构体
struct point {
int x, y;
char label[10];
};
Tag(decimal) | Tag(Hex) | Type | |
2 | 0x02 | INTEGER | 整数 |
3 | 0x03 | BIT STRING | 位串 |
4 | 0x04 | OCTET STRING | 八位字节字符串 |
5 | 0x05 | NULL | 空类型 |
6 | 0x06 | OBJECT IDENTIFIER | 对象标识符 |
12 | 0x00C | UTF8String | UTF8字符串 |
16 | 0x10 (and 0x30)* | SEQUENCE and SEQUENCE OF | 序列 |
17 | 0x11 (and 0x31)* | SET and SET OF | 有序序列 |
19 | 0x13 | PrintableString | 可打印字符串 |
22 | 0x16 | IA5String | IA5字符串 |
23 | 0x17 | UTCTime | UTC时间 |
24 | 0x18 | GeneralizedTime | 广义时间 |
编码
ASN.1 与许多编码相关联:BER、DER、PER、XER,这里以DER编码举例,采用《类型-长度-值》的方式标记,如十六进制 02 03 01 00 01 表示整数65537,第一个02表示类型为整数,03表示长度为3个字节,01 00 01表示十六进制,二进制位0000 0001 0000 0000 0000 1 = 65537
长度字段有两种规格:短格式和长格式
小于等于127的用短格式,即该字节表示实际长度
当长度大于127的时,使用长格式,长格式在下面介绍
Type | Length | Value |
02 | 03 | 01 00 01 |
而作为标记的tag,也有其编码规则,如0x02 = 0000 0010,左数的2bit表示这是一种通用编码
Class | Bit 8 | Bit 7 |
Universal | 0 | 0 |
Application | 0 | 1 |
Context-specific | 1 | 0 |
Private | 1 | 1 |
举一个ASN.1完整的例子,可以点这里查看解码,下面的一串十六进制编码表示了一个坐标(9,9),最开始的30表示接下来是ASN.1序列,06表示这个序列有6个字节;而80表示这是一个具有上下文意义的值,后面的01表示这个值有一个字节,09表示这个值是9;后面的81 01 09含义类似
Base64编码对照表:https://tooltt.com/base64-table/
Point ::= SEQUENCE {
x [APPLICATION 0] INTEGER OPTIONAL,
y [APPLICATION 1] INTEGER OPTIONAL
}
长度
短格式:用一个字节直接表示长度,0~127
当需要表示128以上长度时,长度部分至少由两个字节表示,并且第一个字节的第 8 位(左数第一位)设置为 1。第一个字节的第 7-1 位表示长度字段本身还有n个字节。然后后面的n个字节用来表示总长度。如0x82 =1000 0010,表示后面还有2字节,而这两个字节是09 42,相当于十进制的2370,则表示后面的value是有2370字节
30 82 09 42……
因此可以表示的长度是非常大的,最长时首字节为254 (255 是为将来的扩展保留的),用后续的126个字节表示长度,如果每个字节都是255,则总长度为:[ 2^(1008) ]-1(单位字节)
长度字节起始 | 长度字节截止 | 最大值 | |
短格式 | 0x00 | 0x7F | 127字节 |
长格式 | 0x80 | 0xFE FF FF …… | [ 2^(1008) ]-1 字节 |
ASN.1与DER的应用将在下面的RSA密钥文件中展开
PEM密钥文件(RFC8017,PKCS #1)
PCKS#1标准的RSA密钥文件:https://www.rfc-editor.org/rfc/rfc8017#page-54
PKCS1与PKCS8的小知识:https://www.jianshu.com/p/a428e183e72e
标记介绍
以RFC8017为例,采用的是PKCS #1标准,DER编码格式,下面是ASN.1的tag,分别对应RSA算法的各个参数,可以参考这个
# 公钥
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
# 私钥
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n,模数n
publicExponent INTEGER, -- e,公钥指数
privateExponent INTEGER, -- d,私钥指数
prime1 INTEGER, -- p,大素数1
prime2 INTEGER, -- q,大素数2
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
使用以下命令,通过openssl新建公钥和私钥文件
使用其他命令,其他工具,或者其他版本的openssl生成的密钥文件可能格式不同
我这里默认生成的是PKCS#1
openssl genrsa -out prikey.p1 2048
openssl rsa -in prikey.p1 -pubout -RSAPublicKey_out > pubkey.p1
# openssl版本
[root@localhost tmp]# openssl version -a
OpenSSL 1.0.2k-fips 26 Jan 2017
built on: reproducible build, date unspecified
platform: linux-x86_64
options: bn(64,64) md2(int) rc4(8x,int) des(idx,cisc,16,int) idea(int) blowfish(idx)
compiler: gcc -I. -I.. -I../include -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -DKRB5_MIT -m64 -DL_ENDIAN -Wall -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -Wa,--noexecstack -DPURIFY -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DRC4_ASM -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
OPENSSLDIR: "/etc/pki/tls"
engines: rdrand dynamic
# 查看文件格式
[root@localhost tmp]# file prikey.p1
prikey.p1: PEM RSA private key
[root@localhost tmp]# file pubkey.p1
pubkey.p1: ASCII text
私钥
可以看到生成的私钥文件是PEM格式的,还可以通过前面介绍的ASN.1解码器查看数据的结构,
[root@localhost tmp]# cat prikey.p1
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA4sOucnAjCqCyN9DwTHws7Hnb9v9IK4eQn6SA5IJWqaIjnJl9
vEdQHOYs5fs9PcHw1a3NFIaKLRh8+oA/TDP7ejW9eb8mXj8hLQgQykdsx+K5h1WK
MDFA1024hVigvQ0Gwi+iSQcMsD5EMySX47QulnCclUT2eqR3msbxbhwgt81F+O+W
Nzh/YcNseU09ao4dvSrKFFtR5MrGI3MBo7AaCZi2dVZrY+6yW1aa5LSSwqc9j10w
YWqqkI8tQpNSw67NJln6N6ZPylKT+SGABl2IBZDJaua8D6b18j+74VUKx180KiI+
sxtePcEPwv3YZVj8k9JH2HLSuXQvocLgxJGnpQIDAQABAoIBAQC3892T3/vNJ0br
mIcWxLT8sG4MFFLLXRGpL52PwVvkCC+zXaBuF7uZde9+MS0g6C8iZ/cxa2Kz6mWY
IGk4vAKIxQeLt9C8K6ISEbTtIrvP+tBeRLnxFkNvZREGuzrJocOVy2Zur1VPs3NH
uautgAE68OdU/7ijDMApwrEfjZnM/yV6v0hAtQLVN4KzQJ5/LmbvKQA4lPLB43ag
6/Us+NUGkzKIOiX6TfkD4+ohQDjRexXYhBhNELT/2T7u1yhKsMUEqTzQTcC4KEan
qc2IfEaldEfNR0HYLP+7BHn0xO0q+tZLe6dGwtANLzos+htFKknBm7s4+3qZmj0Z
1AJtZQkBAoGBAPbK4f+idMPrbuIBAaT9FsNGPIRV2/kP8rXevScd1xuanCTCuqXc
F0HX9lkMi4durx+NSDiOm3nGX2wlyxLD+tB2C0WnlBEsCEWY8wjOqPog3kd9eCx6
cMZr5vV/kKhlb3NRjAJmepexMfFvV7L1tpj+cueLFMNPXtTVJXkK90FxAoGBAOs5
goIbR0TXYDuZ95HhOK/IsdV7hVI2EEjMS+NNK/EqW6f3YwSDLjvJU/pmLkX4UznI
3e9cu83N1dlhjcIPffrt1gIBJCIfM0t3snnTexVKeniFQLk6a48KcFOeMdr6I133
pU3B8Ap7sJQC7JVFFDVWhCk2PbQXkIk35Oa+3S91AoGBAMkcX7rQClM14jAuHcbz
r78F138H8LAv/NuUXZQozwhhn+ic/zj198XiVZAPlwyQeAlAOqBMJgrASzZ4PdpW
9mc2Ks1iWFj+BuegUjzVHAJpEnozluC4DnrDp3xbrehscLwKsguqszA2Z7qVv2PI
tDpM8BobzpmyblcGMvlGpGMhAoGBAJWN5xBZz6JRXXoHloRFsgjxjT+yXT276faf
DdDjDAE1cTkF7Qq9bPPOOXqG5PzPufqPX6wERdO//ytK/xpXRz33YY28tACN3csp
u2wBrcwHon5mGpSqNCCp/mjrt78GMt5Xyd36ZGgF5Gr2wPzXAtY2v6iEOA78vITB
6ONcjrzRAoGAZTdGwLWQYqtyZOYpwp6woJBsn4krN6YFmXKFCWUHY7LvqFDwkPBQ
VxmNSY786+LMOCfoTe/bcEMs+Tr4IIHYmVo+R2re2HPCf5BMT0XndNLSA9jOZVlP
lk1GlSCN+BuC6ZowlRlk12KjRnYOvbCIhEURY3lUPZaXhvSnb3ckQDk=
-----END RSA PRIVATE KEY-----
或者通过base64解码器查看,这个私钥文件包含了公钥和私钥,采用的是ASN.1格式
以----分割的中间部分才是base64编码哦!
下面以解码器为参照进行介绍,由于整个密钥文件超过了127字节,因此采用了长格式介绍,0x82表示后面用2个字节表示长度,而整个文件的总长度是:4+1189,前面的4字节是ASN.1的结构,后面的1189是value的长度
版本
标记的顺序为:version, n, e, d, p, q……可以参考上面的私钥标记
关于版本的说明可以参考RFC8017,简单来说,就是默认两个素数的版本就是0,多组素数的版本就是1
version is the version number, for compatibility with future
revisions of this document. It SHALL be 0 for this version of the
document, unless multi-prime is used; in which case, it SHALL be
1.
Version ::= INTEGER { two-prime(0), multi(1) }
(CONSTRAINED BY
{-- version must be multi if otherPrimeInfos present --})
模数(modulus,n)
这个参数就是RSA算法加密/解密时的模数,在文件中也是采用了长格式存储,因此使用两个字节表示长度,2048bit是前面创建文件的时候指定的位数
公钥(publicExponent,e)
同理,02 03 01 00 01就是公钥的65537,实际生产中常用这个值作为公钥的指数,有兴趣的小伙伴可以自行去扩展了解
私钥(privateExponent,d)
素数(prime1,prime2,即p,q)
也可以借助openssl库的命令来解析,可以看到和我们上面借助解码器手动分析的结果是一致的
[root@localhost tmp]# openssl rsa -text -noout < prikey.p1
Private-Key: (2048 bit)
modulus:
00:e2:c3:ae:72:70:23:0a:a0:b2:37:d0:f0:4c:7c:
2c:ec:79:db:f6:ff:48:2b:87:90:9f:a4:80:e4:82:
56:a9:a2:23:9c:99:7d:bc:47:50:1c:e6:2c:e5:fb:
3d:3d:c1:f0:d5:ad:cd:14:86:8a:2d:18:7c:fa:80:
3f:4c:33:fb:7a:35:bd:79:bf:26:5e:3f:21:2d:08:
10:ca:47:6c:c7:e2:b9:87:55:8a:30:31:40:d7:4d:
b8:85:58:a0:bd:0d:06:c2:2f:a2:49:07:0c:b0:3e:
44:33:24:97:e3:b4:2e:96:70:9c:95:44:f6:7a:a4:
77:9a:c6:f1:6e:1c:20:b7:cd:45:f8:ef:96:37:38:
7f:61:c3:6c:79:4d:3d:6a:8e:1d:bd:2a:ca:14:5b:
51:e4:ca:c6:23:73:01:a3:b0:1a:09:98:b6:75:56:
6b:63:ee:b2:5b:56:9a:e4:b4:92:c2:a7:3d:8f:5d:
30:61:6a:aa:90:8f:2d:42:93:52:c3:ae:cd:26:59:
fa:37:a6:4f:ca:52:93:f9:21:80:06:5d:88:05:90:
c9:6a:e6:bc:0f:a6:f5:f2:3f:bb:e1:55:0a:c7:5f:
34:2a:22:3e:b3:1b:5e:3d:c1:0f:c2:fd:d8:65:58:
fc:93:d2:47:d8:72:d2:b9:74:2f:a1:c2:e0:c4:91:
a7:a5
publicExponent: 65537 (0x10001)
privateExponent:
00:b7:f3:dd:93:df:fb:cd:27:46:eb:98:87:16:c4:
b4:fc:b0:6e:0c:14:52:cb:5d:11:a9:2f:9d:8f:c1:
5b:e4:08:2f:b3:5d:a0:6e:17:bb:99:75:ef:7e:31:
2d:20:e8:2f:22:67:f7:31:6b:62:b3:ea:65:98:20:
69:38:bc:02:88:c5:07:8b:b7:d0:bc:2b:a2:12:11:
b4:ed:22:bb:cf:fa:d0:5e:44:b9:f1:16:43:6f:65:
11:06:bb:3a:c9:a1:c3:95:cb:66:6e:af:55:4f:b3:
73:47:b9:ab:ad:80:01:3a:f0:e7:54:ff:b8:a3:0c:
c0:29:c2:b1:1f:8d:99:cc:ff:25:7a:bf:48:40:b5:
02:d5:37:82:b3:40:9e:7f:2e:66:ef:29:00:38:94:
f2:c1:e3:76:a0:eb:f5:2c:f8:d5:06:93:32:88:3a:
25:fa:4d:f9:03:e3:ea:21:40:38:d1:7b:15:d8:84:
18:4d:10:b4:ff:d9:3e:ee:d7:28:4a:b0:c5:04:a9:
3c:d0:4d:c0:b8:28:46:a7:a9:cd:88:7c:46:a5:74:
47:cd:47:41:d8:2c:ff:bb:04:79:f4:c4:ed:2a:fa:
d6:4b:7b:a7:46:c2:d0:0d:2f:3a:2c:fa:1b:45:2a:
49:c1:9b:bb:38:fb:7a:99:9a:3d:19:d4:02:6d:65:
09:01
prime1:
00:f6:ca:e1:ff:a2:74:c3:eb:6e:e2:01:01:a4:fd:
16:c3:46:3c:84:55:db:f9:0f:f2:b5:de:bd:27:1d:
d7:1b:9a:9c:24:c2:ba:a5:dc:17:41:d7:f6:59:0c:
8b:87:6e:af:1f:8d:48:38:8e:9b:79:c6:5f:6c:25:
cb:12:c3:fa:d0:76:0b:45:a7:94:11:2c:08:45:98:
f3:08:ce:a8:fa:20:de:47:7d:78:2c:7a:70:c6:6b:
e6:f5:7f:90:a8:65:6f:73:51:8c:02:66:7a:97:b1:
31:f1:6f:57:b2:f5:b6:98:fe:72:e7:8b:14:c3:4f:
5e:d4:d5:25:79:0a:f7:41:71
prime2:
00:eb:39:82:82:1b:47:44:d7:60:3b:99:f7:91:e1:
38:af:c8:b1:d5:7b:85:52:36:10:48:cc:4b:e3:4d:
2b:f1:2a:5b:a7:f7:63:04:83:2e:3b:c9:53:fa:66:
2e:45:f8:53:39:c8:dd:ef:5c:bb:cd:cd:d5:d9:61:
8d:c2:0f:7d:fa:ed:d6:02:01:24:22:1f:33:4b:77:
b2:79:d3:7b:15:4a:7a:78:85:40:b9:3a:6b:8f:0a:
70:53:9e:31:da:fa:23:5d:f7:a5:4d:c1:f0:0a:7b:
b0:94:02:ec:95:45:14:35:56:84:29:36:3d:b4:17:
90:89:37:e4:e6:be:dd:2f:75
exponent1:
00:c9:1c:5f:ba:d0:0a:53:35:e2:30:2e:1d:c6:f3:
af:bf:05:d7:7f:07:f0:b0:2f:fc:db:94:5d:94:28:
cf:08:61:9f:e8:9c:ff:38:f5:f7:c5:e2:55:90:0f:
97:0c:90:78:09:40:3a:a0:4c:26:0a:c0:4b:36:78:
3d:da:56:f6:67:36:2a:cd:62:58:58:fe:06:e7:a0:
52:3c:d5:1c:02:69:12:7a:33:96:e0:b8:0e:7a:c3:
a7:7c:5b:ad:e8:6c:70:bc:0a:b2:0b:aa:b3:30:36:
67:ba:95:bf:63:c8:b4:3a:4c:f0:1a:1b:ce:99:b2:
6e:57:06:32:f9:46:a4:63:21
exponent2:
00:95:8d:e7:10:59:cf:a2:51:5d:7a:07:96:84:45:
b2:08:f1:8d:3f:b2:5d:3d:bb:e9:f6:9f:0d:d0:e3:
0c:01:35:71:39:05:ed:0a:bd:6c:f3:ce:39:7a:86:
e4:fc:cf:b9:fa:8f:5f:ac:04:45:d3:bf:ff:2b:4a:
ff:1a:57:47:3d:f7:61:8d:bc:b4:00:8d:dd:cb:29:
bb:6c:01:ad:cc:07:a2:7e:66:1a:94:aa:34:20:a9:
fe:68:eb:b7:bf:06:32:de:57:c9:dd:fa:64:68:05:
e4:6a:f6:c0:fc:d7:02:d6:36:bf:a8:84:38:0e:fc:
bc:84:c1:e8:e3:5c:8e:bc:d1
coefficient:
65:37:46:c0:b5:90:62:ab:72:64:e6:29:c2:9e:b0:
a0:90:6c:9f:89:2b:37:a6:05:99:72:85:09:65:07:
63:b2:ef:a8:50:f0:90:f0:50:57:19:8d:49:8e:fc:
eb:e2:cc:38:27:e8:4d:ef:db:70:43:2c:f9:3a:f8:
20:81:d8:99:5a:3e:47:6a:de:d8:73:c2:7f:90:4c:
4f:45:e7:74:d2:d2:03:d8:ce:65:59:4f:96:4d:46:
95:20:8d:f8:1b:82:e9:9a:30:95:19:64:d7:62:a3:
46:76:0e:bd:b0:88:84:45:11:63:79:54:3d:96:97:
86:f4:a7:6f:77:24:40:39
公钥
ssh-keygen和openssl:https://flyingice.github.io/2020/07/09/encryption.html
平时生成密钥常用的命令是ssh-keygen和openssl,其实ssh-keygen也是调用openssl库来生执行RSA算法,默认生成的私钥文件也是PEM格式
[root@localhost tmp]# ssh-keygen -f id_rsa
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa.
Your public key has been saved in id_rsa.pub.
The key fingerprint is:
SHA256:ar6gFQWu5K7utU2HiHn9+iIRwwBR14gjzf8QDdWB1F8 root@localhost.localdomain
The key's randomart image is:
+---[RSA 2048]----+
|+=..+Bo+.. |
|..=oo.+ o E |
| .+o... . . |
| o =o. . |
| o +o S |
| .o.o.o. |
| o.+++o. |
| .o++=o |
|+o...o*+ |
+----[SHA256]-----+
但通过file命令可以看到,ssh-keygen命令生成的公钥文件和openssl生成的有些不同,是SSH格式(见 [rfc4251]),没有采用ASN.1和DER编码
直接查看,ssh-keygen生成的公钥是OpenSSH格式,内容分为三部分,用空格隔开:算法名称,公钥,注释
公钥部分包括指数e和模数n
将OpenSSH格式的公钥转成PKCS格式
ssh-keygen -e -f id_rsa.pub -m pem > newkey.pub
[root@localhost tmp]# cat id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDrgoJ9ZYa5Nwe2aSpK0uL6bIzK+2ctVSTrgAJ6CJ7zTBYsmeRvACLnPjqFfVmi2ZqGLQlgP2tJTLZ8v8UvOhWMc7gF1g52hBJIU4Fe8t+tWd58P+X77CTpPX4OI7bEnoQ4OXCBlr8yTmiLQPjTigvo8Xzs7zSFqtIrj0mrn7E/T4+oWFfbBTz1PFW1rSN+Z/MPpInoIC5q1MjRr9hAxLAClNDvkl24OcqKRB2Mcyjf1n62kDapyz5X9wmWzHqy6VcAEvW+W+YsWR7i4JIistJDbZo6D9nWPmMvpwdZoVwxPnvzk+EQymcecIGiIggqnZXVea/CofvgbiEKRQ1REf6b root@localhost.localdomain
[root@localhost tmp]# cat newkey.pub
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA64KCfWWGuTcHtmkqStLi+myMyvtnLVUk64ACegie80wWLJnkbwAi
5z46hX1Zotmahi0JYD9rSUy2fL/FLzoVjHO4BdYOdoQSSFOBXvLfrVnefD/l++wk
6T1+DiO2xJ6EODlwgZa/Mk5oi0D404oL6PF87O80harSK49Jq5+xP0+PqFhX2wU8
9TxVta0jfmfzD6SJ6CAuatTI0a/YQMSwApTQ75JduDnKikQdjHMo39Z+tpA2qcs+
V/cJlsx6sulXABL1vlvmLFke4uCSIrLSQ22aOg/Z1j5jL6cHWaFcMT5785PhEMpn
HnCBoiIIKp2V1XmvwqH74G4hCkUNURH+mwIDAQAB
-----END RSA PUBLIC KEY-----
对比编码,可以发现两者都包含模数n和指数e
扩展
- 不知道各位小伙伴有没有注意到,PEM格式的证书文件,总是“MII”开头?
还记得前面提到的长格式存储嘛,以pubkey.p1公钥为例
前面的三个字节30 82 01 ,第一个字节是ASN.1的sequence开头,而0x82中的2表明后面用两个字节表示长度,0x01是长度的第一个字节,对比二进制和base64编码表,以6bit对应一个字符,可以得知base64编码为MIIB;
之所以是0x82,是由于用两个字节表示长度,已经足够表示128~65535,而几乎所有证书的长度都在这个范围;而且,只要证书的长度小于16384字节,则表示长度的第一个字节的前两位(箭头所指)即为00,转成base64后就对应了“MII” !!!
在linux中,可以使用xxd命令将十六进制转成二进制
[root@localhost tmp]# xxd -r -p <<<308201 | base64
MIIB
PKCS#1和PKCS#8
PKCS1与PKCS8的小知识:https://www.jianshu.com/p/a428e183e72e
PKCS维基百科:https://en.wikipedia.org/wiki/PKCS
PKCS百度百科:https://baike.baidu.com/item/PKCS/1042350
PKCS#1:全名《Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications》,是RSA的加密标准,最新的文档是RFC8017,版本为2.2,其中也包括了私钥的定义规则;
PKCS#8:全名《Public-Key Cryptography Standards (PKCS) #8: Private-Key Information Syntax Specification》最新文档是RFC5958,版本1.2,是专门描述私钥的格式,和上面的PKCS#1有重叠的部分
创建两个版本的私钥文件
# 创建pkcs#1
openssl genrsa -out pkcs1.pem 1024
# 将pkcs#1转为pkcs#8
openssl pkcs8 -topk8 -inform PEM -in pkcs1.pem -outform PEM -nocrypt -out pkcs8.pem
[root@localhost tmp]# cat pkcs8.pem
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMrtbVNauF7zJLnC
nhEnD5mgXX/rIFNvyBH8RbUoWs8KVS+W5fweUY0jxuKA/doqMV1y8aEtrI8ShVd4
nUgtCPJntmPdgXG64zztI6m/RiSuiUkhHUcQo04votPtGcAgyi3FjqZWfLUdTtH8
l6AMoectBSWGydy5wzw5rXJOE1CtAgMBAAECgYAkHPK6xcdP5ndfs5nLtabaESby
j9Z14su5H6sMHT1j4RPd3z3Yi1C6DGblgk0q2R1l4wHLTESMRaCDO4zJ/nG72McJ
IAp5FAlRw0oj348wjHR3lqlKVuPkFQ24Mr5oDw4Vz4rrJUkl05pSvS2crOdbWwlT
ivopd780hXizr/7cpQJBAOUoMc9/jKJwPGxmUI2SuqIqQHCEZKAtqimbo0JAWw9f
Vd1Ef5bnCrClu5oN7PpWBsgO8muV25tF9RAASN/44I8CQQDisq2bs69wakU21kH/
9hhhjtFOyrBCGLpenySKe2NpY4l9M+axyzLens61PKMaSQ+v/jLWEWE9NEoF+zCS
BeEDAkAh1xOz/rP38PcbUuimX3qsI+0sMIDjnpq4VkF/sR1BYeS+LjVhRXEwsOjJ
UTEjrryFop9t/81UWrpbyI76dDhFAkBXuAnjWLkHB6f2mxt4vrIaddfZ5CYlvUnI
OU47Y5lI+2IFF2Mp4tWxPMQBbxAxqJmwYO0s1hbJRwVpoRPfPPUfAkEAi+aD1tyS
Tu9k1BANDiYPnNbOi42/x3xDZEeSUy2pfDIrQiOezUaXGwDxWJBngQROAkNax96f
NwyBb+a50ab25w==
-----END PRIVATE KEY-----
[root@localhost tmp]# cat pkcs1.pem
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDK7W1TWrhe8yS5wp4RJw+ZoF1/6yBTb8gR/EW1KFrPClUvluX8
HlGNI8bigP3aKjFdcvGhLayPEoVXeJ1ILQjyZ7Zj3YFxuuM87SOpv0YkrolJIR1H
EKNOL6LT7RnAIMotxY6mVny1HU7R/JegDKHnLQUlhsncucM8Oa1yThNQrQIDAQAB
AoGAJBzyusXHT+Z3X7OZy7Wm2hEm8o/WdeLLuR+rDB09Y+ET3d892ItQugxm5YJN
KtkdZeMBy0xEjEWggzuMyf5xu9jHCSAKeRQJUcNKI9+PMIx0d5apSlbj5BUNuDK+
aA8OFc+K6yVJJdOaUr0tnKznW1sJU4r6KXe/NIV4s6/+3KUCQQDlKDHPf4yicDxs
ZlCNkrqiKkBwhGSgLaopm6NCQFsPX1XdRH+W5wqwpbuaDez6VgbIDvJrldubRfUQ
AEjf+OCPAkEA4rKtm7OvcGpFNtZB//YYYY7RTsqwQhi6Xp8kintjaWOJfTPmscsy
3p7OtTyjGkkPr/4y1hFhPTRKBfswkgXhAwJAIdcTs/6z9/D3G1Lopl96rCPtLDCA
456auFZBf7EdQWHkvi41YUVxMLDoyVExI668haKfbf/NVFq6W8iO+nQ4RQJAV7gJ
41i5Bwen9psbeL6yGnXX2eQmJb1JyDlOO2OZSPtiBRdjKeLVsTzEAW8QMaiZsGDt
LNYWyUcFaaET3zz1HwJBAIvmg9bckk7vZNQQDQ4mD5zWzouNv8d8Q2RHklMtqXwy
K0Ijns1GlxsA8ViQZ4EETgJDWsfenzcMgW/mudGm9uc=
-----END RSA PRIVATE KEY-----
首先直接对比私钥的参数(n, e, d, p, q……),可以看出所包含的元素是一样的
这里也可以使用ASN.1解码器解析对比,可以发现PKCS#8比PKCS#1多了一部分投信息,这部分头信息的值表示OID 1.2.840.113549.1.1.11,表明本证书使用的算法是sha256WithRSAEncryption,,而OID也有其自身的编码,这里不展开,感兴趣的小伙伴可以自行扩展
综上所述,PKCS#8比PKCS#1多出一部分头信息,可以用来描述证书所使用的算法,相比于PKCS#1只能描述RSA算法,PKCS#8还可以描述诸如ECC(椭圆曲线加密算法)的私钥
这串编码的解释:https://www.rfc-editor.org/rfc/rfc5754
30 0D 06 09 2A 86 48 86 F7 0D 01 01 01 05 00 04
小结
- 这一节以RSA算法常见的PEM私钥文件为例,讲述了基于DER编码规则的PKCS #1标准,文档为RFC8017
- 而DER编码是BER的子集,都是基于ASN.1数据结构,都是以《Type,Length,Value》的三元组标识数据
- ssh-keygen和openssl命令创建的公钥文件有些不同,ssh-keygen创建的公钥是OpenSSH格式,以空格分割三部分:算法名称,公钥,注释
- 采用PEM格式的RSA证书,几乎都是以“MII”开头,原因和DER编码转换为base64的对应关系有关
- PKCS#8相对于PKCS#1是扩展增强的关系,PKCS#1推出的时候只有RSA算法相对成熟,因此只能用来描述RSA算法;而PKCS#8可以描述其他算法的私钥
概念梳理
读到这里相信各位小伙伴已经对RSA密钥的文件格式有一定了解,对于PKCS#1,PKCS#8,ASN.1,BER,DER等概念也有一定认识,下面让我们来再梳理一下容易混淆的几个概念。
回答几个问题:
- ASN.1和DER,PEM,RSA,openssl之间的区别和联系是怎样的?
- 为什么有的公钥以“-----BEGIN PUBLIC KEY-----”开头,而有的以“-----BEGIN RSA PUBLIC KEY-----”,私钥同问?
各名词之间的区别与联系
- ASN.1定义了数据结构,但没有定义如何编码;
- 而DER则是编码规则,以<Type, Length, Value>的三元组标识一个数据;
- PEM则是为了方便传输和阅读,对数据进行base64编码;
- RSA是一种非对称加密算法,PEM证书文件的内容是RSA算法的参数,包括n,e,d,p,q等;
- PKCS是密钥存储标准,公私钥都可,定义了哪部分是n,哪部分是e,d,间隔的规则就是DER和ASN.1;
- PKCS#1只能描述RSA算法,而PKCS#8是增强版,可以描述其他算法的私钥钥;
- PKCS#8定义了通用的私钥格式,而PKIX定义了通用的公钥格式;
- SSL和TLS都是安全认证协议,TLS是SSL的升级,之所以现在还叫SSL证书,是历史名称,实际上已经是“TLS”证书了;
- SSH是加密的shell,OpenSSH是对SSH协议的免费开源实现,OpenSSH使用OpenSSL提供的库;
- OpenSSL是一个C语言函数库,是对SSL协议的实现,linux中有个命令也叫openssl,可以用来生成证书文件;
# ASN.1
Point ::= SEQUENCE {
x INTEGER,
y INTEGER,
label UTF8String
}
# DER编码,坐标(9,9)
30 06 80 01 09 81 01 09
证书文件的开头
常见的私钥开头有“-----BEGIN RSA PRIVATE KEY-----”和“-----BEGIN PRIVATE KEY-----”两种,从字面上来看,前者特指RSA私钥,而后者只是表明这是个私钥而已;
# PKCS#1私钥
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEApwh…………
-----END RSA PRIVATE KEY-----
# PKCS#1公钥
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEApwhprohZAq4……
-----END RSA PUBLIC KEY-----
# PKCS#8私钥
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqh…………
-----END PRIVATE KEY-----
# PKIX公钥
-----BEGIN PUBLIC KEY-----
MIIGfMA0GCSqGSIb3D……
-----END PUBLIC KEY-----
# SSH公钥
ssh-rsa AAAAB3NXRvRv9mw== vagrant@stretch
从解码器上直接对比,可以看到PKCS#8多一个算法标识部分,这也是它可以存储其他算法秘钥的原因;
同理,公钥文件采用PKCS#1格式和PKIX格式的区别也是类似,可以通过开头的注释判断;而ssh-keygen创建的是SSH格式的公钥,没有使用ASN.1和DER编码
总结
- RSA算法是常用的非对称加密算法,在上一篇中我们了解了RSA算法的原理和简单实现,而在这一篇中我们了解了RSA证书的文件格式;
- 一个证书文件需要涉及到数据结构(ASN.1)和编码规则(DER),为了方便使用还需要指定各参数的位置(PKCS,PKIX);
- 而为了方便阅读和传输,还需要对二进制数据进行编码(DER,base64);
- 我们平时会使用一些工具和命令来生成证书文件(ssh-keygen,openssl),在互联网上有许多协议会使用这些证书文件做加解密和签名验证(SSL,TLS,SSH,OpenSSH,HTTPS);
- 下一篇我们将以常见的HTTPS协议为例,讲解为什么要使用非对称加密,以及如何使用,敬请期待~~
PS:这两篇前前后后写了好几天,原本是打算直接写HTTPS的,但发现相关概念有些多,还是先梳理梳理
参考文档
- 密码学基础3:密钥文件格式完全解析:https://www.jianshu.com/p/ce7ab5f3f33a
- RSA密钥、加密和数字签名:https://juejin.cn/post/6844903881655369742
- ASN.1 和 DER:https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/
- RFC8017:https://www.rfc-editor.org/rfc/rfc8017
- RFC5958:https://www.rfc-editor.org/rfc/rfc5958
- RFC5754:https://www.rfc-editor.org/rfc/rfc5754
- ASN.1解码器:https://lapo.it/asn1js/
- base64编码解码器:https://the-x.cn/base64
- RSA密钥在线生成器:https://www.bejson.com/enc/rsa/
- 解读RSA公钥私钥储存格式:https://www.modb.pro/db/126884
- ssh-keygen生成的id_rsa文件的格式:https://zhuanlan.zhihu.com/p/33949377
- 秘钥格式证书:http://douxinchun.github.io/blog/20180718/rsa-key-structuress.html
- RSA密钥长度、明文长度和密文长度:https://cloud.tencent.com/developer/article/1199963
- 为什么RSA 公钥指数(e=65537):https://blog.csdn.net/hherima/article/details/52461759
- PKCS1与PKCS8的小知识:https://www.jianshu.com/p/a428e183e72e
- 各种格式SSH 公钥和私钥之间的转换:https://blog.csdn.net/stevensxiao/article/details/109381001
- RSA公私钥pkcs1与pkcs8格式的转换:https://blog.csdn.net/boweiqiang/article/details/116309452
- SSL和SSH和OpenSSH,OpenSSL有什么区别:https://www.cnblogs.com/foohack/p/4103212.html
- TLS与SSL有什么区别?你应该使用哪一个?:https://www.wbolt.com/tls-vs-ssl.html
- X.690:https://www.itu.int/rec/T-REC-X.690
- 椭圆曲线ECC密钥:https://crypto.stackexchange.com/questions/68099/how-is-ec-key-encoded-in-pkcs8