天天看點

ASP.NET Core RSA加密或解密

前言

這兩天主要是公司同僚用到了RSA加密,事後也看了下,以為很簡單,最終利用RSACryptoServiceProvider來實作RSA加密,然後大緻了解到RSACryptoServiceProvider不支援跨平台,此類原先存在于.NET Framework中,本文我講講利用RSA.Create來實作各種加密模式統一封裝。

RSA加密或解密

主要是在查找資料時看到了dudu老大寫的有關RSA加密的文章(http://www.cnblogs.com/dudu/p/dotnet-core-rsa-openssl.html),剛好找到了我們項目中需要用到的openssl加密模式對應.NET Core中的Pkcs1,這裡實作代碼實在有點多,然後自己也遇到如評論中所說加密長度超出的情況,這個時候隻能采取分段加密的方式。接下來我們來看看。首先看看如下代碼:

var privateKey = "<RSAKeyValue><Modulus>0wE26IHp4U9OLtPhJ+fT8ej6aWORFP8pd++MjUuhkQQm/zhcImbxQbjxtSAftz+kkDwGDFJpSldQPyigOGcUx7PofTc6VhiFik9E9SsxV9n0iEEtqUndDfmBJfPAWt+4UDMwKakgZqFoapDuwjKlTErFvKCyKCs+qN9OZvZwKWk=</Modulus><Exponent>AQAB</Exponent><P>8Ei6NIsZtgV3DQjuGHfGLS6o1O+IUXxzjqLxdMm77yhEPUxR9YPIxODJ2VVTddXSAHxViJJt30yJ7JhVz6cpQw==</P><Q>4M49NrmalgVQFMsea2RMB1qN8fAPfIw5G9q9hzsLcWSCmkeRRIQlvPYflVEKAYKiDVVzENETbnnduFXWBABx4w==</Q><DP>t+JQbemN0Zi5FQaif6MZzHYKynpNTl75aE0Wj5Pa+RlNr8N6bXNe8Bw/HM2Jw4HQ5oJASvYUk3DVlHS4JuP8VQ==</DP><DQ>lT62iv9brp9mU/epgVh71SH8PJPIZEJfo6tryjyb0zMMNcqvmZI1z6aCv0mm3+vPFBUXqCF1yhFj7n4l8FAvSw==</DQ><InverseQ>flrvgxHvf4l+fdymEVDgKjsfGqshOpppoNgZj9kpeWBto3o8z++Ki6eSLQT3nVnpx2QCZeTWkxTED4nhSLKscw==</InverseQ><D>cQTCg1Eqk7sltmFYxUYgOP/AOPjSufteG9acYwYymPkvZh6rAuY+rSRBmvGE62NUYskzuB/gM6iG2/2HrA5SixfNgCvZ+nsK+kX5pzQRsYdD71ViQW0hOanXwj45I2zHRgBiuTtCUP0fs5pISmQkaeJkDL5pO2l+wvlgl+wunj0=</D></RSAKeyValue>";
            var publicKey = "<RSAKeyValue><Modulus>0wE26IHp4U9OLtPhJ+fT8ej6aWORFP8pd++MjUuhkQQm/zhcImbxQbjxtSAftz+kkDwGDFJpSldQPyigOGcUx7PofTc6VhiFik9E9SsxV9n0iEEtqUndDfmBJfPAWt+4UDMwKakgZqFoapDuwjKlTErFvKCyKCs+qN9OZvZwKWk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";

            using (var rsa = RSA.Create())
            {
                rsa.FromXmlString(publicKey);
            }      
ASP.NET Core RSA加密或解密

經到github上查找解決方案,我們需要手動設定值,如下:

public static void FromXmlString(RSA rsa, string xmlString)
        {
            RSAParameters parameters = new RSAParameters();

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xmlString);

            if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue"))
            {
                foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes)
                {
                    switch (node.Name)
                    {
                        case "Modulus": parameters.Modulus = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "Exponent": parameters.Exponent = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "P": parameters.P = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "Q": parameters.Q = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "DP": parameters.DP = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "DQ": parameters.DQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "InverseQ": parameters.InverseQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "D": parameters.D = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    }
                }
            }
            else
            {
                throw new Exception("Invalid XML RSA key.");
            }

            rsa.ImportParameters(parameters);
        }      

