在 C# 中导入和导出 RSA 密钥

Importing and Exporting RSA Keys in C#

我正在尝试导出和重新导入 public 和 RSA 私钥。我使用 System.Security.Cryptography.RSA class 来完成。但它没有按预期工作。请假设以下代码示例

RSA rsa = RSA.Create();
File.WriteAllBytes("TestPrivateKey", rsa.ExportRSAPrivateKey());
File.WriteAllBytes("TestPublicKey", rsa.ExportRSAPublicKey());
rsa.ImportRSAPrivateKey(File.ReadAllBytes("TestPrivateKey"), out _);
rsa.ImportRSAPublicKey(File.ReadAllBytes("TestPublicKey"), out _);

string textToBeEncrypted = "Hello World";
byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(textToBeEncrypted);
byte[] encryptedBytes = rsa.Encrypt(bytesToBeEncrypted, RSAEncryptionPadding.OaepSHA256);

byte[] decryptedBytes = rsa.Decrypt(encryptedBytes, RSAEncryptionPadding.OaepSHA256);
string result = Encoding.UTF8.GetString(decryptedBytes);

当我不使用导出和导入时,加密和解密工作正常。但是当我使用它们时,我得到一个 Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException 说明在 rsa.Decrypt 函数上找不到密钥。 我不是密码学专家,我知道还有其他方法可以实现我正在做的事情(那里有很多例子),但它们都有些复杂。这在这里看起来很简单。但为什么它不起作用?重新导入后密钥应该不一样吗?

RSA.Create() returns 封装私钥的对象。对象的类型取决于平台,例如在 Windows 10 机器上的 .NET Core 3.1 下,创建了一个 RsaCng 实例,它封装了一个 2048 位私有密钥。在下文中,对象表示为 RSA 对象.

从这样创建的 RSA 对象,RSA 私钥可以以不同的格式导出。由于私钥也包含public密钥数据,所以public密钥也可以导出
同样,可以将私有或 public RSA 密钥导入到 RSA 对象中。因此,先前的密钥将被覆盖。在双重导入的情况下也会发生这种情况。第二次导入的密钥覆盖第一次导入的密钥 (1)。
为了完整性:export/import 方法使用 PKCS#1 格式的 export/import 私钥和 public 密钥,DER 编码。它们仅在 .NET Core 中受支持,此处仅从 3.0 版开始支持。

此外,.NET 允许使用封装私钥的 RSA 对象进行加密 (2)。这纯粹是 正式的 ,为了加密,使用 public 密钥的数据(如前所述,私钥也包含),即 在这种情况下,有效地 也是使用 public 密钥加密的。

至此,贴出的代码中抛出的异常可以得到解释:
使用 RSA.Create() 创建一个带有私钥的 RSA 对象。私钥和 public 密钥被导出并再次导入,首先是私钥,然后是 public 密钥。双重导入导致私钥被 public 密钥覆盖,因此实际上只导入了 public 密钥(第 1 节)。这允许加密但不允许解密,从而导致相应的异常。

如评论中所述,使用单独的 RSA 对象进行加密和解密是有意义的,这样每个 RSA 对象只需要导入一次。这会自动避免一个导入在错误的时间覆盖另一个。此外,这种实现更符合实际,因为加密和解密通常是分开实现的,因此不可避免地使用不同的RSA对象。


编辑:关于评论中的场景:

评论中描述的场景也可以解释:
如果导入以相反的顺序进行,即首先是 public 密钥,然后是私有密钥,则实际上只有私有密钥被导入(第 1 节)。这可用于加密(第 2 节)和解密。
如果根本没有发生export/import,RSA对象封装了初始私钥,也可以用来加解密
所以既能工作又不会抛出异常。