【问题标题】:PCLCrypto PEM PublicKey to Portable.BouncyCastle or System.Security.Cryptography .NetStandard2.0 ConversionPCLCrypto PEM PublicKey 到 Portable.BouncyCastle 或 System.Security.Cryptography .NetStandard2.0 的转换
【发布时间】:2023-04-01 16:45:01
【问题描述】:

我不是安全背景,但继承了以下代码。

我希望能够将其移植到 .NET Standard 中。

public sealed class Pem
{
    private string type;
    private string base64Encoded;

    private const string PemStart = "-----BEGIN ";
    private const string PemEnd = "-----END ";
    private const string PemEndOfLine = "-----";

    public Pem(string content)
    {
        using (var reader = new StringReader(content.Trim()))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                if (line.StartsWith(PemStart) && line.EndsWith(PemEndOfLine))
                {
                    type = line.Substring(PemStart.Length, line.Length - PemStart.Length - PemEndOfLine.Length);
                }

                else if (line.StartsWith(PemEnd))
                {
                    //ignore    
                }

                else
                {
                    base64Encoded += line;
                }
            }
        }
    }

    public string Type
    {
        get { return type; }
    }

    public byte[] Decoded
    {
        get { return Convert.FromBase64String(base64Encoded); }
    }
}

public class EncryptionService : IEncryption
{
    public string Encrypt(string target)
    {
        var key = "-----BEGIN PUBLIC KEY-----\nXXXXKEYMATERIALREMOVEDXXXX\n-----END PUBLIC KEY-----";

        var pem = new Pem(key);

        byte[] keyMaterial = pem.Decoded;
        byte[] data = Encoding.UTF8.GetBytes(target);

        var provider = PCLCrypto.WinRTCrypto.AsymmetricKeyAlgorithmProvider.OpenAlgorithm(PCLCrypto.AsymmetricAlgorithm.RsaPkcs1);
        var publicKey = provider.ImportPublicKey(keyMaterial);

        byte[] cipherText = PCLCrypto.WinRTCrypto.CryptographicEngine.Encrypt(publicKey, data);

        return Convert.ToBase64String(cipherText);
    }             
}
}

任何人都可以指出我将如何使用 .NET Standard 2.0 重写它的正确方向

  • System.Security.Cryptography 或
  • Portable.BouncyCastle?

假设密钥保持为代码所示的字符串格式。

