我有一些服务器可以通过加密API提供对数据的访问。我需要在C#中编写客户端,可以创建对服务器的请求并从中读取响应。
为此,我需要创建公共和私有RSA密钥并将它们转换为字节数组。我在java中有一个工作示例:
java.security.KeyPairjava.security.KeyPair keypair = keyGen.genKeyPair();
byte[] pubKeyBytes = keypair.getPublic().getEncoded();
byte[] privKeyBytes = keypair.getPrivate().getEncoded();
我试图用.NET中的C#做同样的事情:
RSACryptoServiceProvider keyPair = new RSACryptoServiceProvider(2048);
var publicKey = keyPair.ExportParameters(false);
var privateKey = keyPair.ExportParameters(true);
而且我不知道该怎么做。我有D,Dp,DQ,InverseQ,Modulus,Exponent作为publicKey和privateKey的属性,但在java示例中,这些键看起来像单个联合键。 D,Dp,DQ,InverseQ,Modulus,Exponent中的哪一个应该用于我的任务?有什么方法与java示例相同,但在C#中?
答案 0 :(得分:1)
根据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
(又名SEQUENCE(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值。因此,假设您的Exponent值为01 00 01
,则编码值为02 03 01 00 01
。模数长度取决于密钥的大小。
int modulusBytes = parameters.Modulus.Length;
if (parameters.Modulus[0] >= 0x80)
modulusBytes++;
RSACryptoServiceProvider应始终创建需要额外字节的密钥,但技术上可能存在密钥,而不存在密钥。我们需要它的原因是parameters.Modulus是一个UNsigned大端编码,如果设置了高位,那么我们将编码一个负数到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();
}
或者,对于通用响应(创建大量临时byte [] s):
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
答案 1 :(得分:0)
您需要使用ExportCspBlob
方法:
RSACryptoServiceProvider keyPair = new RSACryptoServiceProvider(2048);
var publicKey = keyPair.ExportCspBlob(false);
var privateKey = keyPair.ExportCspBlob(true);
ExportParameters
导出可以从中计算密钥本身的特定参数。有关这些参数的详细信息,请参阅wiki article。