首页 > 解决方案 > 在 C# 中导入和导出 RSA 密钥

问题描述

我正在尝试导出和重新导入公共和私有 RSA 密钥。我使用 System.Security.Cryptography.RSA 类来做到这一点。但它没有按预期工作。请假设以下代码示例

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 函数上找不到密钥。我不是密码学专家,我知道还有其他方法可以实现我正在做的事情(有很多例子),但它们都有些复杂。这在这里看起来很简单。但为什么它不起作用?重新导入后密钥应该不一样吗?

标签: c#.net-coreencryptionrsaprivate-key

解决方案


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

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

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

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

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


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

注释中描述的场景也可以解释:
如果导入以相反的顺序进行,即首先是公钥,然后是私钥,则实际上只导入私钥(第 1 节)。这可用于加密(第 2 节)和解密。
如果根本不进行导出/导入,则 RSA 对象会封装初始私钥,该私钥也可用于加密和解密。
所以两者都可以工作并且不会抛出异常。


推荐阅读