其中經過很多嘗試,要麼是Base64格式不正确,要麼是待解密的資料長度和Key Size無效,我第一次嘗試是傳回的加密字元串,然後進行解密,就是有問題,最後還是傳回位元組數組來進行加密和解密,直接看代碼,這裡解決了加密資料長度超出問題以及各種加密模式的統一,如下RSAHelper。

public static class RSAHelper
    {
        private const string privateKey = "<RSAKeyValue><Modulus>0wE26IHp4U9OLtPhJ+fT8ej6aWORFP8pd++MjUuhkQQm/zhcImbxQbjxtSAftz+kkDwGDFJpSldQPyigOGcUx7PofTc6VhiFik9E9SsxV9n0iEEtqUndDfmBJfPAWt+4UDMwKakgZqFoapDuwjKlTErFvKCyKCs+qN9OZvZwKWk=</Modulus><Exponent>AQAB</Exponent><P>8Ei6NIsZtgV3DQjuGHfGLS6o1O+IUXxzjqLxdMm77yhEPUxR9YPIxODJ2VVTddXSAHxViJJt30yJ7JhVz6cpQw==</P><Q>4M49NrmalgVQFMsea2RMB1qN8fAPfIw5G9q9hzsLcWSCmkeRRIQlvPYflVEKAYKiDVVzENETbnnduFXWBABx4w==</Q><DP>t+JQbemN0Zi5FQaif6MZzHYKynpNTl75aE0Wj5Pa+RlNr8N6bXNe8Bw/HM2Jw4HQ5oJASvYUk3DVlHS4JuP8VQ==</DP><DQ>lT62iv9brp9mU/epgVh71SH8PJPIZEJfo6tryjyb0zMMNcqvmZI1z6aCv0mm3+vPFBUXqCF1yhFj7n4l8FAvSw==</DQ><InverseQ>flrvgxHvf4l+fdymEVDgKjsfGqshOpppoNgZj9kpeWBto3o8z++Ki6eSLQT3nVnpx2QCZeTWkxTED4nhSLKscw==</InverseQ><D>cQTCg1Eqk7sltmFYxUYgOP/AOPjSufteG9acYwYymPkvZh6rAuY+rSRBmvGE62NUYskzuB/gM6iG2/2HrA5SixfNgCvZ+nsK+kX5pzQRsYdD71ViQW0hOanXwj45I2zHRgBiuTtCUP0fs5pISmQkaeJkDL5pO2l+wvlgl+wunj0=</D></RSAKeyValue>";
        private const string publicKey = "<RSAKeyValue><Modulus>0wE26IHp4U9OLtPhJ+fT8ej6aWORFP8pd++MjUuhkQQm/zhcImbxQbjxtSAftz+kkDwGDFJpSldQPyigOGcUx7PofTc6VhiFik9E9SsxV9n0iEEtqUndDfmBJfPAWt+4UDMwKakgZqFoapDuwjKlTErFvKCyKCs+qN9OZvZwKWk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";

        public static byte[] Encrypt(byte[] encryptBytes, RSAEncryptionPadding padding)
        {
            using (var rsa = RSA.Create())
            {

                FromXmlString(rsa, publicKey);

                var maxBlockSize = GetMaxBlockSize(rsa, padding);

                if (encryptBytes.Length <= maxBlockSize)
                {
                    var @bytes = rsa.Encrypt(encryptBytes, padding);
                    return @bytes;
                }

                using (var memoryStream = new MemoryStream(encryptBytes))
                {
                    using (var readStream = new MemoryStream())
                    {
                        byte[] buffer = new byte[maxBlockSize];

                        int blockSize = memoryStream.Read(buffer, 0, maxBlockSize);

                        while (blockSize > 0)
                        {
                            var blockByte = new byte[blockSize];

                            Array.Copy(buffer, 0, blockByte, 0, blockSize);

                            var encrypts = rsa.Encrypt(blockByte, padding);

                            readStream.Write(encrypts, 0, encrypts.Length);

                            blockSize = memoryStream.Read(buffer, 0, maxBlockSize);
                        }

                        return readStream.ToArray();
                    }
                }
            }
        }

        public static byte[] Decrypt(byte[] decryptBytes, RSAEncryptionPadding padding)
        {
            using (var rsa = RSA.Create())
            {
                FromXmlString(rsa, privateKey);

                var maxBlockSize = rsa.KeySize / 8;

                if (decryptBytes.Length <= maxBlockSize)
                {
                    var @bytes = rsa.Decrypt(decryptBytes, padding);
                    return @bytes;
                }

                using (var memoryStream = new MemoryStream(decryptBytes))
                {
                    using (var readStream = new MemoryStream())
                    {
                        var buffer = new byte[maxBlockSize];

                        var blockSize = memoryStream.Read(buffer, 0, maxBlockSize);

                        while (blockSize > 0)
                        {
                            var blockByte = new byte[blockSize];

                            Array.Copy(buffer, 0, blockByte, 0, blockSize);

                            var decrypts = rsa.Decrypt(blockByte, padding);

                            readStream.Write(decrypts, 0, decrypts.Length);

                            blockSize = memoryStream.Read(buffer, 0, maxBlockSize);
                        }

                        return readStream.ToArray();
                    }
                }
            }
        }

        public static void FromXmlString(RSA rsa, string xmlString)
        {
            RSAParameters parameters = new RSAParameters();

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xmlString);

            if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue"))
            {
                foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes)
                {
                    switch (node.Name)
                    {
                        case "Modulus": parameters.Modulus = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "Exponent": parameters.Exponent = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "P": parameters.P = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "Q": parameters.Q = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "DP": parameters.DP = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "DQ": parameters.DQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "InverseQ": parameters.InverseQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "D": parameters.D = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    }
                }
            }
            else
            {
                throw new Exception("Invalid XML RSA key.");
            }

            rsa.ImportParameters(parameters);
        }

        static int GetMaxBlockSize(RSA rsa, RSAEncryptionPadding padding)
        {
            var offset = 0;
            if (padding.Mode == RSAEncryptionPaddingMode.Pkcs1)
            {
                offset = 11;
            }
            else
            {
                if (padding.Equals(RSAEncryptionPadding.OaepSHA1))
                {
                    offset = 42;
                }

                if (padding.Equals(RSAEncryptionPadding.OaepSHA256))
                {
                    offset = 66;
                }

                if (padding.Equals(RSAEncryptionPadding.OaepSHA384))
                {
                    offset = 98;
                }

                if (padding.Equals(RSAEncryptionPadding.OaepSHA512))
                {
                    offset = 130;
                }
            }
            return rsa.KeySize / 8 - offset;
        }
    }      

我們開始進行如下測試,加密資料長度為6890,如下。

static void Main(string[] args)
        {
            var encryptString = "0";
            for (int i = 1; i < 2000; i++)
            {
                encryptString += i;
            }

            Console.WriteLine(encryptString.Length);

            var encryptBytes = Encoding.UTF8.GetBytes(encryptString);

            //加密後位元組數組
            var resultBytes = RSAHelper.Encrypt(encryptBytes, RSAEncryptionPadding.Pkcs1);

            //解密後位元組數組
            var decryptBytes = RSAHelper.Decrypt(resultBytes, RSAEncryptionPadding.Pkcs1);

            //解密結果
            var result = Encoding.UTF8.GetString(decryptBytes);

            //比較加密字元串和解密結果是否相等
            Console.WriteLine(encryptString == result);
            Console.ReadKey();
        }      
ASP.NET Core RSA加密或解密

總結

本文通過實作RSA加密和解密,同時也對加密資料超出采取分段加密的方式,測試在windows上通過,由于沒有linux和mac環境未經測試,不知是否好使,一試見分曉。 

你所看到的并非事物本身,而是經過诠釋後所賦予的意義