如何调整C#的公共/私有RSA密钥以便在Java中使用它们?

时间:2018-03-14 16:05:32

标签: java c# cryptography rsa

我有一些服务器可以通过加密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#中?

2 个答案:

答案 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))));
}

您不应该真正需要编码的私钥。但是,如果你真的这样做,你需要通用的方法,因为私钥数据的可变性有很大的空间。

PrivateKeyInfoRFC 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.keyopenssl 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