如何从.NET读取PEM RSA私钥

99

我有一个PEM格式的RSA私钥,是否有一种直接的方法可以从.NET中读取并实例化RSACryptoServiceProvider以解密使用相应公钥加密的数据?


1
这里有很多答案,但请注意PEM不是私钥的特定格式。它基本上是一个包装器,将二进制编码的私钥转换为文本 - 所谓的ASCII装甲。现在内部格式可以是PKCS#1格式的私钥(只是私钥,没有指示它是RSA密钥),未加密的PKCS#8格式的私钥(仅“内部”PKCS#8)或使用密钥或密码短语包装的PKCS#8私钥。这使读者能够为每个3种可能性选择正确的答案。 - Maarten Bodewes
1
如果它是PKCS#1私钥,那么PEM头部应该有"RSA PRIVATE KEY"。如果它是PKCS#8,则只需读取"PRIVATE KEY" - 因为算法已经在二进制编码中了。如果它受到密码保护,那么通常会期望一些参数跟随初始标题行,尽管这很不幸是严格可选的。 - Maarten Bodewes
10个回答

97

2021年3月3日更新

.NET 5现在原生支持此功能。

要尝试下面的代码片段,请在http://travistidwell.com/jsencrypt/demo/上生成密钥对并加密一些文本。

var privateKey = @"-----BEGIN RSA PRIVATE KEY-----
{ the full PEM private key } 
-----END RSA PRIVATE KEY-----";

var rsa = RSA.Create();
rsa.ImportFromPem(privateKey.ToCharArray());

var decryptedBytes = rsa.Decrypt(
    Convert.FromBase64String("{ base64-encoded encrypted string }"), 
    RSAEncryptionPadding.Pkcs1
);

// this will print the original unencrypted string
Console.WriteLine(Encoding.UTF8.GetString(decryptedBytes));

原始回答

我解决了,谢谢。 如果有人感兴趣,bouncycastle 就是解决方案,只是由于我的知识不足和文档缺乏而花费了我一些时间。 这是代码:

var bytesToDecrypt = Convert.FromBase64String("la0Cz.....D43g=="); // string to decrypt, base64 encoded
 
AsymmetricCipherKeyPair keyPair; 
 
using (var reader = File.OpenText(@"c:\myprivatekey.pem")) // file containing RSA PKCS1 private key
    keyPair = (AsymmetricCipherKeyPair) new PemReader(reader).ReadObject(); 
 
var decryptEngine = new Pkcs1Encoding(new RsaEngine());
decryptEngine.Init(false, keyPair.Private); 
 
var decrypted = Encoding.UTF8.GetString(decryptEngine.ProcessBlock(bytesToDecrypt, 0, bytesToDecrypt.Length)); 

59
@WildJoe: 要么是供蝴蝶居住的充气栖息地,要么是www.bouncycastle.org ;) - das_weezul
2
一个受密码保护的证书呢? - Sinaesthetic
4
RSA并非用于加密长消息,而是用于加密采用对称算法AES加密的长消息的密钥。换句话说,如果我想要加密一个非常长的消息,我将创建一个密钥,使用该密钥对长消息进行AES加密,并使用RSA加密密钥,将两个加密字符串一起发送给接收者。 - therightstuff
3
现在甚至可以从加密文件中导入,例如使用System.Security.Cryptography.RSA.ImportFromEncryptedPem。请注意,我已经尽力让翻译内容更为简洁和易懂,但没有改变原意。 - Radall
3
您真心应该得到全世界的赞,13年后回答自己的问题,并帮助那些在2022年有同样问题的可怜人。 - Jens Caasen
显示剩余5条评论

36

关于轻松导入RSA私钥,而不使用第三方代码(如BouncyCastle),我认为答案是“否,仅凭私钥的PEM格式无法实现”。

然而,正如Simone所提到的,您可以简单地将私钥的PEM(*.key)和使用该密钥(*.crt)的证书文件的PEM组合成一个*.pfx文件,然后轻松导入该文件。

要从命令行生成PFX文件:

openssl pkcs12 -in a.crt -inkey a.key -export -out a.pfx

然后可以像这样使用.NET证书类:

using System.Security.Cryptography.X509Certificates;

