简体   繁体   English

如何像Java中那样使用C#的公共/私有RSA密钥?

[英]How to adapt public/private RSA keys of C# for using them as in Java?

I have some server that provides access to data by cryptographic API. 我有一些服务器,可以通过加密API访问数据。 I need to write client in C# that can create requests to server and read responses from. 我需要用C#编写客户端,该客户端可以创建对服务器的请求并从中读取响应。

For doing it I need to create public and private RSA keys and convert them to bytes array. 为此,我需要创建公共和私有RSA密钥并将其转换为字节数组。 I had the working example in java: 我在Java中有工作示例:

    java.security.KeyPairjava.security.KeyPair keypair = keyGen.genKeyPair();

    byte[] pubKeyBytes = keypair.getPublic().getEncoded();
    byte[] privKeyBytes = keypair.getPrivate().getEncoded();

I tried to do the same with C# in .NET: 我试图对.NET中的C#做同样的事情:

    RSACryptoServiceProvider keyPair = new RSACryptoServiceProvider(2048);
    var publicKey = keyPair.ExportParameters(false);
    var privateKey = keyPair.ExportParameters(true);

And I don't know how to do it. 而且我不知道该怎么做。 I have D, Dp, DQ, InverseQ, Modulus, Exponent as properties of publicKey and privateKey, but in java sample those key looks like single united keys. 我有D,Dp,DQ,InverseQ,Modulus,Exponent作为publicKey和privateKey的属性,但是在Java示例中,这些键看起来像单个联合键。 Which one of D, Dp, DQ, InverseQ, Modulus, Exponent I should use for my task? 我应该为任务使用D,Dp,DQ,InverseQ,模量,指数中的哪一个? What the way to do the same as in java example, but in C#? 与Java示例相同,但是在C#中又有什么方法呢?

According to https://docs.oracle.com/javase/7/docs/api/java/security/Key.html#getFormat() the default for a public key encoding is X.509 SubjectPublicKeyInfo and for a private key is PKCS#8 PrivateKeyInfo. 根据https://docs.oracle.com/javase/7/docs/api/java/security/Key.html#getFormat() ,公钥编码的默认值为X.509 SubjectPublicKeyInfo,私钥为PKCS #8 PrivateKeyInfo。

There are a number of questions (like Correctly Create RSACryptoServiceProvider from public key ) on creating RSAParameters from a SubjectPublicKeyInfo, but not as many for the reverse. 关于从SubjectPublicKeyInfo创建RSAParameters的问题很多(例如从公钥正确创建RSACryptoServiceProvider ),反之则不多。

If you're creating your key via RSACryptoServiceProvider then the new key will always have an exponent value of 0x010001, which means the only variable sized piece of data that you have to contend with is the modulus value. 如果您通过RSACryptoServiceProvider创建密钥,那么新密钥将始终具有0x010001的指数值,这意味着您必须应对的唯一可变大小的数据就是模量值。 The reason that this is important is that a SubjectPublicKeyInfo is (almost always) encoded in DER (defined by ITU-T X.690 ), which uses length-prefixed values. 之所以如此重要,是因为SubjectPublicKeyInfo(几乎总是)以DER编码(由ITU-T X.690定义),而DER使用长度前缀的值。 The ASN.1 ( ITU-T X.680 ) is defined in RFC 5280 as ASN.1( ITU-T X.680 )在RFC 5280中定义为

SubjectPublicKeyInfo  ::=  SEQUENCE  {
    algorithm            AlgorithmIdentifier,
    subjectPublicKey     BIT STRING  }

The encoded value for the AlgorithmIdentifier for RSA is RSA的AlgorithmIdentifier的编码值为

30 0D 06 09 2A 86 48 86 F7 0D 01 01 01 05 00

(aka SEQUENCE( OID("1.2.840.113549.1.1.1") , NULL)) (又名SEQUENCE( OID(“ 1.2.840.113549.1.1.1”) ,NULL))

The value for subjectPublicKey depends on the algorithm. subjectPublicKey的值取决于算法。 For RSA it's RSAPublicKey , defined in RFC 3447 as 对于RSA,它是RSAPublicKey ,在RFC 3447中定义为

RSAPublicKey ::= SEQUENCE {
    modulus           INTEGER,  -- n
    publicExponent    INTEGER   -- e }

The encoding for an INTEGER is 02 (then the length) then the signed big-endian value. INTEGER的编码是02(然后是长度),然后是带符号的big-endian值。 So, assuming that your Exponent value is 01 00 01 the encoded value is 02 03 01 00 01 . 因此,假设您的指数值为01 00 01则编码值为02 03 01 00 01 The modulus length depends on the size of your key. 模数长度取决于密钥的大小。

