【问题标题】:Use PEM encoded RSA private key in .NET在 .NET 中使用 PEM 编码的 RSA 私钥
【发布时间】:2013-01-16 16:25:30
【问题描述】:

我有一个如下所示的私钥:

-----开始 RSA 私钥-- 一些私钥数据 -----结束 RSA 隐私

我需要在我的 C# 项目中使用此密钥,但我找不到任何示例如何以这种格式使用密钥。谢谢

【问题讨论】:

  • 那么问题是什么?你有什么尝试吗?提供给您的密钥是通过什么媒介提供的?
  • 您是否尝试使用此密钥发货?或从用户提供的磁盘读取它?还是...?
  • 我从 Web 服务收到密钥。我需要用它来解密加密数据,但我不知道如何在 .NET 中使用它。我记得 .NET 有它自己的基于 xml 的私钥格式...

标签: c# .net cryptography rsa private-key


【解决方案1】:

首先,您需要使用Bouncy Castle 库将私钥转换为RSA 参数的形式。然后您需要将 RSA 参数作为私钥传递给 RSA 算法。最后,您使用JWT 库对令牌进行编码和签名。

    public string GenerateJWTToken(string rsaPrivateKey)
    {
        var rsaParams = GetRsaParameters(rsaPrivateKey);
        var encoder = GetRS256JWTEncoder(rsaParams);

        // create the payload according to your need
        var payload = new Dictionary<string, object>
        {
            { "iss", ""},
            { "sub", "" },
            // and other key-values 
        };

        var token = encoder.Encode(payload, new byte[0]);

        return token;
    }

    private static IJwtEncoder GetRS256JWTEncoder(RSAParameters rsaParams)
    {
        var csp = new RSACryptoServiceProvider();
        csp.ImportParameters(rsaParams);

        var algorithm = new RS256Algorithm(csp, csp);
        var serializer = new JsonNetSerializer();
        var urlEncoder = new JwtBase64UrlEncoder();
        var encoder = new JwtEncoder(algorithm, serializer, urlEncoder);

        return encoder;
    }

    private static RSAParameters GetRsaParameters(string rsaPrivateKey)
    {
        var byteArray = Encoding.ASCII.GetBytes(rsaPrivateKey);
        using (var ms = new MemoryStream(byteArray))
        {
            using (var sr = new StreamReader(ms))
            {
                // use Bouncy Castle to convert the private key to RSA parameters
                var pemReader = new PemReader(sr);
                var keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
                return DotNetUtilities.ToRSAParameters(keyPair.Private as RsaPrivateCrtKeyParameters);
            }
        }
    }

ps:RSA 私钥应采用以下格式:

-----开始 RSA 私钥--

{base64 格式的值}

-----结束 RSA 私钥-----