X509Certificate2 combinedCertificate = new X509Certificate2(@"C:\path\to\file.pfx");

现在你可以按照MSDN的示例,通过RSACryptoServiceProvider进行加密和解密:

我省略了解密需要使用PFX密码和Exportable标志导入的步骤。(参见:BouncyCastle RSAPrivateKey转为.NET RSAPrivateKey)

X509KeyStorageFlags flags = X509KeyStorageFlags.Exportable;
X509Certificate2 cert = new X509Certificate2("my.pfx", "somepass", flags);

RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey;
RSAParameters rsaParam = rsa.ExportParameters(true); 

6
有很多例子假定我们想要使用命令行工具(特别是openssl)的负担...我希望/需要一个完全基于程序的解决方案。此外,我所在的环境无法使用.NET Standard 2.1(又名.NET Core 3.0+/5.0)。 - Greg Vogel

33
你可以看一下JavaScienceOpenSSLKey源代码,里面有与你想要实现的功能完全相同的代码。实际上,他们还提供了大量的加密源代码

//------- Parses binary ans.1 RSA private key; returns RSACryptoServiceProvider  ---
public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
        byte[] MODULUS, E, D, P, Q, DP, DQ, IQ ;

        // ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
        MemoryStream  mem = new MemoryStream(privkey) ;
        BinaryReader binr = new BinaryReader(mem) ;    //wrap Memory Stream with BinaryReader for easy reading
        byte bt = 0;
        ushort twobytes = 0;
        int elems = 0;
        try {
                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                        binr.ReadByte();        //advance 1 byte
                else if (twobytes == 0x8230)
                        binr.ReadInt16();       //advance 2 bytes
                else
                        return null;

                twobytes = binr.ReadUInt16();
                if (twobytes != 0x0102) //version number
                        return null;
                bt = binr.ReadByte();
                if (bt !=0x00)
                        return null;


                //------  all private key components are Integer sequences ----
                elems = GetIntegerSize(binr);
                MODULUS = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                E = binr.ReadBytes(elems) ;

                elems = GetIntegerSize(binr);
                D = binr.ReadBytes(elems) ;

                elems = GetIntegerSize(binr);
                P = binr.ReadBytes(elems) ;

                elems = GetIntegerSize(binr);
                Q = binr.ReadBytes(elems) ;

                elems = GetIntegerSize(binr);
                DP = binr.ReadBytes(elems) ;

                elems = GetIntegerSize(binr);
                DQ = binr.ReadBytes(elems) ;

                elems = GetIntegerSize(binr);
                IQ = binr.ReadBytes(elems) ;

                Console.WriteLine("showing components ..");
                if (verbose) {
                        showBytes("\nModulus", MODULUS) ;
                        showBytes("\nExponent", E);
                        showBytes("\nD", D);
                        showBytes("\nP", P);
                        showBytes("\nQ", Q);
                        showBytes("\nDP", DP);
                        showBytes("\nDQ", DQ);
                        showBytes("\nIQ", IQ);
                }

                // ------- create RSACryptoServiceProvider instance and initialize with public key -----
                RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
                RSAParameters RSAparams = new RSAParameters();
                RSAparams.Modulus =MODULUS;
                RSAparams.Exponent = E;
                RSAparams.D = D;
                RSAparams.P = P;
                RSAparams.Q = Q;
                RSAparams.DP = DP;
                RSAparams.DQ = DQ;
                RSAparams.InverseQ = IQ;
                RSA.ImportParameters(RSAparams);
                return RSA;
        }
        catch (Exception) {
                return null;
        }
        finally {
                binr.Close();
        }
}

尝试过了,但不起作用,还没有花时间仔细查看代码,希望有更简单的解决方案。 - Simone
1
我刚从这个SO问题中发现了这个答案的交叉链接。现在我看到它是实际的代码文件,而且它们只是解码ASN.1编码数据,这个答案应该得到更多的+1。(他们真的不应该称自己为“JavaScience”) - Ian Boyd
它适用于我的场景,即使用RSA私钥加密的DES3密码短语。但是,代码很混乱;有对Console.ReadLine和WriteLine的调用;需要用户交互来输入密码等。需要重写以用作正确的库,但它可以工作! - Tahir Hassan
2
这对我也起作用了,如果有人想看一个完整的工作代码示例,这里有一个项目的示例,其中PHP脚本创建PEM格式的密钥并将其发送到C#,然后使用上面链接的库将其转换为XML:http://csharp-tricks-en.blogspot.de/2015/04/rsa-with-c-and-php.html - Oliver
2
收集了javascience的代码,用于PHP生成的公私钥组合中与私钥相关的特定功能 -- 运行得非常好。如果我可以为那个不宣传“Bouncy Castle”或其他臃肿库的人点赞1000次,那就太棒了。非常感谢!! - Kraang Prime
显示剩余3条评论

4

之间的东西

-----BEGIN RSA PRIVATE KEY---- 

-----END RSA PRIVATE KEY----- 

这是PKCS#8 PrivateKeyInfo的base64编码(除非它说RSA ENCRYPTED PRIVATE KEY,否则它是EncryptedPrivateKeyInfo)。

手动解码并不难,但最好的选择是使用P / Invoke到CryptImportPKCS8


更新:自Windows Server 2008和Windows Vista起,CryptImportPKCS8函数不再可用。请改用PFXImportCertStore函数。

4

好的,我使用Mac生成自签名密钥。下面是我使用的有效方法。

我创建了一个shell脚本来加速我的密钥生成。

genkey.sh

#/bin/sh

ssh-keygen -f host.key
openssl req -new -key host.key -out request.csr
openssl x509 -req -days 99999 -in request.csr -signkey host.key -out server.crt
openssl pkcs12 -export -inkey host.key -in server.crt -out private_public.p12 -name "SslCert"
openssl base64 -in private_public.p12 -out Base64.key

给脚本添加 +x 执行标志

chmod +x genkey.sh

然后调用 genkey.sh。

./genkey.sh

我输入了一个密码(至少在最后导出时要包括一个密码,这一点很重要)

Enter pass phrase for host.key:
Enter Export Password:   {Important to enter a password here}
Verifying - Enter Export Password: { Same password here }

我将Base64.Key中的所有内容放入一个名为sslKey的字符串中。
private string sslKey = "MIIJiAIBA...................................." +
                        "......................ETC...................." +
                        "......................ETC...................." +
                        "......................ETC...................." +
                        ".............ugICCAA=";

我随后使用了一个惰性加载的属性 getter 来获取带有私钥的 X509 证书。
X509Certificate2 _serverCertificate = null;
X509Certificate2 serverCertificate{
    get
    {
        if (_serverCertificate == null){
            string pass = "Your Export Password Here";
            _serverCertificate = new X509Certificate(Convert.FromBase64String(sslKey), pass, X509KeyStorageFlags.Exportable);
        }
        return _serverCertificate;
    }
}

我选择这条路线是因为我正在使用 .net 2.0 和 Mac 上的 Mono,并且我想使用纯框架代码而没有编译的库或依赖项。

我最终使用 SslStream 来保护我的应用程序的 TCP 通信。

SslStream sslStream = new SslStream(serverCertificate, false, SslProtocols.Tls, true);

我希望这能帮助其他人。
注意:
没有密码,我无法正确地导出私钥。

4

我尝试过针对PEM编码的PKCS#8 RSA私钥进行所接受的答案,但结果是一个带有“RSA私钥中的序列无效”的“PemException”异常。原因是Org.BouncyCastle.OpenSsl.PemReader似乎只支持PKCS#1私钥。

通过转换到Org.BouncyCastle.Utilities.IO.Pem.PemReader(注意类型名称匹配!),我能够获取私钥,如下所示:

private static RSAParameters GetRsaParameters(string rsaPrivateKey)
{
    var byteArray = Encoding.ASCII.GetBytes(rsaPrivateKey);
    using (var ms = new MemoryStream(byteArray))
    {
        using (var sr = new StreamReader(ms))
        {
            var pemReader = new Org.BouncyCastle.Utilities.IO.Pem.PemReader(sr);
            var pem = pemReader.ReadPemObject();
            var privateKey = PrivateKeyFactory.CreateKey(pem.Content);

            return DotNetUtilities.ToRSAParameters(privateKey as RsaPrivateCrtKeyParameters);
        }
    }
}

