根据https://docs.oracle.com/javase/7/docs/api/java/security/Key.html#getFormat(),公钥编码的默认值为 X.509 SubjectPublicKeyInfo,私钥为 PKCS#8 PrivateKeyInfo。
有很多关于从 SubjectPublicKeyInfo 创建 RSAParameters 的问题(如 Correctly create RSACryptoServiceProvider from public key),但反之则不多。
如果您通过 RSACryptoServiceProvider 创建密钥,那么新密钥的指数值将始终为 0x010001,这意味着您必须处理的唯一可变大小的数据是模值。这很重要的原因是 SubjectPublicKeyInfo(几乎总是)在 DER(由ITU-T X.690 定义)中编码,它使用长度前缀值。 ASN.1 (ITU-T X.680) 在RFC 5280 中定义为
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
用于 RSA 的 AlgorithmIdentifier 的编码值为
30 0D 06 09 2A 86 48 86 F7 0D 01 01 01 05 00
(又名序列(OID("1.2.840.113549.1.1.1"), NULL))
subjectPublicKey 的值取决于算法。对于 RSA,它是 RSAPublicKey,在 RFC 3447 中定义为
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e }
INTEGER 的编码是 02(然后是长度),然后是带符号的 big-endian 值。因此,假设您的指数值为01 00 01,则编码值为02 03 01 00 01。模数长度取决于密钥的大小。
int modulusBytes = parameters.Modulus.Length;
if (parameters.Modulus[0] >= 0x80)
modulusBytes++;
RSACryptoServiceProvider 应始终创建需要额外字节的密钥,但技术上可能存在不需要的密钥。我们需要它的原因是 parameters.Modulus 是一个无符号大端编码,如果设置了高位,那么我们会将一个负数编码到 RSAPublicKey 中。我们通过插入一个 00 字节来解决这个问题,以保持符号位清晰。
模数的长度字节有点棘手。如果模数可以用 127 个字节或更少(RSA-1015 或更小)表示,那么您只需为该值使用一个字节。否则,您需要最小的字节数来报告该数字,加一。那个额外的字节(实际上是第一个字节)表示长度是多少字节。所以 128-255 是一个字节,81。 256-65535 是两个,所以82。
然后我们需要将它包装成一个 BIT STRING 值,这很容易(如果我们忽略困难的部分,因为它们在这里不相关)。然后将其他所有内容封装在一个 SEQUENCE 中,这很容易。
又快又脏,只适用于指数=0x010001 的 2048 位密钥:
private static byte[] s_prefix =
{
0x30, 0x82, 0x01, 0x22,
0x30, 0x0D,
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
0x05, 0x00,
0x03, 0x82, 0x01, 0x0F,
0x00,
0x30, 0x82, 0x01, 0x0A,
0x02, 0x82, 0x01, 0x01, 0x00
};
private static byte[] s_suffix = { 0x02, 0x03, 0x01, 0x00, 0x01 };
private static byte[] MakeSubjectPublicInfoEasy2048(RSA rsa)
{
if (rsa.KeySize != 2048)
throw new ArgumentException(nameof(rsa));
RSAParameters rsaParameters = rsa.ExportParameters(false);
if (Convert.ToBase64String(rsaParameters.Exponent) != "AQAB")
{
throw new ArgumentException(nameof(rsa));
}
return s_prefix.Concat(rsaParameters.Modulus).Concat(s_suffix).ToArray();
}
或者,对于通用响应(创建大量临时字节[]):
private static byte[] MakeTagLengthValue(byte tag, byte[] value, int index = 0, int length = -1)
{
if (length == -1)
{
length = value.Length - index;
}
byte[] data;
if (length < 0x80)
{
data = new byte[length + 2];
data[1] = (byte)length;
}
else if (length <= 0xFF)
{
data = new byte[length + 3];
data[1] = 0x81;
data[2] = (byte)length;
}
else if (length <= 0xFFFF)
{
data = new byte[length + 4];
data[1] = 0x82;
data[2] = (byte)(length >> 8);
data[3] = unchecked((byte)length);
}
else
{
throw new InvalidOperationException("Continue the pattern");
}
data[0] = tag;
int dataOffset = data.Length - length;
Buffer.BlockCopy(value, index, data, dataOffset, length);
return data;
}
private static byte[] MakeInteger(byte[] unsignedBigEndianValue)
{
if (unsignedBigEndianValue[0] >= 0x80)
{
byte[] tmp = new byte[unsignedBigEndianValue.Length + 1];
Buffer.BlockCopy(unsignedBigEndianValue, 0, tmp, 1, unsignedBigEndianValue.Length);
return MakeTagLengthValue(0x02, tmp);
}
for (int i = 0; i < unsignedBigEndianValue.Length; i++)
{
if (unsignedBigEndianValue[i] != 0)
{
if (unsignedBigEndianValue[i] >= 0x80)
{
i--;
}
return MakeTagLengthValue(0x02, unsignedBigEndianValue, i);
}
}
// All bytes were 0, encode 0.
return MakeTagLengthValue(0x02, unsignedBigEndianValue, 0, 1);
}
private static byte[] MakeSequence(params byte[][] data)
{
return MakeTagLengthValue(0x30, data.SelectMany(a => a).ToArray());
}
private static byte[] MakeBitString(byte[] data)
{
byte[] tmp = new byte[data.Length + 1];
// Insert a 0x00 byte for the unused bit count value
Buffer.BlockCopy(data, 0, tmp, 1, data.Length);
return MakeTagLengthValue(0x03, tmp);
}
private static byte[] s_rsaAlgorithmId = new byte[] { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
private static byte[] ExportSubjectPublicKeyInfo(RSA rsa)
{
RSAParameters parameters = rsa.ExportParameters(false);
return MakeSequence(
s_rsaAlgorithmId,
MakeBitString(
MakeSequence(
MakeInteger(parameters.Modulus),
MakeInteger(parameters.Exponent))));
}
你不应该真的需要编码的私钥。但是,如果您真的这样做,您需要通用方法,因为私钥数据有很大的可变性空间。
PrivateKeyInfo 在RFC 5208 中定义为
PrivateKeyInfo ::= SEQUENCE {
version Version,
privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
privateKey PrivateKey,
attributes [0] IMPLICIT Attributes OPTIONAL }
Version ::= INTEGER
PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
PrivateKey ::= OCTET STRING
Attributes ::= SET OF Attribute
它还说当前版本号是0。
私钥的八位字节串由算法定义。对于 RSA,我们在 RFC 3447 中看到,以及 RSAPublicKey:
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 }
忽略otherPrimeInfos。它不适用,也不应该适用。因此要使用的版本号为 0。
采用已经定义的实用方法,我们得到其余的
private static byte[] MakeOctetString(byte[] data)
{
return MakeTagLengthValue(0x04, data);
}
private static byte[] s_integerZero = new byte[] { 0x02, 0x01, 0x00 };
private static byte[] ExportPrivateKeyInfo(RSA rsa)
{
RSAParameters parameters = rsa.ExportParameters(true);
return MakeSequence(
s_integerZero,
s_rsaAlgorithmId,
MakeOctetString(
MakeSequence(
s_integerZero,
MakeInteger(parameters.Modulus),
MakeInteger(parameters.Exponent),
MakeInteger(parameters.D),
MakeInteger(parameters.P),
MakeInteger(parameters.Q),
MakeInteger(parameters.DP),
MakeInteger(parameters.DQ),
MakeInteger(parameters.InverseQ))));
}
.NET Core 的功能路线图让这一切变得更容易(https://github.com/dotnet/corefx/issues/20414 - 不是说导出,但是有导入的地方通常会有导出 :))
将您的输出保存到文件中,您可以使用openssl rsa -inform der -pubin -text -in pub.key 和openssl rsa -inform der -text -in priv.key 进行检查