【问题标题】:How to read a PEM RSA private key from .NET如何从 .NET 读取 PEM RSA 私钥
【发布时间】:2010-09-19 14:36:42
【问题描述】:

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

【问题讨论】:

  • 这里有不少答案,但请注意PEM不是私钥的特定格式。它基本上是将二进制编码的私钥转换为文本的包装器 - 所谓的 ASCII 盔甲。现在里面的格式可以是一个 PKCS#1 格式的私钥(只是私钥,没有表明它是 RSA 密钥),一个未加密的 PKCS#8 格式的私钥(只有“内部”PKCS#8)或使用密钥或密码短语包装的 PKCS#8 私钥。这让读者可以为每 3 种可能性选择正确的答案。
  • 如果它是 PKCS#1 私钥,那么 PEM 那么它应该在标题中包含“RSA PRIVATE KEY”。如果是 PKCS#8,那么它只会读取“PRIVATE KEY”——因为算法无论如何都是二进制编码的。如果它是受密码保护的,那么您通常希望在初始标题行之后有一些参数,尽管不幸的是,这完全是可选的。

标签: c# .net cryptography rsa


【解决方案1】:

2021 年 3 月 3 日更新

.NET 5 现在支持此功能。

要尝试下面的代码 sn-p,请生成一个密钥对并在 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)); 

【讨论】:

  • @WildJoe:要么是君主的膨胀栖息地,要么是 www.bouncycastle.org ;)
  • 受密码保护的证书怎么样?
  • new PemReader(reader) 在这之后,PemReader 为空,我看不懂,你有没有想法,通常 pem 没有问题,它可以工作..
  • @KhurramMajeed RSA 并非设计用于加密长消息,它用于加密长消息通过 AES 等对称算法加密的秘密。换句话说,如果我想加密一个很长的消息,我会创建一个秘密,使用秘密对长消息进行 AES 加密,然后使用 RSA 对秘密进行加密,并将两个加密字符串一起发送给接收者。
  • 现在甚至可以从加密导入,例如System.Security.Cryptography.RSA.ImportFromEncryptedPem
【解决方案2】:

我已经为 PEM 编码的 PKCS#8 RSA 私钥尝试了the accepted answer,结果是PemExceptionmalformed sequence in RSA private key 消息。原因是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);
        }
    }
}

【讨论】:

    【解决方案3】:

    您可以查看JavaScience's OpenSSLKey 的来源

    里面有代码可以做你想做的事。

    其实他们有很多加密源代码available here


    源码sn-p:

    //------- 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();
            }
    }
    

    【讨论】:

    • 试过了,不行,还没有花时间看代码,希望有更简单的解决方案。
    • 您能否详细说明失败的原因?我看了一下代码,似乎它应该可以工作。也许您甚至可以发布 PEM 文件? (如果你有一个不敏感的)。
    • 我刚刚发现这个答案与this SO question 交叉链接。现在我看到它是实际的代码文件,它们实际上只是在解码 ASN.1 编码数据,这个答案值得更多 +1。 (他们真的不应该自称JavaScience
    • 这也对我有用,如果有人想查看一个完整的代码示例,这里是一个项目,其中 PHP 脚本创建 PEM 格式的密钥并将其发送到 C#,然后使用上述链接库将其转换为 XML:csharp-tricks-en.blogspot.de/2015/04/rsa-with-c-and-php.html
    • 在 PHP 中生成的 pub/priv 密钥组合中为与私钥相关的特定函数收集了 javascience 的代码——就像一个魅力。如果可以的话,我会投票赞成 1000 倍,因为我是那个不推广“充气城堡”或其他英国媒体报道库的人。非常感谢!!
    【解决方案4】:

    我创建的 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();
        // ...
    }
    

    【讨论】:

      【解决方案5】:

      对于不想使用 Bouncy 并且正在尝试其他答案中包含的一些代码的人,我发现该代码在大多数情况下都能正常工作,但会遇到一些 RSA 私有字符串,例如我在下面包含的那个。通过查看有弹性的代码,我将 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-----
      

      【讨论】:

        【解决方案6】:

        关于轻松导入 RSA 私钥,不使用 BouncyCastle 等 3rd 方代码,我认为答案是“不,不单独使用私钥的 PEM。”

        但是,正如 Simone 上面提到的,您可以简单地将私钥 (*.key) 的 PEM 和使用该密钥 (*.crt) 的证书文件组合成一个 *.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 to .NET RSAPrivateKey

        X509KeyStorageFlags flags = X509KeyStorageFlags.Exportable;
        X509Certificate2 cert = new X509Certificate2("my.pfx", "somepass", flags);
        
        RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey;
        RSAParameters rsaParam = rsa.ExportParameters(true); 
        

        【讨论】:

        • 有很多示例假设我们想要携带命令行工具的包袱,特别是openssl ...我想要/需要一个完全编程的解决方案。此外,.NET Standard 2.1(又名 .NET Core 3.0+/5.0)不适用于我所在的环境。
        【解决方案7】:

        好的,我正在使用 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=";
        

        然后我使用延迟加载属性获取器通过私钥获取我的 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;
            }
        }
        

        我想走这条路,因为我在 mac 上使用 .net 2.0 和 Mono,并且我想使用没有编译库或依赖项的 vanilla Framework 代码。

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

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

        我希望这对其他人有所帮助。

        注意

        没有密码我无法正确解锁导出私钥。

        【讨论】:

          【解决方案8】:

          查看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); 
          } 
          

          【讨论】:

          • 不,它不支持任何非对称算法。
          • 这段代码无法加载PEM rsa私钥,它需要一个基于该密钥的证书文件,可以生成,但我想避免这一步。
          • 好的,那么告诉我你的私钥在哪里?
          • 如果您阅读我对上一个答案的回复,您会发现我已经尝试过但没有成功。
          • 没注意到,我删除了最后一部分,因为它等于第一个答案。
          【解决方案9】:

          之间的东西

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

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

          是 PKCS#8 PrivateKeyInfo 的 base64 编码(除非它说 RSA ENCRYPTED PRIVATE KEY 在这种情况下它是一个 EncryptedPrivateKeyInfo)。

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


          更新:CryptImportPKCS8 功能不再可用于 Windows Server 2008 和 Windows Vista。相反,请使用 PFXImportCertStore 函数。

          【讨论】:

            猜你喜欢
            • 2011-11-05
            • 2023-02-07
            • 2013-01-16
            • 1970-01-01
            • 2012-05-21
            相关资源
            最近更新 更多