仍然在Framework <=4.8中有代码的开发人员。我发现这是解决PKCS#8引起的所有麻烦的解决方案。 我建议看一下这个“https://dev59.com/HGMl5IYBdhLWcg3wXWAT”,这样你就会有更好的想法。 - Ashwin A

3
我已经创建了 PemUtils 库来实现这一点。该代码可以在 GitHub 上获取,并且可以从 NuGet 安装:
PM> Install-Package PemUtils

如果您只需要 DER 转换器:

PM> Install-Package DerConverter

从PEM数据中读取RSA密钥的用法:

using (var stream = File.OpenRead(path))
using (var reader = new PemReader(stream))
{
    var rsaParameters = reader.ReadRsaKey();
    // ...
}

2

对于不想使用Bouncy的人,以及正在尝试其他答案中包含的代码的人,我发现这些代码大多数时间都可以运行,但是在某些RSA私有字符串上会遇到问题,例如我下面提供的字符串。通过查看Bouncy代码,我调整了wprl提供的代码。

    RSAparams.D = ConvertRSAParametersField(D, MODULUS.Length);
    RSAparams.DP = ConvertRSAParametersField(DP, P.Length);
    RSAparams.DQ = ConvertRSAParametersField(DQ, Q.Length);
    RSAparams.InverseQ = ConvertRSAParametersField(IQ, Q.Length);

    private static byte[] ConvertRSAParametersField(byte[] bs, int size)
    {
        if (bs.Length == size)
            return bs;

        if (bs.Length > size)
            throw new ArgumentException("Specified size too small", "size");

        byte[] padded = new byte[size];
        Array.Copy(bs, 0, padded, size - bs.Length, bs.Length);
        return padded;
    }

-----BEGIN RSA PRIVATE KEY-----
MIIEoQIBAAKCAQEAxCgWAYJtfKBVa6Px1Blrj+3Wq7LVXDzx+MiQFrLCHnou2Fvb
fxuDeRmd6ERhDWnsY6dxxm981vTlXukvYKpIZQYpiSzL5pyUutoi3yh0+/dVlsHZ
UHheVGZjSMgUagUCLX1p/augXltAjgblUsj8GFBoKJBr3TMKuR5TwF7lBNYZlaiR
k9MDZTROk6MBGiHEgD5RaPKA/ot02j3CnSGbGNNubN2tyXXAgk8/wBmZ4avT0U4y
5oiO9iwCF/Hj9gK/S/8Q2lRsSppgUSsCioSg1CpdleYzIlCB0li1T0flB51zRIpg
JhWRfmK1uTLklU33xfzR8zO2kkfaXoPTHSdOGQIDAQABAoIBAAkhfzoSwttKRgT8
sgUYKdRJU0oqyO5s59aXf3LkX0+L4HexzvCGbK2hGPihi42poJdYSV4zUlxZ31N2
XKjjRFDE41S/Vmklthv8i3hX1G+Q09XGBZekAsAVrrQfRtP957FhD83/GeKf3MwV
Bhe/GKezwSV3k43NvRy2N1p9EFa+i7eq1e5i7MyDxgKmja5YgADHb8izGLx8Smdd
+v8EhWkFOcaPnQRj/LhSi30v/CjYh9MkxHMdi0pHMMCXleiUK0Du6tnsB8ewoHR3
oBzL4F5WKyNHPvesYplgTlpMiT0uUuN8+9Pq6qsdUiXs0wdFYbs693mUMekLQ4a+
1FOWvQECgYEA7R+uI1r4oP82sTCOCPqPi+fXMTIOGkN0x/1vyMXUVvTH5zbwPp9E
0lG6XmJ95alMRhjvFGMiCONQiSNOQ9Pec5TZfVn3M/w7QTMZ6QcWd6mjghc+dGGE
URmCx8xaJb847vACir7M08AhPEt+s2C7ZokafPCoGe0qw/OD1fLt3NMCgYEA08WK
S+G7dbCvFMrBP8SlmrnK4f5CRE3pV4VGneWp/EqJgNnWwaBCvUTIegDlqS955yVp
q7nVpolAJCmlUVmwDt4gHJsWXSQLMXy3pwQ25vdnoPe97y3xXsi0KQqEuRjD1vmw
K7SXoQqQeSf4z74pFal4CP38U3pivvoE4MQmJeMCfyJFceWqQEUEneL+IYkqrZSK
7Y8urNse5MIC3yUlcose1cWVKyPh4RCEv2rk0U1gKqX29Jb9vO2L7RflAmrLNFuA
J+72EcRxsB68RAJqA9VHr1oeAejQL0+JYF2AK4dJG/FsvvFOokv4eNU+FBHY6Tzo
k+t63NDidkvb5jIF6lsCgYEAlnQ08f5Y8Z9qdCosq8JpKYkwM+kxaVe1HUIJzqpZ
X24RTOL3aa8TW2afy9YRVGbvg6IX9jJcMSo30Llpw2cl5xo21Dv24ot2DF2gGN+s
peFF1Z3Naj1Iy99p5/KaIusOUBAq8pImW/qmc/1LD0T56XLyXekcuK4ts6Lrjkit
FaMCgYAusOLTsRgKdgdDNI8nMQB9iSliwHAG1TqzB56S11pl+fdv9Mkbo8vrx6g0
NM4DluCGNEqLZb3IkasXXdok9e8kmX1en1lb5GjyPbc/zFda6eZrwIqMX9Y68eNR
IWDUM3ckwpw3rcuFXjFfa+w44JZVIsgdoGHiXAdrhtlG/i98Rw==
-----END RSA PRIVATE KEY-----

