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

我有一些服务器,可以通过加密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,模量,指数中的哪一个?与Java示例相同的方法是什么,但是在C#中呢?

参考方案

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

关于从SubjectPublicKeyInfo创建RSAParameter的问题很多(例如Correctly Create RSACryptoServiceProvider from public key),反之则不多。

如果您通过RSACryptoServiceProvider创建密钥,那么新密钥将始终具有0x010001的指数值,这意味着您必须应对的唯一可变大小的数据就是模量值。之所以如此重要,是因为SubjectPublicKeyInfo(几乎总是)以DER编码(由ITU-T X.690定义),而DER使用长度前缀的值。 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

(aka 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值。因此,假设您的指数值是01 00 01,则编码值是02 03 01 00 01。模数长度取决于密钥的大小。

int modulusBytes = parameters.Modulus.Length;

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

RSACryptoServiceProvider应该始终创建需要额外字节的密钥,但是从技术上讲,可以存在不需要的密钥。我们需要它的原因是parameters.Modulus是UNsigned big-endian编码,如果设置了高位,那么我们将在RSAPublicKey中编码一个负数。我们通过插入00字节来保持符号位清晰来解决此问题。

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

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.keyopenssl rsa -inform der -text -in priv.key进行检查

JAVA:字节码和二进制有什么区别? - java

java字节代码(已编译的语言,也称为目标代码)与机器代码(当前计算机的本机代码)之间有什么区别?我读过一些书,他们将字节码称为二进制指令,但我不知道为什么。 参考方案 字节码是独立于平台的,在Windows中运行的编译器编译的字节码仍将在linux / unix / mac中运行。机器代码是特定于平台的,如果在Windows x86中编译,则它将仅在Win…

java:继承 - java

有哪些替代继承的方法? java大神给出的解决方案 有效的Java:偏重于继承而不是继承。 (这实际上也来自“四人帮”)。他提出的理由是,如果扩展类未明确设计为继承,则继承会引起很多不正常的副作用。例如,对super.someMethod()的任何调用都可以引导您通过未知代码的意外路径。取而代之的是,持有对本来应该扩展的类的引用,然后委托给它。这是与Eric…

Java:BigInteger,如何通过OutputStream编写它 - java

我想将BigInteger写入文件。做这个的最好方式是什么。当然,我想从输入流中读取(使用程序,而不是人工)。我必须使用ObjectOutputStream还是有更好的方法?目的是使用尽可能少的字节。谢谢马丁 参考方案 Java序列化(ObjectOutputStream / ObjectInputStream)是将对象序列化为八位字节序列的一种通用方法。但…

从较小的字节数组(Java)进行长时间转换 - java

我正在尝试将字节数组转换为long,但是接收到BufferUnderflowException ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); byte[] arg1 = new byte[] {0x04, (byte)0xB0}; buffer.put(arg1, 0, arg1.length); …

Java-如何将此字符串转换为日期? - java

我从服务器收到此消息,我不明白T和Z的含义,2012-08-24T09:59:59Z将此字符串转换为Date对象的正确SimpleDateFormat模式是什么? java大神给出的解决方案 这是ISO 8601标准。您可以使用SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM…