在.NET中使用PEM编码的RSA私钥

4

我有一个形如以下内容的私钥:

-----BEGIN RSA PRIVATE KEY----- 一些私钥数据 -----END RSA PRIVATE KEY-----

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


1
那么问题是什么?你尝试过什么吗?密钥是以什么样的媒介提供给你的? - DHN
您是要使用这个密钥进行发货吗?还是从磁盘上读取,由用户提供?或者……? - jglouie
我从一个Web服务中获取密钥。我需要使用它来解密加密数据,但我不知道如何在.NET中使用它。我记得.NET有自己的私钥格式,它是基于XML的... - Davita
5个回答

17
  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;
}

我的问题是:我应该如何知道这个方法? :) 为什么它还没有出现在Microsoft RSA库中? - Alexander

2

虽然这是一篇较旧的帖子,但我认为我可以提供自己的答案来回答这个问题,因为我今年早些时候也遇到了同样的挑战。

我编写了一个库来处理PEM密钥、加密、解密、签名和签名验证。附有一个完整的示例解决方案(Load-Encrypt-Decrypt-Save),所以你应该能够在很短的时间内上手。

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

祝好, Christoffer


尝试了你的代码。我的密钥是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 顺便说一下,通过设置断点,我可以看到base64BlockSize = 258字节。 请帮助我解决这个问题。 - Khurram Majeed

2

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


为了澄清,.NET基类库不支持PEM格式,对吧?:( - Davita
1
在PEM中不行。但是,您可以手动剥离头部,对数据进行base64解码,并获取PKCS8PrivateKeyInfo的数据。有关更多信息,请参见此问题:https://dev59.com/3renzYgBFxS5KdRjENbW - Nickolay Olshevsky

1
以下是我关于数字签名的文章,提供了一个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;
  }

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

提示:RSA 私钥应具有以下格式:

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

{base64 格式值}

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


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