前言
这两天主要是公司同事用到了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); }
经到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(); }
总结
本文通过实现RSA加密和解密,同时也对加密数据超出采取分段加密的方式,测试在windows上通过,由于没有linux和mac环境未经测试,不知是否好使,一试见分晓。