如何像Java中那样使用C#的公共/私有RSA密钥? [英] How to adapt public/private RSA keys of C# for using them as in Java?

查看:93
本文介绍了如何像Java中那样使用C#的公共/私有RSA密钥?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些服务器通过加密API提供对数据的访问。我需要用C#编写客户端,该客户端可以创建对服务器的请求并从中读取响应。

I have some server that provides access to data by cryptographic API. I need to write client in C# that can create requests to server and read responses from.

为此,我需要创建公共和私有RSA密钥并将其转换为字节数组。我在Java中有一个工作示例:

For doing it I need to create public and private RSA keys and convert them to bytes array. I had the working example in java:

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

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

我尝试对.NET中的C#做同样的事情:

I tried to do the same with C# in .NET:

    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,模量,指数中的哪一个?

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. Which one of D, Dp, DQ, InverseQ, Modulus, Exponent I should use for my task? What the way to do the same as in java example, but in C#?

推荐答案

根据 https://docs.oracle.com/javase/7/docs /api/java/security/Key.html#getFormat()公钥编码的默认值是X.509 SubjectPublicKeyInfo,私钥的默认值是PKCS#8 PrivateKeyInfo。

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.

在从a创建RSAParameters时,存在很多问题(例如根据公钥正确创建RSACryptoServiceProvider )。 SubjectPublicKeyInfo,但反之则不多。

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.

如果您通过RSACryptoServiceProvider创建密钥,那么新密钥将始终具有0x010001的指数值,这意味着模数值是您唯一需要应对的可变大小数据。之所以如此重要,是因为SubjectPublicKeyInfo(几乎总是)以DER编码(由 ITU-T X.690 ),它使用长度前缀的值。 ASN.1( ITU-T X.680 )在 RFC 5280 中定义为

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. 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. The ASN.1 (ITU-T X.680) is defined in RFC 5280 as

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

RSA的AlgorithmIdentifier的编码值为

The encoded value for the AlgorithmIdentifier for RSA is

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

(aka SEQUENCE(< a href = http://www.oid-info.com/get/1.2.840.113549.1.1.1 rel = nofollow noreferrer> OID( 1.2.840.113549.1.1.1), NULL))

(aka SEQUENCE(OID("1.2.840.113549.1.1.1"), NULL))

subjectPublicKey 的值取决于算法。对于RSA,它是 RSAPublicKey ,在 RFC 3447 as

The value for subjectPublicKey depends on the algorithm. For RSA it's RSAPublicKey, defined in RFC 3447 as

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

一个INTEGER的编码为02(则为长度)然后签名的大端值。因此,假设您的指数值是 01 00 01 ,则编码值是 02 03 01 00 01 。模数的长度取决于密钥的大小。

The encoding for an INTEGER is 02 (then the length) then the signed big-endian value. So, assuming that your Exponent value is 01 00 01 the encoded value is 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应该始终创建需要额外字节的密钥,但从技术上讲,可以存在不需要的密钥。我们需要它的原因是parameters.Modulus是UNsigned big-endian编码,如果设置了高位,则我们将在RSAPublicKey中编码一个负数。我们通过插入00字节来保持符号位清晰来解决该问题。

RSACryptoServiceProvider should always create keys that need the extra byte, but technically keys could exist which don't. 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. We fix that by inserting an 00 byte to keep the sign bit clear.

模数的长度字节有些棘手。如果模数可表示为127个字节或更少(RSA-1015或更小),则只需为该值使用一个字节。否则,您需要报告的最小字节数加1。这个额外的字节(实际上是第一个字节)表示长度为多少个字节。因此128-255是一个字节, 81 。 256-65535是2,所以 82

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. Otherwise you need the smallest number of bytes to report the number, plus one. That extra byte (the first one, actually) says how many bytes the length is. So 128-255 is one byte, 81. 256-65535 is two, so 82.

然后我们需要将其包装到BIT STRING值中很容易(如果我们忽略了困难的部分,因为它们与此处无关)。然后将其他所有内容包装在一个SEQUENCE中,很容易。

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). And then wrap everything else up in a SEQUENCE, which is easy.

又快又脏,仅适用于指数= 0x010001的2048位密钥:

Quick and dirty, only works on a 2048-bit key with exponent=0x010001:

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

或者,对于通用响应(会创建大量临时字节) [] s):

Or, for a general-purpose response (that creates a lot of temporary 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))));
}

您实际上不需要编码的私钥。但是,如果确实需要,则需要通用方法,因为私钥数据的可变性很大。

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 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。

It also says the current version number is 0.

私钥的八位位组字符串由算法定义。对于RSA,我们在RFC 3447中看到了 RSAPublicKey

The octet string of the private key is defined by the algorithm. For RSA we see in RFC 3447, along with 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。

Ignore otherPrimeInfos. It doesn't, and shouldn't ever, apply. Therefore the version number to use is 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))));
}

让这一切变得更容易的是.NET Core的功能路线图(< a href = https://github.com/dotnet/corefx/issues/20414 rel = nofollow noreferrer> https://github.com/dotnet/corefx/issues/20414 -不说出口,但通常有出口:))

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 :))

将输出保存到文件中,可以使用 openssl rsa进行检查-通知der -pubin-文本-in pub.key openssl rsa-通知der -text -in priv.key

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

这篇关于如何像Java中那样使用C#的公共/私有RSA密钥?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