[practical tips] RSA asymmetric encryption and decryption and XML & PEM format exchange scheme

Time:2021-9-3

Recently, due to the consideration of interface security, a unified parameter authentication function has been implemented for the web API to prevent the request parameters from being tampered with or repeatedly executed. The parameter authentication method is basically the same as the common authentication idea, using (timestamp + sign). In order to prevent the timestamp from being changed, I use the sign algorithm (timestamp + related parameter sorting Splicing after formatting (MD5) is also unsafe at the front end, so asymmetric encryption and decryption is adopted for timestamp to ensure that the generated sign is not easy to be cracked or replaced as much as possible;

RSA encryption and decryption (i.e. asymmetric encryption and decryption)

Generate the public key and private key pair method (c#), which is in XML format by default:

public static Tuple GeneratePublicAndPrivateKeyPair()
        {
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                string publicKey = rsa.ToXmlString(false); //  Public key
                string privateKey = rsa.ToXmlString(true); //  Private key

                return Tuple.Create(publicKey, privateKey);
            }
        }

Use public key encryption: (it supports segmented encryption. Ordinary single encryption may report errors because the content is too long)

public static string RSAEncrypt(string publicKey, string rawInput)
        {
            if (string.IsNullOrEmpty(rawInput))
            {
                return string.Empty;
            }

            if (string.IsNullOrWhiteSpace(publicKey))
            {
                throw new ArgumentException("Invalid Public Key");
            }

            using (var rsaProvider = new RSACryptoServiceProvider())
            {
                var inputBytes = Encoding.UTF8.GetBytes(rawInput);// A meaningful string is converted to a byte stream
                rsaProvider.FromXmlString(publicKey);// Load public key
                int bufferSize = (rsaProvider.KeySize / 8) - 11;// Maximum length of single block
                var buffer = new byte[bufferSize];
                using (MemoryStream inputStream = new MemoryStream(inputBytes),
                     outputStream = new MemoryStream())
                {
                    while (true)
                    {// segmented encryption
                        int readSize = inputStream.Read(buffer, 0, bufferSize);
                        if (readSize <= 0)
                        {
                            break;
                        }

                        var temp = new byte[readSize];
                        Array.Copy(buffer, 0, temp, 0, readSize);
                        var encryptedBytes = rsaProvider.Encrypt(temp, false);
                        outputStream.Write(encryptedBytes, 0, encryptedBytes.Length);
                    }
                    return Convert.ToBase64String(outputStream.ToArray());// Convert to byte stream for easy transmission
                }
            }
        }

Decryption using private key: (supports segmented decryption. Ordinary single decryption may report an error because the ciphertext is too long)

public static string RSADecrypt(string privateKey,string encryptedInput)
        {
            if (string.IsNullOrEmpty(encryptedInput))
            {
                return string.Empty;
            }

            if (string.IsNullOrWhiteSpace(privateKey))
            {
                throw new ArgumentException("Invalid Private Key");
            }

            using (var rsaProvider = new RSACryptoServiceProvider())
            {
                var inputBytes = Convert.FromBase64String(encryptedInput);
                rsaProvider.FromXmlString(privateKey);
                int bufferSize = rsaProvider.KeySize / 8;
                var buffer = new byte[bufferSize];
                using (MemoryStream inputStream = new MemoryStream(inputBytes),
                     outputStream = new MemoryStream())
                {
                    while (true)
                    {
                        int readSize = inputStream.Read(buffer, 0, bufferSize);
                        if (readSize <= 0)
                        {
                            break;
                        }

                        var temp = new byte[readSize];
                        Array.Copy(buffer, 0, temp, 0, readSize);
                        var rawBytes = rsaProvider.Decrypt(temp, false);
                        outputStream.Write(rawBytes, 0, rawBytes.Length);
                    }
                    return Encoding.UTF8.GetString(outputStream.ToArray());
                }
            }
        }

If both projects are c# projects, the above two methods may be enough. However, if it is necessary to cooperate and interact with other programming languages such as web front-end and Java (for example, the web front-end is encrypted with public key and the back-end c# private key is decrypted), the normal docking may not be possible due to the different format of public key and private key [the front-end and java languages use PEM format, While c# uses XML format], when querying XML to PEM format scheme on the Internet, it is copied from:https://www.cnblogs.com/micenote/p/7862989.htmlThis article, but in fact, this article only wrote the conversion of private key XML to PEM format, did not explain how the public key XML was converted to PEM format, and only wrote that it supports obtaining content from files and then converting. The scheme is incomplete, but it gave it to me (dream on the journey,http://www.zuowenjun.cnOr zuowj. Cnblogs. Com. CN). After various verifications, I finally realized a relatively friendly conversion mode between PEM and XML format, which passed the unit test. I share it with you here.

The following is the complete XML and PEM format converter class code( Note the need to introduceBouncyCastleNuget package)

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace Zuowj.Common
{
    /// 
    ///RSA public key and private key pair format (XML and PEM) converter
    /// author:zuowenjun
    /// date:2020-12-29
    /// 
    public static class RsaKeysFormatConverter
    {
        /// 
        ///Convert XML public key to PEM public key
        /// 
        /// 
        /// 
        public static string XmlPublicKeyToPem(string xmlPublicKey)
        {
            RSAParameters rsaParam;
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.FromXmlString(xmlPublicKey);
                rsaParam = rsa.ExportParameters(false);
            }
            RsaKeyParameters param = new RsaKeyParameters(false, new BigInteger(1, rsaParam.Modulus), new BigInteger(1, rsaParam.Exponent));

            string pemPublicKeyStr = null;
            using (var ms = new MemoryStream())
            {
                using (var sw = new StreamWriter(ms))
                {
                    var pemWriter = new Org.BouncyCastle.OpenSsl.PemWriter(sw);
                    pemWriter.WriteObject(param);
                    sw.Flush();

                    byte[] buffer = new byte[ms.Length];
                    ms.Position = 0;
                    ms.Read(buffer, 0, (int)ms.Length);
                    pemPublicKeyStr = Encoding.UTF8.GetString(buffer);
                }
            }

            return pemPublicKeyStr;
        }

        /// 
        ///Convert PEM public key to XML public key
        /// 
        /// 
        /// 
        public static string PemPublicKeyToXml(string pemPublicKeyStr)
        {
            RsaKeyParameters pemPublicKey;
            using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(pemPublicKeyStr)))
            {
                using (var sr = new StreamReader(ms))
                {
                    var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(sr);
                    pemPublicKey = (RsaKeyParameters)pemReader.ReadObject();
                }
            }

            var p = new RSAParameters
            {
                Modulus = pemPublicKey.Modulus.ToByteArrayUnsigned(),
                Exponent = pemPublicKey.Exponent.ToByteArrayUnsigned()
            };

            string xmlPublicKeyStr;
            using (var rsa = new RSACryptoServiceProvider())
            {
                rsa.ImportParameters(p);
                xmlPublicKeyStr = rsa.ToXmlString(false);
            }

            return xmlPublicKeyStr;
        }

        /// 
        ///Convert XML private key to PEM private key
        /// 
        /// 
        /// 
        public static string XmlPrivateKeyToPem(string xmlPrivateKey)
        {
            RSAParameters rsaParam;
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.FromXmlString(xmlPrivateKey);
                rsaParam = rsa.ExportParameters(true);
            }

            var param = new RsaPrivateCrtKeyParameters(
                new BigInteger(1, rsaParam.Modulus), new BigInteger(1, rsaParam.Exponent), new BigInteger(1, rsaParam.D),
                new BigInteger(1, rsaParam.P), new BigInteger(1, rsaParam.Q), new BigInteger(1, rsaParam.DP), new BigInteger(1, rsaParam.DQ),
                new BigInteger(1, rsaParam.InverseQ));

            string pemPrivateKeyStr = null;
            using (var ms = new MemoryStream())
            {
                using (var sw = new StreamWriter(ms))
                {
                    var pemWriter = new Org.BouncyCastle.OpenSsl.PemWriter(sw);
                    pemWriter.WriteObject(param);
                    sw.Flush();

                    byte[] buffer = new byte[ms.Length];
                    ms.Position = 0;
                    ms.Read(buffer, 0, (int)ms.Length);
                    pemPrivateKeyStr = Encoding.UTF8.GetString(buffer);
                }
            }

            return pemPrivateKeyStr;
        }

        /// 
        ///Convert PEM private key to XML private key
        /// 
        /// 
        /// 
        public static string PemPrivateKeyToXml(string pemPrivateKeyStr)
        {
            RsaPrivateCrtKeyParameters pemPrivateKey;
            using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(pemPrivateKeyStr)))
            {
                using (var sr = new StreamReader(ms))
                {
                    var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(sr);
                    var keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
                    pemPrivateKey = (RsaPrivateCrtKeyParameters)keyPair.Private;
                }
            }

            var p = new RSAParameters
            {
                Modulus = pemPrivateKey.Modulus.ToByteArrayUnsigned(),
                Exponent = pemPrivateKey.PublicExponent.ToByteArrayUnsigned(),
                D = pemPrivateKey.Exponent.ToByteArrayUnsigned(),
                P = pemPrivateKey.P.ToByteArrayUnsigned(),
                Q = pemPrivateKey.Q.ToByteArrayUnsigned(),
                DP = pemPrivateKey.DP.ToByteArrayUnsigned(),
                DQ = pemPrivateKey.DQ.ToByteArrayUnsigned(),
                InverseQ = pemPrivateKey.QInv.ToByteArrayUnsigned(),
            };

            string xmlPrivateKeyStr;
            using (var rsa = new RSACryptoServiceProvider())
            {
                rsa.ImportParameters(p);
                xmlPrivateKeyStr = rsa.ToXmlString(true);
            }

            return xmlPrivateKeyStr;
        }

    }
}

