如何为 ASN.1 DER 生成两个 RSA,对其模数进行填充和不填充(示例代码中的错误?)

How to generate two RSA for ASN.1 DER encoding their modulus with and without padding (bug in sample code?)

我的问题基本如下:

如何生成两种情况的 2048 位 RSA 密钥,以便它们的无符号大端模数在进行 ASN.1 DER 编码时需要前缀 0x00 而另一种不需要前缀?

我有代码片段和解释我在下面尝试做的事情。所以,我尝试这样做的理由和我使用的方法。

我把它作为上下文,因为我在生成这样的一对时遇到了问题,可能是我误解了一些东西。也可能是这种情况 0x00 前缀的要求是如此罕见,我还没有设法用随机生成来做到这一点(我不这么认为,但我不确定如何 calculate/estimate 它也不是我在网上找到的例子)。

所以,我在考虑 ASN.1 DER 编码 2048 位 RSA 模数。在 .NET(和许多其他框架)中,序列被编码为无符号大端字节数组。

这个的一个实际结果似乎是,如果模数的第一个字节在 0x00(十进制:0)和 0x7F(十进制:127)之间,那么在 DER“header”之后将不会有添加零字节。否则添加一个 0x00 字节。这样做是因为在 ASN.1 中 DER 编码发生在有符号整数上,否则该字节将被解释为负数(并且模数是无符号大端整数)。

上有一些关于这个逻辑的很好的解释。

https://crypto.stackexchange.com/questions/1795/how-can-i-convert-a-der-ecdsa-signature-to-asn-1

what is the format of RSA public key modulus and exponent in .net?

dumpasn1/asn1playground 稍作改动,以 crypto.stackexchange 中给出的示例为例,并将此处的解释从 ECDSA 修改为 RSA,我收集了模数案例的序列带前缀0x00的如下:

  1. 序列开始:0x30
  2. 序列长度:0x82 0x01 0x0a
  3. 整数标记:0x02
  4. 取模整数长度:0x82 0x01 0x01
  5. 模数前导零的整数:0x00
  6. [插入 RSA 模数字节]
  7. 一些其他的 DER 编码字节来结束序列

这里5.点是关键。仅当设置了无符号模数数组的 MSB 时才添加。

或者,在代码术语中,这就是我所看到的

var key = RSA.Create(2048);
var parameters = key.ExportParameters(includePrivateParameters: false);
var modulus = parameters.Modulus!;

// See this check here assuming MSB is in first byte in the array and this checks if the MSB bit is set.
bool isModulusMsbSet = (modulus[0] & 0x80) != 0;

// See here choosing the first version of the same array and concatenating a leading 0x00
// if the MSB is set on unsigned, big endian modulus.
var prefix = isModulusMsbSet ? new byte[] { 0x30, 0x82, 0x01, 0xa, 0x02, 0x82, 0x01, 0x01 }.Concat(new byte[] { 0x0 }).ToArray() : new byte[] { 0x30, 0x82, 0x1, 0xa, 0x2, 0x82, 0x1, 0x1 };

为了更接近我的问题和问题的原因,我尝试生成 non-padding 案例的代码:

RSA? key = null;
do
{
   var testKey = RSA.Create(2048);
   var parameters = testKey.ExportParameters(includePrivateParameters: false);
   var modulusBytes = parameters.Modulus!;

   // See here the checking of MSB is inverted from != to ==.
   // I.e. accept this key only if 0x00 padding is not needed.   
   if((modulusBytes[0] & 0x80) == 0)
   {
      key = testKey;
   }

} while(key == null);

但是这个循环永远不会停止!在我看来,这似乎表明 MSB 始终已设置!但是 RSA.Create(2048); 不应该偶尔创建一个可接受的密钥吗?是否有另一个很好的案例来为填充案例生成测试 material?

我也在部分询问,因为我不确定是否应在 ASN.1 DER 编码序言序列中考虑条件 0x00

不清楚您要解决什么问题。在我看来,您似乎正在尝试解决 XY 问题。

我不是最擅长解释这些事情的,我会尽力解释:

Which to me seem to indicate the MSB is always set!

这是意料之中的。 RSA public 密钥大小除以 8,无需提醒(例如 512、1024、2048 等)。结果,模数将消耗 KeySizeInBits / 8 个字节,并且此整数中的所有位都表示模数。由于整数的前导零被忽略(如十进制,1001 表示相同的数字 1),模数的第一位(或 MSB)必须为 1。否则它是被忽略,然后你的模数将不是 2048 位,它将是 2047 甚至更少,最多 2040 位长。这些是 RSA public 密钥模数的无效长度(没有提醒就不会除以 8)。将 RSA 模数想象成一个长位字符串,其中长度计数从设置为 1 的第一位开始。

只是一个非常说明性的例子。这个位串占多少位?

0 1 0 1 0 1 0 1

答案是 7,忽略前导零位,从 1 的第一位开始计数。并且 7 不除以 8 没有提示。还有这个:

1 0 0 0 0 0 0 0

正好是8位。在 ASN.1 中,整数是 two-complement 值,当字节中的第一位为 0 时,整数为正数,否则为负数。由于 RSA 模数中的 MSB 为 1 并且该数字是正数,因此作为编码的一部分,它在前面用零字节填充。但是,这个零字节不是 public 密钥的一部分,它是编码的一部分。

此外,模数是两个质数的乘积:N = p*q。而且产品总是奇数。这意味着 RSA 模数的最后一位也将以 1 结尾。因此对于 RSA public 密钥,模数中的第一位和最后一位设置为 1.