“.key”和“.cer”文件扩展名绝不是密钥编码方式的明确规范。然而,“.cer”文件似乎是一个 X.509 证书,其中包含(以及许多其他内容)公钥;因此,您可能想使用X509Certificate
and X509Certificate2
类(在System.Security.Cryptography.X509Certificates
命名空间)来解码证书并提取公钥。
但是,您需要私钥,而不是公钥。这关于 X509Certificate2 的 MSDN 文档 http://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate2.aspx非常令人困惑,因为它使用术语“证书”来指定公共部分(“.cer”文件中的内容)或公共和私有部分的联合,作为单个文件(按照 MSDN 描述的格式)作为“PKCS7(验证码)”)。
编码的 RSA 私钥通常遵循中描述的格式PKCS#1 http://www.rsa.com/rsalabs/node.asp?id=2125,并不复杂,但仍然依赖于ASN.1 http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One,其使用需要小心。有时,此类 RSA 密钥被包装到更大的结构中,该结构还指定密钥类型(即密钥用于 RSA);看PKCS#8 http://www.rsa.com/rsalabs/node.asp?id=2130了解详情。此外,这两种密钥编码通常都以 PEM 格式表示:即Base64 http://en.wikipedia.org/wiki/Base64带有标题(-----BEGIN RSA PRIVATE KEY-----
)和页脚。当然,your私钥可以是任何格式(“.key”扩展名并不过分)。或者,PKCS#8 和 PEM 都可以进行对称加密(使用密码派生密钥)。还有PKCS#12 http://www.rsa.com/rsalabs/node.asp?id=2138格式,可以被视为证书和私钥集合的存档格式,环绕以前的格式; PKCS#12 包括多层加密,在 Microsoft 世界中以“PFX”(或“证书文件”,这一直令人困惑)的名称而闻名。
可以使用一些代码来解码所有这些格式,但此时建议使用已经完成此类工作的库,而不是自行开发。充气城堡 http://www.bouncycastle.org/csharp/是这项工作的常见嫌疑人。
The OpenSSL http://www.openssl.org/命令行工具可以帮助您在某些密钥和证书格式之间进行转换。
Edit:如果您的私钥采用 PKCS#8 DER 格式并且是not受密码保护(PKCS#8可以做到),然后你可以用相对简单的代码对其进行解码。 DER 是一组用于将结构化数据转换为字节序列的规则。数据元素被编码为三个连续的部分:
- a tag它告诉我们它是什么类型的值
- a length对第三部分中的字节数进行编码
- a value这将被解释为由标签指定的
因此名称为“TLV”(即“标签、长度、值”)。某些元素本身是包含子元素的结构,在这种情况下,值包含子元素编码的串联,每个子元素都有自己的标签、长度和值。
标签通常是一个字节;对于 PKCS#8 和 RSA 密钥,您感兴趣的是标签 0x30(表示“SEQUENCE”,即具有子元素的元素)、0x02(“INTEGER”:整数值)和 0x04(“OCTET STRING”:一个 blob) 。
长度被编码为以下之一:
- 单个字节的值n0 到 127 之间(含):编码长度n;
- 一个字节的值n等于或大于 129,后面紧跟n-128以大端格式编码长度的字节。例如,长度 324 将被编码为三个字节:0x82 0x01 0x44。其读作是:“0x82 是 128+2,因此我必须读取两个额外字节;长度是 256*0x01+0x44 = 324”。
对于 INTEGER,该值应使用有符号、大端约定进行解释(第一个字节是最高有效的,第一个字节的高位指定整数符号;对于 RSA,所有值都是正数,因此第一个字节应其值介于 0 到 127 之间)。注意System.Numerics.BigInteger
在 .NET 4.0 中,有一个可以解码一堆字节的构造函数,但它期望它们采用小端约定,而不是大端约定,因此您必须反转字节的顺序。
PKCS#8的结构是:
PrivateKeyInfo ::= SEQUENCE {
version Version,
privateKeyAlgorithm AlgorithmIdentifier,
privateKey OCTET STRING,
attributes [0] Attributes OPTIONAL
}
Version ::= INTEGER { v1(0) }
这就是 ASN.1 表示法。这里必须理解的是,该对象是一个 SEQUENCE 元素:它被编码为一个 SEQUENCE 标签(0x30),然后是一个长度(n),然后是一个值 (n字节,确切地说)。该值由一系列编码元素组成,每个元素都采用 TLV 格式。第一个元素是一个 INTEGER,在正常情况下其数值应为 0(零编码为“0x02 0x01 0x00”)。第二个元素是AlgorithmIdentifier
,我这里就不详细说了;它实际上是一个 SEQUENCE 并且它标识密钥类型(这里应该说“这是一个 RSA 密钥”);只需读取标签(应为 0x30),然后读取长度,然后跳过该值。第三个元素是一个八位字节字符串:一个 0x04 标签,然后是一个长度m,以及值为m字节。这就是我们感兴趣的。应该提取该值,即八位字节字符串的内容;我们将在下一段中对其进行解码。第四个元素PrivateKeyInfo
SEQUENCE 是可选的(它可能根本不存在,并且通常不会),可用于对此格式的各种扩展进行编码。
假设您已提取 OCTET STRING 的内容。这是一个字节序列,实际上是一个结构的 DER 编码,在 ASN.1 中,如下所示:
RSAPrivateKey ::= SEQUENCE {
version INTEGER,
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
-- otherPrimeInfos must be absent if version is two-prime,
-- present if version is multi-prime.
}
因此 OCTET STRING 的内容应以 0x30(SEQUENCE 标记)开头,然后是长度 (r), then r字节。那些rbytes 是九个整数的编码。第一个 INTEGER 应该是 0;如果不是,那么 RSA 密钥有两个以上素因数,那么你就完蛋了。随后的八个 INTEGER 就是您要查找的整数;只需解码它们,就完成了。最后一个字段(otherPrimeInfos
) 是可选的,如果您的 RSA 密钥是“正常”RSA 密钥(具有两个质因数,而不是三个或更多),则应该不存在。