【讨论】:

    【解决方案2】:
    1. 步骤 1 获取“一些私钥数据”内容。删除 -----BEGIN RSA PRIVATE KEY----- 和 -----END RSA PRIVATE KEY-----,删除所有行符号( "\n");
    2. 第 2 步。将密钥解析为 RSA。
    private RSACryptoServiceProvider CreateRsaProviderFromPrivateKey(string privateKey)
    {
        var privateKeyBits = System.Convert.FromBase64String(privateKey);
    
        var RSA = new RSACryptoServiceProvider();
        var RSAparams = new RSAParameters();
    
        using (BinaryReader binr = new BinaryReader(new MemoryStream(privateKeyBits)))
        {
            byte bt = 0;
            ushort twobytes = 0;
            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130)
                binr.ReadByte();
            else if (twobytes == 0x8230)
                binr.ReadInt16();
            else
                throw new Exception("Unexpected value read binr.ReadUInt16()");
    
            twobytes = binr.ReadUInt16();
            if (twobytes != 0x0102)
                throw new Exception("Unexpected version");
    
            bt = binr.ReadByte();
            if (bt != 0x00)
                throw new Exception("Unexpected value read binr.ReadByte()");
    
            RSAparams.Modulus = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.Exponent = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.D = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.P = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.Q = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.DP = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.DQ = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.InverseQ = binr.ReadBytes(GetIntegerSize(binr));
        }
    
        RSA.ImportParameters(RSAparams);
        return RSA;
    }
    
    private int GetIntegerSize(BinaryReader binr)
    {
        byte bt = 0;
        byte lowbyte = 0x00;
        byte highbyte = 0x00;
        int count = 0;
        bt = binr.ReadByte();
        if (bt != 0x02)
            return 0;
        bt = binr.ReadByte();
    
        if (bt == 0x81)
            count = binr.ReadByte();
        else
            if (bt == 0x82)
            {
                highbyte = binr.ReadByte();
                lowbyte = binr.ReadByte();
                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
                count = BitConverter.ToInt32(modint, 0);
            }
            else
            {
                count = bt;
            }
    
        while (binr.ReadByte() == 0x00)
        {
            count -= 1;
        }
        binr.BaseStream.Seek(-1, SeekOrigin.Current);
        return count;
    }
    

    【讨论】:

      【解决方案3】:

      下面我写的关于数字签名的文章给出了一个 c# 的解决方案(不需要额外的库)。该代码显示了如何将 PEM 格式的 RSA 私钥转换为 .NET RSACryptoServiceProvider 类使用的 XML 格式。

      使用 Atlando 加密货币地理服务,您的身份会在注册后存储在您的浏览器中。在每次请求时,我们都会使用您的私钥通过此身份签署和加密合同。本文解释了它的工作原理。

      下面的代码通过比较原始版本和签名版本,给出了身份验证过程的 C#(RSACryptoServiceProvider 类)实现。模数来自 PEM 格式的 RSA 公钥(指数 AQAB)。

        private static bool Verify(string original, string signature, string modulus)
        {
          SHA256Managed sha = new SHA256Managed();
      
          byte[] bytes = Encoding.UTF8.GetBytes(original);
          byte[] hash = sha.ComputeHash(bytes);
      
          sha.Clear();
      
          byte[] signed = new byte[signature.Length/2];
      
          for (int i = 0; i < signature.Length; i += 2)
          {
            signed[i/2] = Convert.ToByte(Convert.ToInt32(signature.Substring(i, 2), 16));
          }
      
          string key = "<RSAKeyValue><Modulus>" + modulus + "</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
      
          using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
          {
            rsa.FromXmlString(key);
      
            RSAPKCS1SignatureDeformatter RSADeformatter = new RSAPKCS1SignatureDeformatter(rsa);
      
            RSADeformatter.SetHashAlgorithm("SHA256");
      
            return RSADeformatter.VerifySignature(hash, signed);
          }
        }
      
      
        public static string Modulus(string pem)
        {
          byte[] x509der = Convert.FromBase64String(pem.Replace("-----BEGIN PUBLIC KEY-----", "").Replace("-----END PUBLIC KEY-----", ""));
      
          byte[] seqOID = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
      
          MemoryStream ms = new MemoryStream(x509der);
          BinaryReader reader = new BinaryReader(ms);
      
          if (reader.ReadByte() == 0x30) ReadASNLength(reader); //skip the size
          else return null;
      
          int identifierSize = 0; //total length of Object Identifier section
      
          if (reader.ReadByte() == 0x30) identifierSize = ReadASNLength(reader);
          else return null;
      
          if (reader.ReadByte() == 0x06) //is the next element an object identifier?
          {
            int oidLength = ReadASNLength(reader);
            byte[] oidBytes = new byte[oidLength];
            reader.Read(oidBytes, 0, oidBytes.Length);
      
            if (oidBytes.SequenceEqual(seqOID) == false) return null; //is the object identifier rsaEncryption PKCS#1?
      
            int remainingBytes = identifierSize - 2 - oidBytes.Length;
            reader.ReadBytes(remainingBytes);
          }
      
          if (reader.ReadByte() == 0x03) //is the next element a bit string?
          {
            ReadASNLength(reader); //skip the size
            reader.ReadByte(); //skip unused bits indicator
            if (reader.ReadByte() == 0x30)
            {
              ReadASNLength(reader); //skip the size
              if (reader.ReadByte() == 0x02) //is it an integer?
              {
                int modulusSize = ReadASNLength(reader);
                byte[] modulus = new byte[modulusSize];
                reader.Read(modulus, 0, modulus.Length);
                if (modulus[0] == 0x00) //strip off the first byte if it's 0
                {
                  byte[] tempModulus = new byte[modulus.Length - 1];
                  Array.Copy(modulus, 1, tempModulus, 0, modulus.Length - 1);
                  modulus = tempModulus;
                }
      
                if (reader.ReadByte() == 0x02) //is it an integer?
                {
                  int exponentSize = ReadASNLength(reader);
                  byte[] exponent = new byte[exponentSize];
                  reader.Read(exponent, 0, exponent.Length);
      
                  RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
                  RSAParameters RSAKeyInfo = new RSAParameters();
                  RSAKeyInfo.Modulus = modulus;
                  RSAKeyInfo.Exponent = exponent;
                  rsa.ImportParameters(RSAKeyInfo);
                  return rsa.ToXmlString(false).Replace("<RSAKeyValue><Modulus>", "").Replace("</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>", "");
                }
              }
            }
          }
      
          return null;
        }
      
      
        public static int ReadASNLength(BinaryReader reader)
        {//Note: this method only reads lengths up to 4 bytes long as this is satisfactory for the majority of situations.
          int length = reader.ReadByte();
          if ((length & 0x00000080) == 0x00000080) //is the length greater than 1 byte
          {
            int count = length & 0x0000000f;
            byte[] lengthBytes = new byte[4];
            reader.Read(lengthBytes, 4 - count, count);
            Array.Reverse(lengthBytes); //
            length = BitConverter.ToInt32(lengthBytes, 0);
          }
          return length;
        }
      

      【讨论】:

        【解决方案4】:

        虽然在一篇较早的帖子中,当我在今年早些时候遇到同样的挑战时,我还是会用我自己的答案来回答这个问题。 我编写了一个用于处理 PEM 密钥、加密、解密、签名和签名验证的库。 它附带了一个完整的示例解决方案(Load-Encrypt-Decrypt-Save),因此您应该能够立即开始运行。

        https://github.com/jrnker/CSharp-easy-RSA-PEM

        干杯, 克里斯托弗

        【讨论】:

        • 试过你的代码。我的密钥是 PEM 格式,密文长度为 512 个字符(Base64),私钥为 1588 个字符(Base64)/2048 个字节。试图用私钥解密这个密文函数:byte[] DecryptBytes(string inputString, RSACryptoServiceProvider key) 抛出以下异常,The data to be decrypted exceeds the maximum for this modulus of 256 bytes FYI,通过放置一个断点我可以看到 base64BlockSize = 258 字节你能帮我解决这个问题吗?跨度>
        【解决方案5】:

        所有主要的 .NET/C# 加密库(如 BouncyCastle 或 SecureBlackbox [商业])都应支持这种格式,以及加载密钥的操作(加密/解密/签名/验证)。

        【讨论】:

        • 澄清一下,.NET 基类库不支持 PEM 格式对吧? :(
        • 在 PEM 中 - 不是。但是,您可以手动去除标头、base64 解码数据,并获取 PKCS8PrivateKeyInfo 的数据。有关更多信息,请参阅此问题:stackoverflow.com/questions/243646/…
        猜你喜欢
        • 2016-05-18
        • 2011-11-05
        • 1970-01-01
        • 2010-09-19
        相关资源
        最近更新 更多