int modulusBytes = parameters.Modulus.Length;

if (parameters.Modulus[0] >= 0x80)
    modulusBytes++;

RSACryptoServiceProvider should always create keys that need the extra byte, but technically keys could exist which don't. RSACryptoServiceProvider应该始终创建需要额外字节的密钥,但是从技术上讲,可以存在不需要的密钥。 The reason we need it is that parameters.Modulus is an UNsigned big-endian encoding, and if the high bit is set then we would be encoding a negative number into the RSAPublicKey. 我们需要它的原因是parameters.Modulus是UNsigned big-endian编码,如果设置了高位,则我们将在RSAPublicKey中编码一个负数。 We fix that by inserting an 00 byte to keep the sign bit clear. 我们通过插入00字节来保持符号位清晰来解决此问题。

The length bytes for the modulus are slightly tricky. 模数的长度字节有些棘手。 If the modulus is representable in 127 bytes or fewer (RSA-1015 or smaller) then you just use one byte for that value. 如果模数可表示为127个字节或更少(RSA-1015或更小),则只需为该值使用一个字节。 Otherwise you need the smallest number of bytes to report the number, plus one. 否则,您需要报告的最小字节数加1。 That extra byte (the first one, actually) says how many bytes the length is. 这个额外的字节(实际上是第一个字节)表示长度为多少个字节。 So 128-255 is one byte, 81 . 所以128-255是一个字节81 256-65535 is two, so 82 . 256-65535是2,所以82

We then need to wrap that into a BIT STRING value, which is easy (if we ignore the hard parts, since they're not relevant here). 然后,我们需要将其包装到一个BIT STRING值中,这很容易(如果我们忽略硬部分,因为它们在这里不相关,则可以忽略)。 And then wrap everything else up in a SEQUENCE, which is easy. 然后将其他所有内容包装在SEQUENCE中,这很容易。

Quick and dirty, only works on a 2048-bit key with exponent=0x010001: 快速而肮脏,仅适用于指数= 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();
}

Or, for a general-purpose response (that creates a lot of temporary byte[]s): 或者,对于通用响应(会创建很多临时byte []):

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

You shouldn't really need the encoded private key. 您实际上不需要编码的私钥。 But, if you really do, you need the general-purpose approach because there's a lot of room for variability in the private key data. 但是,如果确实需要,则需要通用方法,因为私钥数据有很大的可变性空间。

PrivateKeyInfo is defined in RFC 5208 as RFC 5208中将PrivateKeyInfo定义为

PrivateKeyInfo ::= SEQUENCE {
  version                   Version,
  privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
  privateKey                PrivateKey,
  attributes           [0]  IMPLICIT Attributes OPTIONAL }

Version ::= INTEGER

PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier

PrivateKey ::= OCTET STRING

Attributes ::= SET OF Attribute

It also says the current version number is 0. 它还说当前版本号是0。

The octet string of the private key is defined by the algorithm. 私钥的八位位组字符串由算法定义。 For RSA we see in RFC 3447, along with RSAPublicKey : 对于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 }

Ignore otherPrimeInfos . 忽略otherPrimeInfos It doesn't, and shouldn't ever, apply. 它不适用,也不应该适用。 Therefore the version number to use is 0. 因此,要使用的版本号为0。

Taking utility methods already defined, we get the rest by 采取已经定义的效用方法,其余

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

Making all of this easier is on the feature roadmap for .NET Core ( https://github.com/dotnet/corefx/issues/20414 - doesn't say export, but where there's an import there's usually an export :)) .NET Core的功能路线图( https://github.com/dotnet/corefx/issues/20414-没有说导出,但是在有导入的地方通常是导出:))使所有这些变得更容易。

Save your output to a file and you can check it with openssl rsa -inform der -pubin -text -in pub.key and openssl rsa -inform der -text -in priv.key 将输出保存到文件中,然后可以使用openssl rsa -inform der -pubin -text -in pub.keyopenssl rsa -inform der -text -in priv.key

You need to use the ExportCspBlob method: 您需要使用ExportCspBlob方法:

RSACryptoServiceProvider keyPair = new RSACryptoServiceProvider(2048);
var publicKey = keyPair.ExportCspBlob(false);
var privateKey = keyPair.ExportCspBlob(true);

ExportParameters exports the specific parameters from which the keys themselves can be calculated. ExportParameters导出可以从中计算密钥本身的特定参数。 For more information about those parameters, see the wiki article . 有关这些参数的更多信息,请参见Wiki文章

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM