1、文章回顾
在文章windows使用openssl生成公钥和私钥 中成功使用openssl生成了用于RSA加密和解密的公钥和私钥。
在文章在JavaScript中进行RSA加密和解密 中成功使用JavaScript进行了RSA加密和解密。
如果你习惯使用.net平台,根据以上的文章已经实现了在网页端使用JS进行RSA加密解密的功能,但是还有.net后端呢?只有后端与前端相互配合,才能实现通过RSA加密和解密的整个流程。今天这篇文章就是把这环得圆上。
2、关键代码
示例基于.net6框架。关于RSA的实现函数都放在一个文件中。特别说明一下,该代码只支持windows平台。
密钥生成的关键函数:
#region 密钥生成
/// <summary>
/// 取得私钥和公钥 XML 格式,返回数组第一个是私钥,第二个是公钥.
/// </summary>
/// <param name="size">密钥长度,默认1024,可以为2048</param>
/// <returns></returns>
public static string[] CreateXmlKey(int size = 1024)
{
//密钥格式要生成pkcs#1格式的 而不是pkcs#8格式的
RSACryptoServiceProvider sp = new RSACryptoServiceProvider(size);
string privateKey = sp.ToXmlString(true);//private key
string publicKey = sp.ToXmlString(false);//public key
return new string[] { privateKey, publicKey };
}
/// <summary>
/// 取得私钥和公钥 CspBlob 格式,返回数组第一个是私钥,第二个是公钥.
/// </summary>
/// <param name="size"></param>
/// <returns></returns>
public static string[] CreateCspBlobKey(int size = 1024)
{
//密钥格式要生成pkcs#1格式的 而不是pkcs#8格式的
RSACryptoServiceProvider sp = new RSACryptoServiceProvider(size);
string privateKey = System.Convert.ToBase64String(sp.ExportCspBlob(true));//private key
string publicKey = System.Convert.ToBase64String(sp.ExportCspBlob(false));//public key
return new string[] { privateKey, publicKey };
}
/// <summary>
/// 导出PEM PKCS#1格式密钥对,返回数组第一个是私钥,第二个是公钥.
/// </summary>
public static string[] CreateKey_PEM_PKCS1(int size = 1024)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(size);
string privateKey = ToPEM(rsa, false, false);
string publicKey = ToPEM(rsa, true, false);
return new string[] { privateKey, publicKey };
}
/// <summary>
/// 导出PEM PKCS#8格式密钥对,返回数组第一个是私钥,第二个是公钥.
/// </summary>
public static string[] CreateKey_PEM_PKCS8(int size = 1024, bool convertToPublic = false)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(size);
string privateKey = ToPEM(rsa, false, true);
string publicKey = ToPEM(rsa, true, true);
return new string[] { privateKey, publicKey };
}
#endregion
加密解密函数:
加密块有最大长度限制,如果加密数据的长度超过私钥长度/8-11,会引发长度不正确的异常,所以进行数据的分块加密。
#region 加密和解密
/// <summary>
/// RSA加密
/// </summary>
/// <param name="Data">原文</param>
/// <param name="PublicKeyString">公钥</param>
/// <param name="KeyType">密钥类型XML/PEM</param>
/// <returns></returns>
public static string RSAEncrypt(string Data, string PublicKeyString, string KeyType)
{
byte[] data = Encoding.GetEncoding("UTF-8").GetBytes(Data);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
switch (KeyType)
{
case "XML":
rsa.FromXmlString(PublicKeyString);
break;
case "PEM":
rsa = FromPEM(PublicKeyString);
break;
default:
throw new Exception("不支持的密钥类型");
}
int MaxBlockSize = rsa.KeySize / 8 - 11;
//正常长度
if (data.Length <= MaxBlockSize)
{
byte[] hashvalueEcy = rsa.Encrypt(data, false); //加密
return System.Convert.ToBase64String(hashvalueEcy);
}
//长度超过正常值
else
{
using (MemoryStream PlaiStream = new MemoryStream(data))
using (MemoryStream CrypStream = new MemoryStream())
{
Byte[] Buffer = new Byte[MaxBlockSize];
int BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize);
while (BlockSize > 0)
{
Byte[] ToEncrypt = new Byte[BlockSize];
Array.Copy(Buffer, 0, ToEncrypt, 0, BlockSize);
Byte[] Cryptograph = rsa.Encrypt(ToEncrypt, false);
CrypStream.Write(Cryptograph, 0, Cryptograph.Length);
BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize);
}
return System.Convert.ToBase64String(CrypStream.ToArray(), Base64FormattingOptions.None);
}
}
}
/// <summary>
/// RSA解密
/// </summary>
/// <param name="Data">密文</param>
/// <param name="PrivateKeyString">私钥</param>
/// <param name="KeyType">密钥类型XML/PEM</param>
/// <returns></returns>
public static string RSADecrypt(string Data, string PrivateKeyString, string KeyType)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
switch (KeyType)
{
case "XML":
rsa.FromXmlString(PrivateKeyString);
break;
case "PEM":
rsa = FromPEM(PrivateKeyString);
break;
default:
throw new Exception("不支持的密钥类型");
}
int MaxBlockSize = rsa.KeySize / 8; //解密块最大长度限制
//正常解密
if (Data.Length <= MaxBlockSize)
{
byte[] hashvalueDcy = rsa.Decrypt(System.Convert.FromBase64String(Data), false);//解密
return Encoding.GetEncoding("UTF-8").GetString(hashvalueDcy);
}
//分段解密
else
{
using (MemoryStream CrypStream = new MemoryStream(System.Convert.FromBase64String(Data)))
using (MemoryStream PlaiStream = new MemoryStream())
{
Byte[] Buffer = new Byte[MaxBlockSize];
int BlockSize = CrypStream.Read(Buffer, 0, MaxBlockSize);
while (BlockSize > 0)
{
Byte[] ToDecrypt = new Byte[BlockSize];
Array.Copy(Buffer, 0, ToDecrypt, 0, BlockSize);
Byte[] Plaintext = rsa.Decrypt(ToDecrypt, false);
PlaiStream.Write(Plaintext, 0, Plaintext.Length);
BlockSize = CrypStream.Read(Buffer, 0, MaxBlockSize);
}
string output = Encoding.GetEncoding("UTF-8").GetString(PlaiStream.ToArray());
return output;
}
}
}
#endregion
3、功能测试
在Program中首先生成私钥和公钥分别保存在pri.txt和pub.txt文本中。
//生成公钥和密钥并保存到文本中
string[] a = RSA_Tools.CreateKey_PEM_PKCS8();
string privString = a[0];//私钥
string pubString = a[1];//公钥
File.WriteAllText("pri.txt", privString);
File.WriteAllText("pub.txt", pubString);
Console.WriteLine($"privateKey:{privString}");
Console.WriteLine($"publicKey:{pubString}");
分别读取私钥和公钥进行加密和解密测试:
string privString = File.ReadAllText("pri.txt");
string pubString = File.ReadAllText("pub.txt");
string text = "123";
Console.WriteLine($"被加密字符:{text}");
string j = RSA_Tools.RSAEncrypt(text, pubString, "PEM");
Console.WriteLine($"加密后字符:{j}");
string b = RSA_Tools.RSADecrypt(j, privString, "PEM");
Console.WriteLine($"解密后字符:{b}");
如图,加密文本“123”,再用加密后的文本解密得到“123,测试成功。
4、JavaScript和.net后端互联互通测试
使用文章在JavaScript中进行RSA加密和解密 中的示例对原始数据进行RSA加密,再把加密后的字符串用.net后端的代码解密,测试是否成功。注意双方使用的公钥和私钥需一致。
简单列出加密解密的流程:
1).net后端生成公钥和私钥,自个保存私钥,把公钥发给前端。(私钥不在网络上传播)
2)前端接收到公钥后,使用公钥加密数据,把加密后的数据返回后端。
3)后端使用私钥解密前端的数据,得到原始数据。
RSA非对称加密和解密的关键就体现于此,私钥是绝不在网络上传播的,公钥是主动公开的。使用公钥加密的数据只有用配对的私钥才能解密!真是神奇,的数学。
这里讲解第二步,JavaScript使用公钥加密代码如下:(注意公钥是第一步中.net后端生成的)
<script type="text/javascript">
$(function () {
var encryptor = new JSEncrypt() // 创建加密对象实例
//之前ssl生成的公钥,复制的时候要小心不要有空格
var pubKey = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1QQRl0HlrVv6kGqhgonD6A9
SU6ZJpnEN+Q0blT/ue6Ndt97WRfxtS' +
'As0QoquTreaDtfC4RRX4o+CU6BTuHLUm+eSvxZS9TzbwoYZq7ObbQAZAY+SYDgAA5PHf1wNN20dGMFFgVS
/y0ZWvv1UNa2laEz0I8Vmr5ZlzIn88GkmSiQIDAQAB-----END PUBLIC KEY-----'
encryptor.setPublicKey(pubKey)//设置公钥
var rsaPassWord = encryptor.encrypt('123')
console.log(rsaPassWord)
});
</script>
把原始数据”123“加密后得到”KoO8MxKRr4mu5Z3iG4cpF/se+8Axdd0ydX374CSmFPj+asXCa/bvHcr3NBUpB3YBxi/B9LVzuajgnne
4nYWj8NERS2L5OM2WH11irLLxZNqG98uD9OOx2zO9CjT8VaMWEo0dErvcu
VAdZeXpgiD1CJh5sWNKZEioBiVVU/sApzo=“
使用后端解密:
string privString = File.ReadAllText("pri.txt");
string j = "KoO8MxKRr4mu5Z3iG4cpF/se+8Axdd0ydX374CSmFPj+asXCa/bvHcr3NBUpB3YBxi/B9LVzuajgnne4n
YWj8NERS2L5OM2WH11irLLxZNqG98uD9OOx2zO9CjT8VaMWEo0dErvcuVAdZeXpgiD1CJh
5sWNKZEioBiVVU/sApzo=";
string b = RSA_Tools.RSADecrypt(j, privString, "PEM");
Console.WriteLine($"解密后字符:{b}");
解密得到原始数据”123“。
5、代码下载
至此已经实现了前后端的RSA非对称加密和解密的功能,可以愉快地基于此实现数据的安全传输。以上示例基于服务端生产一对密钥对,其实也可以前端和后端各自生成一对密钥对,并向对方分享各自的公钥。在传输数据前,使用对方的公钥加密数据后再进行传输,安全性将大大提高!
在微信小程序上搜索(密码盾)集成了开心一刻、程序员计算器、密码箱、习惯打卡、还款提醒、倒数纪念日、日记本、备忘录、日程提醒、端口映射、练打字等功能。前后端的数据传输就应用了RSA非对称加密和解密,有兴趣可体验一番!
我已将代码上传,下载码是:D2BC8C3473
下载码是啥?如何下载=》点击查看