【问题讨论】:

    标签: c# encryption cryptography .net-standard


    【解决方案1】:

    抱歉,我们没有机会查看@komsky 发布的上述代码,但作为参考,我们最终使用了此答案引用的 PemKeyUtils 类 -

    How to load the RSA public key from file in C#

    如下——

    public class EncryptionService : Interfaces.IEncryption
    {
        public string Encrypt(string target)
        {
            var key = "-----BEGIN PUBLIC KEY-----\YOURKEY\n-----END PUBLIC KEY-----";
    
            var rsaProvider = PemKeyUtils.GetRSAProviderFromPemString(key);
            RSAEncyptionManager manager = new RSAEncyptionManager(rsaProvider, RSAEncryptionPadding.Pkcs1);         
    
            return Convert.ToBase64String(manager.Encrypt(target));
        }
    }
    
    public class RSAEncyptionManager {      
        private readonly RSACryptoServiceProvider _serviceProvider;
        private readonly RSAEncryptionPadding _padding;
        private RSAParameters _rSAKeyInfo;
    
        public RSAEncyptionManager(RSACryptoServiceProvider serviceProvider, RSAEncryptionPadding padding)
        {           
            _serviceProvider = serviceProvider;
            _padding = padding;
    
            _rSAKeyInfo = _serviceProvider.ExportParameters(false);
        }
    
        public byte[] Encrypt(string stringToEncrypt)
        {                                                                                   
            return _serviceProvider.Encrypt(Encoding.UTF8.GetBytes(stringToEncrypt), _padding);
        }
    
        public RSAEncyptionManager SetKey(byte[] key) {
            _rSAKeyInfo.Modulus = key;
            _serviceProvider.ImportParameters(_rSAKeyInfo);
            return this ;
        }
    }
    

    【讨论】:

      【解决方案2】:

      将你的任务分成两部分:

      1. 将 PEM 文件导入 X509Certificate2
      2. Use imported certificate with an encryption method

      您可能想尝试下面的代码来执行第一阶段。

      我正在使用它将 PEM 证书解码为 X509Certificate2 类。

      public class Crypto
      {
          /// <summary>
          /// This helper function parses an RSA private key using the ASN.1 format
          /// </summary>
          /// <param name="privateKeyBytes">Byte array containing PEM string of private key.</param>
          /// <returns>An instance of <see cref="RSACryptoServiceProvider"/> rapresenting the requested private key.
          /// Null if method fails on retriving the key.</returns>
          public static RSACryptoServiceProvider DecodeRsaPrivateKey(byte[] privateKeyBytes)
          {
              MemoryStream ms = new MemoryStream(privateKeyBytes);
              BinaryReader rd = new BinaryReader(ms);
      
              try
              {
                  byte byteValue;
                  ushort shortValue;
      
                  shortValue = rd.ReadUInt16();
      
                  switch (shortValue)
                  {
                      case 0x8130:
                          // If true, data is little endian since the proper logical seq is 0x30 0x81
                          rd.ReadByte(); //advance 1 byte
                          break;
                      case 0x8230:
                          rd.ReadInt16();  //advance 2 bytes
                          break;
                      default:
                          Debug.Assert(false);     // Improper ASN.1 format
                          return null;
                  }
      
                  shortValue = rd.ReadUInt16();
                  if (shortValue != 0x0102) // (version number)
                  {
                      Debug.Assert(false);     // Improper ASN.1 format, unexpected version number
                      return null;
                  }
      
                  byteValue = rd.ReadByte();
                  if (byteValue != 0x00)
                  {
                      Debug.Assert(false);     // Improper ASN.1 format
                      return null;
                  }
      
                  // The data following the version will be the ASN.1 data itself, which in our case
                  // are a sequence of integers.
      
                  // In order to solve a problem with instancing RSACryptoServiceProvider
                  // via default constructor on .net 4.0 this is a hack
                  CspParameters parms = new CspParameters();
                  parms.Flags = CspProviderFlags.NoFlags;
                  parms.KeyContainerName = Guid.NewGuid().ToString().ToUpperInvariant();
                  parms.ProviderType = ((Environment.OSVersion.Version.Major > 5) || ((Environment.OSVersion.Version.Major == 5) && (Environment.OSVersion.Version.Minor >= 1))) ? 0x18 : 1;
      
                  RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(parms);
                  RSAParameters rsAparams = new RSAParameters();
      
                  rsAparams.Modulus = rd.ReadBytes(Helpers.DecodeIntegerSize(rd));
      
                  // Argh, this is a pain.  From emperical testing it appears to be that RSAParameters doesn't like byte buffers that
                  // have their leading zeros removed.  The RFC doesn't address this area that I can see, so it's hard to say that this
                  // is a bug, but it sure would be helpful if it allowed that. So, there's some extra code here that knows what the
                  // sizes of the various components are supposed to be.  Using these sizes we can ensure the buffer sizes are exactly
                  // what the RSAParameters expect.  Thanks, Microsoft.
                  RSAParameterTraits traits = new RSAParameterTraits(rsAparams.Modulus.Length * 8);
      
                  rsAparams.Modulus = Helpers.AlignBytes(rsAparams.Modulus, traits.size_Mod);
                  rsAparams.Exponent = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_Exp);
                  rsAparams.D = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_D);
                  rsAparams.P = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_P);
                  rsAparams.Q = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_Q);
                  rsAparams.DP = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_DP);
                  rsAparams.DQ = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_DQ);
                  rsAparams.InverseQ = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_InvQ);
      
                  rsa.ImportParameters(rsAparams);
                  return rsa;
              }
              catch (Exception)
              {
                  Debug.Assert(false);
                  return null;
              }
              finally
              {
                  rd.Close();
              }
          }
      }
         public class Helpers
      {
          /// <summary>
          /// This helper function parses an integer size from the reader using the ASN.1 format
          /// </summary>
          /// <param name="rd"></param>
          /// <returns></returns>
          public static int DecodeIntegerSize(System.IO.BinaryReader rd)
          {
              byte byteValue;
              int count;
      
              byteValue = rd.ReadByte();
              if (byteValue != 0x02)        // indicates an ASN.1 integer value follows
                  return 0;
      
              byteValue = rd.ReadByte();
              if (byteValue == 0x81)
              {
                  count = rd.ReadByte();    // data size is the following byte
              }
              else if (byteValue == 0x82)
              {
                  byte hi = rd.ReadByte();  // data size in next 2 bytes
                  byte lo = rd.ReadByte();
                  count = BitConverter.ToUInt16(new[] { lo, hi }, 0);
              }
              else
              {
                  count = byteValue;        // we already have the data size
              }
      
              //remove high order zeros in data
              while (rd.ReadByte() == 0x00)
              {
                  count -= 1;
              }
              rd.BaseStream.Seek(-1, System.IO.SeekOrigin.Current);
      
              return count;
          }
      
          /// <summary>
          /// 
          /// </summary>
          /// <param name="pemString"></param>
          /// <param name="type"></param>
          /// <returns></returns>
          public static byte[] GetBytesFromPEM(string pemString, PemStringType type)
          {
              string header; string footer;
      
              switch (type)
              {
                  case PemStringType.Certificate:
                      header = "-----BEGIN CERTIFICATE-----";
                      footer = "-----END CERTIFICATE-----";
                      break;
                  case PemStringType.RsaPrivateKey:
                      header = "-----BEGIN RSA PRIVATE KEY-----";
                      footer = "-----END RSA PRIVATE KEY-----";
                      break;
                  default:
                      return null;
              }
      
              int start = pemString.IndexOf(header) + header.Length;
              int end = pemString.IndexOf(footer, start) - start;
              return Convert.FromBase64String(pemString.Substring(start, end));
          }
      
          /// <summary>
          /// 
          /// </summary>
          /// <param name="inputBytes"></param>
          /// <param name="alignSize"></param>
          /// <returns></returns>
          public static byte[] AlignBytes(byte[] inputBytes, int alignSize)
          {
              int inputBytesSize = inputBytes.Length;
      
              if ((alignSize != -1) && (inputBytesSize < alignSize))
              {
                  byte[] buf = new byte[alignSize];
                  for (int i = 0; i < inputBytesSize; ++i)
                  {
                      buf[i + (alignSize - inputBytesSize)] = inputBytes[i];
                  }
                  return buf;
              }
              else
              {
                  return inputBytes;      // Already aligned, or doesn't need alignment
              }
          }
      }
          public enum PemStringType
      {
          Certificate,
          RsaPrivateKey
      }
          internal class RSAParameterTraits
      {
          public RSAParameterTraits(int modulusLengthInBits)
          {
              // The modulus length is supposed to be one of the common lengths, which is the commonly referred to strength of the key,
              // like 1024 bit, 2048 bit, etc.  It might be a few bits off though, since if the modulus has leading zeros it could show
              // up as 1016 bits or something like that.
              int assumedLength = -1;
              double logbase = Math.Log(modulusLengthInBits, 2);
              if (logbase == (int)logbase)
              {
                  // It's already an even power of 2
                  assumedLength = modulusLengthInBits;
              }
              else
              {
                  // It's not an even power of 2, so round it up to the nearest power of 2.
                  assumedLength = (int)(logbase + 1.0);
                  assumedLength = (int)(Math.Pow(2, assumedLength));
                  System.Diagnostics.Debug.Assert(false);  // Can this really happen in the field?  I've never seen it, so if it happens
                  // you should verify that this really does the 'right' thing!
              }
      
              switch (assumedLength)
              {
                  case 1024:
                      this.size_Mod = 0x80;
                      this.size_Exp = -1;
                      this.size_D = 0x80;
                      this.size_P = 0x40;
                      this.size_Q = 0x40;
                      this.size_DP = 0x40;
                      this.size_DQ = 0x40;
                      this.size_InvQ = 0x40;
                      break;
                  case 2048:
                      this.size_Mod = 0x100;
                      this.size_Exp = -1;
                      this.size_D = 0x100;
                      this.size_P = 0x80;
                      this.size_Q = 0x80;
                      this.size_DP = 0x80;
                      this.size_DQ = 0x80;
                      this.size_InvQ = 0x80;
                      break;
                  case 4096:
                      this.size_Mod = 0x200;
                      this.size_Exp = -1;
                      this.size_D = 0x200;
                      this.size_P = 0x100;
                      this.size_Q = 0x100;
                      this.size_DP = 0x100;
                      this.size_DQ = 0x100;
                      this.size_InvQ = 0x100;
                      break;
                  default:
                      System.Diagnostics.Debug.Assert(false); // Unknown key size?
                      break;
              }
          }
      
          public int size_Mod = -1;
          public int size_Exp = -1;
          public int size_D = -1;
          public int size_P = -1;
          public int size_Q = -1;
          public int size_DP = -1;
          public int size_DQ = -1;
          public int size_InvQ = -1;
      }
      

      最后,您需要从程序中调用它:

             public X509Certificate2 GetCertificateFromText(string pem, string key)
          {
              byte[] certBuffer, keyBuffer;
              if (string.IsNullOrEmpty(pem))
              {
                  throw new ArgumentNullException("Missing certificate");
              }
              try
              {
                  certBuffer = Helpers.GetBytesFromPEM(pem, PemStringType.Certificate);
                  keyBuffer = Helpers.GetBytesFromPEM(pem, PemStringType.RsaPrivateKey);
                  X509Certificate2 certificate = new X509Certificate2(certBuffer, "your_key_password");
      
                  RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
                  certificate.PrivateKey = prov;
      
                  return certificate;
              }
              catch (Exception ex)
              {
                  throw new ArgumentException("Invalid certificate", ex);
              }
      
          }
      

      我在 csharpcorner 某处找到了代码,但刚才找不到原始链接。

      【讨论】:

      • 谢谢 komsky,让我花点时间查看代码。
      • 如果你想让它在非 Windows 上工作,你需要使用 RSA.Create() 而不是 RSACryptoServiceProvider 和 CspParameters。
      猜你喜欢
      • 2015-01-08
      • 2022-08-23
      • 2011-10-25
      • 2018-01-09
      • 2018-10-20
      • 2013-10-21
      • 2015-04-02
      相关资源
      最近更新 更多