RSACryptoServiceProvider 是否正常工作?

Is RSACryptoServiceProvider working correctly?

我正在使用 .NET 的 RSA 实现,有两件事对我来说很奇怪。我想确认它是否正常运行。

背景

使用 System.Security.Cryptography.RSACryptoServiceProvider with 2048-bit keyword size to perform asymmetric encryption/decrpytion, initially following the example in this question, "AES 256 Encryption: public and private key how can I generate and use it .net".

作为第一个实现,这似乎可行:

public const int  CSPPARAMETERS_FLAG = 1;      // Specifies RSA: https://msdn.microsoft.com/en-us/library/ms148034(v=vs.110).aspx
public const bool USE_OAEP_PADDING   = false;
public const int  KEYWORD_SIZE       = 2048;

public static byte[] Encrypt(byte[] publicKey, byte[] dataToEncrypt)
{
    var cspParameters = new System.Security.Cryptography.CspParameters(CSPPARAMETERS_FLAG);
    byte[] encryptedData = null;

    using (var rsaProvider = new System.Security.Cryptography.RSACryptoServiceProvider(cspParameters))
    {
        try
        {
            rsaProvider.PersistKeyInCsp = false;
            rsaProvider.ImportCspBlob(publicKey);
            encryptedData = rsaProvider.Encrypt(dataToEncrypt, USE_OAEP_PADDING);
        }
        finally
        {
            rsaProvider.PersistKeyInCsp = false;
            rsaProvider.Clear();
        }
    }
    return encryptedData;
}

public static byte[] Decrypt(byte[] privateKey, byte[] dataToDecrypt)
{
    var cspParameters = new System.Security.Cryptography.CspParameters(CSPPARAMETERS_FLAG);
    byte[] encryptedData = null;

    using (var rsaProvider = new System.Security.Cryptography.RSACryptoServiceProvider(cspParameters))
    {
        try
        {
            rsaProvider.PersistKeyInCsp = false;
            rsaProvider.ImportCspBlob(privateKey);
            encryptedData = rsaProvider.Decrypt(dataToDecrypt, USE_OAEP_PADDING);
        }
        finally
        {
            rsaProvider.PersistKeyInCsp = false;
            rsaProvider.Clear();
        }
    }

    return encryptedData;
}

在进一步研究这些方法之后,我从 the example 生成的 public 密钥似乎在开始时有很多非常可预测的数据,它有 276 个字节长。

显然rsaProvider.ExportCspBlob(bool includePrivateParameters) is a functional alternative to rsaProvider.ExportParameters(bool includePrivateParameters); the main difference is that the blob is already serialized as a byte[] while the other emits the object version, RSAParameters

关于方法的两点观察:

  1. .Exponent总是0x010001$=65537$。
  2. 与序列化类型版本相比,导出的 blob 包含 17 个额外字节。
    • rsaProvider.ExportCspBlob():
      • Public 密钥为 276 字节。
      • 私钥为1172字节。
    • RSAParameters:
      • Public 密钥是 259 字节。
        • .Exponent.Length = 3
        • .Modulus .Length = 256
      • 私钥为1155字节。
        • .D .Length = 256
        • .DP .Length = 128
        • .DQ .Length = 128
        • .Exponent.Length = 3
        • .InverseQ.Length = 128
        • .Modulus .Length = 256
        • .P .Length = 128
        • .Q .Length = 128
    • 额外的 17 个字节似乎位于二进制 blob 的 header。

担忧

由此,有两点顾虑:

  1. 指数不是随机的可以吗?
  2. 多出来的17个字节是信息泄露吗?
    • 它们是否表示密钥长度和方法等选项参数?
    • 当我已经知道发送方和接收方都使用相同的 hard-coded 方法时,传输选项参数信息是个好主意吗?

问题

RSACryptoServiceProvider的行为是否值得关注,或者这些事情是否正常?

更新 1

Should RSA public exponent be only in {3, 5, 17, 257 or 65537} due to security considerations? 中,被接受的答案首先注意到:

There is no known weakness for any short or long public exponent for RSA, as long as the public exponent is "correct" (i.e. relatively prime to p-1 for all primes p which divide the modulus).

如果是这样,那么我猜 0x010001$=65537$ 的 apparently-constant 指数就足够了,只要它与 $p-1$ 互质即可。因此,大概 RSA 的 .NET 实现会检查这种情况。

但是,如果不满足该条件,RSACryptoServiceProvider 会做什么?如果它选择不同的指数,那么只要指数不是 0x010001,它似乎就会泄露有关 $p$ 的信息。或者,如果选择了不同的键,那么我们似乎可以假设指数总是 0x010001 并从序列化中省略它。

一切正常,non-alarming。

public 指数 e 很短并且 non-random 是完全可以的。 e = 216+1 = 65537 = 0x010001 是常见且安全的。一些权威机构强制执行(或包含它的某些范围)。使用它(or/and 比 public 模数的位大小大得多的东西)可以针对一些最糟糕的 RSA 填充提供一些保护。

不是,public密钥中多出的17个字节不太可能是信息泄露;它们更可能是您使用的软件为 RSA public 密钥选择的数据格式的 header 部分。我的猜测是您遇到了 answer 中详述的 MS-specific 格式(也许在字节序内),它也恰好使用 276 字节用于具有 2048 位 public模数。在那种情况下,您应该发现额外的字节总是相同的(因此它们显然没有泄漏)。还有无数更微妙的方法可以泄露有关私钥的信息,例如 public 模数本身。

实践中使用的很多RSA密钥生成器,包括我猜的RSACryptoServiceProvider,首先选择e,然后稍微避免生成素数p 这样 gcd(e, p-1) ≠ 1. 因为 e = 65537 是素数,满足 ( p % e ) ≠ 1 就足够了,这很容易检查,或者由生成 [=20 的过程保证=]p.