gpt4 book ai didi

java - 如何调整 C# 的公钥/私钥 RSA key 以像在 Java 中一样使用它们?

转载 作者:行者123 更新时间:2023-12-02 11:31:21 35 4
gpt4 key购买 nike

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

为此,我需要创建公共(public)和私有(private) RSA key 并将它们转换为字节数组。我有 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 示例中这些 key 看起来像单个联合 key 。我应该使用 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 创建 RSAParameters 有很多问题(例如 Correctly create RSACryptoServiceProvider from public key ),但反之则没有那么多。

如果您通过 RSACryptoServiceProvider 创建 key ,则新 key 的指数值始终为 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

(又名序列(OID("1.2.840.113549.1.1.1"),NULL))

subjectPublicKey 的值取决于算法。对于 RSA,它是 RSAPublicKey,在 RFC 3447 中定义。作为

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

INTEGER 的编码是 02(然后是长度),然后是带符号的大端值。因此,假设您的指数值为 01 00 01,则编码值为 02 03 01 00 01。模数长度取决于 key 的大小。

int modulusBytes = parameters.Modulus.Length;

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

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

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

然后我们需要将其包装成一个位字符串值,这很容易(如果我们忽略困难部分,因为它们在这里不相关)。然后将其他所有内容包装在一个序列中,这很容易。

快速而肮脏,仅适用于指数 = 0x010001 的 2048 位 key :

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

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

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 - 如何调整 C# 的公钥/私钥 RSA key 以像在 Java 中一样使用它们?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49282558/

35 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com