在“RSA 实验室”(编辑 PKCS 标准的组织)和 RSA(加密算法)之间可能存在一些混淆。PKCS#1是 PKCS 标准之一,因此由 RSA 实验室编辑;它谈论算法 RSA,并且只谈论 RSA 算法。
特别是,椭圆曲线 (EC) 密钥没有“PKCS#1 格式”之类的东西,因为 EC 密钥不是 RSA 密钥——它们是 EC 密钥,根本不是同一种对象。
然而,混乱已经蔓延得更远了,所以让我们解开几层。
PKCS#1 讨论 RSA 并为 RSA 私钥定义了基于 ASN.1 的编码。它看起来像这样:
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
我们在这里认识到构成 RSA 公钥/私钥对的各种数学元素。基于ASN.1,这种对象(通过DER)编码成一些字节。OpenSSL 可以产生和消费这样一个字节序列;但是,将这些字节进一步重新编码为传统(且指定不明确)的 PEM 格式是司空见惯的:这些字节使用Base64编码,并添加了标头和页脚,以指定编码对象的类型。
重要的是要注意,在 PKCS#1 中定义的 RSA 私钥的基于 ASN.1 的原始格式会导致不包含密钥类型的明确标识的字节序列。任何以该格式读取 DER 编码的 RSA 私钥的应用程序都必须事先知道它应该期待一个 RSA 私钥。PEM 标头,即“RSA PRIVATE KEY”,提供该信息。
由于 PKCS 标准没有讨论 PEM,它们为识别密钥类型的问题提供了自己的解决方案;它被称为PKCS#8。PKCS#8 格式的密钥同样基于 ASN.1,其结构如下所示:
PrivateKeyInfo ::= SEQUENCE {
version Version,
privateKeyAlgorithm AlgorithmIdentifier {{PrivateKeyAlgorithms}},
privateKey PrivateKey,
attributes [0] Attributes OPTIONAL }
Version ::= INTEGER {v1(0)} (v1,...)
PrivateKey ::= OCTET STRING
这意味着 PKCS#8 对象实际上是某种其他格式的包装器。在 RSA 私钥的情况下,包装器(通过privateKeyAlgorithm
字段)表明该密钥确实是 RSA 密钥,并且该PrivateKey
字段的内容(an OCTET STRING
,即任意字节序列)实际上是 PKCS 的 DER 编码#1 私钥。
默认情况下,OpenSSL 不会让 PKCS#8 文件以 DER 编码的字节序列的形式存在;它将再次将其转换为 PEM,并且这一次将添加“BEGIN PRIVATE KEY”标题。请注意,此标头未指定密钥类型,因为编码对象(通过 Base64 转换为字符)已经包含信息。
(更复杂的是,PKCS#8 还定义了一种可选的、通常基于密码的私钥加密;OpenSSL 实现的传统的类似 PEM 的格式还包括对基于密码的加密的一些通用支持;因此您可以有多种组合指定某种加密的包装器,导致只能被描述为一团糟。)
现在这告诉我们关于EC 密钥的什么信息?PKCS#1(仅讨论 RSA)未描述 EC 密钥。但是,如果某处有一个标准说明如何将 EC 私钥转换为字节序列,那么:
- 该字节序列可以由 OpenSSL 进行 PEM 编码,并带有一些明确的文本标头;
- 可以将相同的字节序列包装到 PKCS#8 对象中。
这正是发生的事情。定义 EC 密钥编码格式的标准是SEC 1(名义上,EC 加密的标准是 ANSI X9.62;然而,虽然 X9.62 重用了 SEC 1 的大部分内容,但编码私有EC 密钥的规范仅在 SEC 1,因为 X9.62 只关心公钥的编码)。在 SEC 1(第 C.4 节)中,定义如下:
ECPrivateKey ::= SEQUENCE {
version INTEGER { ecPrivkeyVer1(1) },
privateKey OCTET STRING,
parameters [0] EXPLICIT ECDomainParameters OPTIONAL,
publicKey [1] EXPLICIT BIT STRING OPTIONAL
}
因此,编码的私钥包含私钥本身(1.. n -1 范围内的整数,其中n是曲线子组顺序),可选的描述或对所用曲线的引用,以及可选的公钥副本(否则可以重新计算)。
让我们试试看。我们使用 OpenSSL 在标准 NIST P-256 曲线(这是每个人都实现和使用的曲线)中生成一个新的 EC 密钥对:
$ openssl ecparam -out ec1.pem -genkey -name prime256v1
我们在ec1.pem
文件中得到了这个:
$ cat ec1.pem
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIBdVHnnzZmJm+Z1HAYYOZlvnB8Dj8kVx9XBH+6UCWlGUoAoGCCqGSM49
AwEHoUQDQgAEThPp/xgEov0mKg2s0GII76VkZAcCc//3quAqzg+PuFKXgruaF7Kn
3tuQVWHBlyZX56oOstUYQh3418Z3Gb1+yw==
-----END EC PRIVATE KEY-----
第一个元素(“EC PARAMETERS”)是多余的;它包含对所用曲线的引用,但此信息也存在于第二个元素中。因此,让我们使用文本编辑器删除“EC PARAMETERS”,我们只保留“EC PRIVATE KEY”部分。现在我的ec1.pem
文件如下所示:
$ cat ec1.pem
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIBdVHnnzZmJm+Z1HAYYOZlvnB8Dj8kVx9XBH+6UCWlGUoAoGCCqGSM49
AwEHoUQDQgAEThPp/xgEov0mKg2s0GII76VkZAcCc//3quAqzg+PuFKXgruaF7Kn
3tuQVWHBlyZX56oOstUYQh3418Z3Gb1+yw==
-----END EC PRIVATE KEY-----
我们可以使用 OpenSSL 来解码它的结构:
$ openssl asn1parse -i -in ec1.pem
0:d=0 hl=2 l= 119 cons: SEQUENCE
2:d=1 hl=2 l= 1 prim: INTEGER :01
5:d=1 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:17551E79F3666266F99D4701860E665BE707C0E3F24571F57047FBA5025A5194
39:d=1 hl=2 l= 10 cons: cont [ 0 ]
41:d=2 hl=2 l= 8 prim: OBJECT :prime256v1
51:d=1 hl=2 l= 68 cons: cont [ 1 ]
53:d=2 hl=2 l= 66 prim: BIT STRING
我们认识到预期的 ASN.1 结构,如 SEC 1 所定义:一个包含值为 1 的 INTEGER(version
字段)的 SEQUENCE,一个 OCTET STRING(它privateKey
本身,它是数学私钥的大端无符号编码) ,[0]
对所用曲线[1]
的引用(用公钥。
我们可以将其转换为(未加密的)PKCS#8 格式:
$ openssl pkcs8 -topk8 -nocrypt -in ec1.pem -out ec2.pem
这产生了这个:
$ cat ec2.pem
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgF1UeefNmYmb5nUcB
hg5mW+cHwOPyRXH1cEf7pQJaUZShRANCAAROE+n/GASi/SYqDazQYgjvpWRkBwJz
//eq4CrOD4+4UpeCu5oXsqfe25BVYcGXJlfnqg6y1RhCHfjXxncZvX7L
-----END PRIVATE KEY-----
我们可以用 OpenSSL 解码:
$ openssl asn1parse -i -in ec2.pem
0:d=0 hl=3 l= 135 cons: SEQUENCE
3:d=1 hl=2 l= 1 prim: INTEGER :00
6:d=1 hl=2 l= 19 cons: SEQUENCE
8:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
17:d=2 hl=2 l= 8 prim: OBJECT :prime256v1
27:d=1 hl=2 l= 109 prim: OCTET STRING [HEX DUMP]:306B0201010420(...)
(我已经截断了十六进制转储。)这个结构确实是一个 PKCS#8 对象:
- 算法标识符字段说:“这包含一个 EC 密钥”(从技术上讲,它使用一个名称为“id-ecPublicKey”的标识符,但由于这发生在 PKCS#8 文件中,每个人都知道这实际上意味着一个 EC私钥) .
- 该文件包括对所用曲线的引用作为关键参数。
- 键值被编码到
OCTET STRING
. 如果我们进一步解码该 OCTET STRING,我们将发现 EC 私钥按照 SEC 1 的规定进行编码(有趣的是,在这种情况下,对曲线的引用似乎已被省略,因为它已经存在于密钥参数中)。
可以在另一个方向进行转换(从 PKCS#8 到原始 SEC 1 格式):
$ openssl ec -in ec2.pem -out ec3.pem
然后,您将获得文件ec3.pem
中的内容ec1.pem
:一个带有标题“BEGIN EC PRIVATE KEY”的 PEM 编码对象。
摘要:没有“PKCS#1 格式的 EC 密钥”之类的东西:PKCS#1 仅用于 RSA 密钥,不用于 EC 密钥。但是,还有另一种格式,类似于 PKCS#1,但用于 EC 密钥,并在 SEC 1 中定义。OpenSSL 可以使用“”命令将该格式转换为通用 PKCS#8,并使用“ ”openssl pkcs8
命令转换回 SEC 1 格式openssl ec
.