The following is the unit test code:

//Public key (XML, PEM format mutual) test
String srcpublickey = "specific XML public key";
            string pemPublicKeyStr=  RsaKeysFormatConverter.XmlPublicKeyToPem(publicKey);
            string xmlPublicKeyStr= RsaKeysFormatConverter.PemPublicKeyToXml(pemPublicKeyStr);
            Assert.AreEqual(srcPublicKey, xmlPublicKeyStr);
//Private key (XML, PEM format mutual) test
String srcprivatekey = "specific XML private key";
            string pemPrivateKeyStr = RsaKeysFormatConverter.XmlPrivateKeyToPem(srcPrivateKey);
            string xmlPrivateKeyStr = RsaKeysFormatConverter.PemPrivateKeyToXml(pemPrivateKeyStr);
            Assert.AreEqual(privateKey,xmlPrivateKeyStr)

Of course, you can also realize format conversion without so much effort. You can use the online website to directly convert:https://the-x.cn/certificate/XmlToPem.aspxIn addition, another article implements similar functions, but the generated PEM format is not a complete format and lacks comment headers and tails:https://www.cnblogs.com/datous/p/RSAKeyConvert.html

Recommended Today

Lua language novice simple tutorial

1、 Foreword Lua is a lightweight and compact scripting language, which is written in standard C language and open in the form of source code. Its design purpose is to be embedded in the application, so as to provide flexible expansion and customization functions for the application. Lua can be applied in game development, independent […]