对于我这样每两三年使用一次密码学的人来说,我不知道如何获取你的方法的长度值。希望能得到类似于PemReader的东西,可以自己计算这些值,或者至少有一些解释。 - Ernis

1

在密码应用程序块下检查http://msdn.microsoft.com/en-us/library/dd203099.aspx

不知道你是否能得到答案,但值得一试。

评论后编辑

那么,请检查此代码。

using System.Security.Cryptography;


public static string DecryptEncryptedData(stringBase64EncryptedData, stringPathToPrivateKeyFile) { 
    X509Certificate2 myCertificate; 
    try{ 
        myCertificate = new X509Certificate2(PathToPrivateKeyFile); 
    } catch{ 
        throw new CryptographicException("Unable to open key file."); 
    } 

    RSACryptoServiceProvider rsaObj; 
    if(myCertificate.HasPrivateKey) { 
         rsaObj = (RSACryptoServiceProvider)myCertificate.PrivateKey; 
    } else 
        throw new CryptographicException("Private key not contained within certificate."); 

    if(rsaObj == null) 
        return String.Empty; 

    byte[] decryptedBytes; 
    try{ 
        decryptedBytes = rsaObj.Decrypt(Convert.FromBase64String(Base64EncryptedData), false); 
    } catch { 
        throw new CryptographicException("Unable to decrypt data."); 
    } 

    //    Check to make sure we decrpyted the string 
   if(decryptedBytes.Length == 0) 
        return String.Empty; 
    else 
        return System.Text.Encoding.UTF8.GetString(decryptedBytes); 
} 

不,它不支持任何非对称算法。 - Simone
3
这段代码无法加载PEM格式的RSA私钥,它需要一个基于该私钥的证书文件,虽然可以生成该文件,但我希望避免这一步骤。 - Simone
好的,那告诉我你的私钥在哪里? - João Augusto
如果你看了我对之前回答的回复,你会发现我已经尝试过了,但是没有成功。 - Simone
没有注意到,我删除了最后一部分,因为它与第一个答案相同。 - João Augusto

-1
private static PrivateKey getPrivateKey(String privateKeyPath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        KeyFactory factory = KeyFactory.getInstance("RSA");

        try (FileReader keyReader = new FileReader(privateKeyPath);
             PemReader pemReader = new PemReader(keyReader)) {

            PemObject pemObject = pemReader.readPemObject();
            byte[] content = pemObject.getContent();
            PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
            return (PrivateKey) factory.generatePrivate(privKeySpec);

        }
    }

1
感谢您对Stack Overflow社区做出贡献的兴趣。这个问题已经有很多答案了,其中一个答案已经得到社区广泛验证。您确定您的方法之前没有被提到过吗?如果是这样的话,能否解释一下您的方法与众不同的地方,在什么情况下您的方法可能更好,并且为什么您认为之前的答案不够满意。您可以编辑您的回答并提供解释吗? - Jeremy Caney

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接