[原创] C# .NetCore 跨平台RSA加密实现
注意,文中提到的代码由于时间原因,没有经过严格跨平台测试,只是编译通过,在本地windows平台下测试正常。其它平台大家自行测试,如有时间,本人后面测试完后再来更新此文。
相信很多朋友.NetCore下使用RSA时,如下图所示,都是用的这个第三方NuGet包
这个包用起来确实很方便,它可以很方便的生成密钥对供我们调用,也可以很方便的实现RSA加密和解密。网上搜索到的结果基本上都是它的。本以为我也可以这样风平浪静心安理得的一直使用,但是直到有一天,我把它引入了一个项目,发现它上面有个黄色的警告。提示更新,但明明已经是最新版本了,这让原本就九年义务教育不合格的我束手无策。。。项目发布以后也莫明其妙的报错,网上查询的结果是.NET版本不匹配之类的,怀疑和它有关,于是决定替换了它。本以为这是一件很简单的事情,没想到实现起来还是浪费了不少时间。
本来以为网上有很多现成的轮子拿来直接用就OK了,没想到搜索到的几乎全是BouncyCastle.NetCore的, 国外技术论坛上面也搜索到了一些.NET自带的System.Security.Cryptography的相关结果,但都是只言片语,看的人云里雾里,头昏奶涨的,不知其所以然。当翻看了MS官方的文档后看到MS这样介绍,为了跨平台,
You should avoid using RSACryptoServiceProvider as it is tightly bound to the Windows platform. Instead, we'll be using the RSA base class, which will return a platform-specific RSA implementation
要尽量用基类开发,以更好的为平台解耦。网上搜索的好多代码都是以前ASP.NET框架下的,如果移植到。netcore下过来,WINDOWS平台下就算没问题,其它平台下也会报错,阅读了一些老外的讨论,再加上MS官网说明,大概了解到,能用 System.Security.Cryptography.RSA.Create() 的时候,尽量用RSA.Create(),而 new RSACryptoServiceProvider(2048) 方法能不用就不用。基于以上需求,简单写一个类,实现以下功能:
public class RSAService
{
private RSA rsa = RSA.Create();
public RSAService() { }
public RSAService(string PubKey_Base64String,string PriKey_Base64String)
//......省略若干行
}
初始化构造函数RSAService,用公钥私钥初始化RSA对象
public (string, string) CreateKeyPair //向调用者返回一对密钥对
public string PriKey()//返回成员rsa的私钥
public string PubKey() //返回公钥
public string Encrypt(string plainText) //对象对加密明文
public string Decrypt(string cipherText) //对象内解密密文
public string Decrypt(string privateKey, string cipherText) //解密对象外密文,即,传递一条密钥和一条密文,由密钥解出密文
因为我的需求很简单,就是前端加密,后端能解密就可以了,所以代码也是相对简单。但是在调试的过程中其实还是浪费了不少周折。因为前台是用JS加密,用AJAX发送的,JS用/js/jsencrypt.js 这一个好像就够了。因为RSA算法所限,加密密文不可以超过117个(1024位时),所以长的明文要分段加密才可以。另外,.NETCORE生成的密钥也分很多类型,什么PEM之类的,还有PKCS1\PKCS8之类的,PKCS8又分带密码的,不带密码的,拼写也相近,眼神不好,一看走眼就浪费半天时间,大家调试的时候注意一点,这里值得强调的一点就是,.NETCORE生成的PKCS1的密钥对,返回前台JS调用的时候,加密直接返回FALSE,为此浪费了我不少时间,开始以为是自己COPY密钥的时候是因为格式转换引起的,所以一顿查找,浪费不少时间,后来才发现,是PKCS1的密钥不受JS支持,所以我们返回的时候要向前台返回PKCS8格式的。废话不多说,以下是代码:
<script> function EncryptPost(data,url){ var returnData; $.ajax({ beforeSend: function (xhr) { if($("#inbox").val().length>113) { } }, type: "get", async: false, url: '/api/cer/GetPublicKey', success: function (_data) { var cid=_data.cid; var encrypt = new JSEncrypt(); encrypt.setPublicKey('-----BEGIN PUBLIC KEY-----' + _data.spbKey + '-----END PUBLIC KEY-----'); var encrypted = encrypt.encrypt(JSON.stringify(data)); $.ajax({ url:url, type: "post", data:JSON.stringify({"cid":cid, "json": Base64.encode(encrypted) }), dataType: "json", contentType:"application/json", async: false, success: function (data) { returnData= data; } }); }, error: function (msg) { alert('遇到网络错误'); } }); return returnData; } function mod(n, m) { return ((n % m) + m) % m; } function splitencrypt(str,spbKey) { if(str=="") return ""; if(str.length>117)//1024位密钥要改成117 { var strArr = []; var n = 117; var encrypt = new JSEncrypt(); encrypt.setPublicKey('-----BEGIN PUBLIC KEY-----' + spbKey + '-----END PUBLIC KEY-----'); for (var i = 0, l = str.length; i < l/n; i++) { var a = str.slice(n*i, n*(i+1)); strArr.push(a); if(i==0) { //c= encrypt.encrypt(JSON.stringify(a)); c= encrypt.encrypt(a); } else { //c=c+"|"+encrypt.encrypt(JSON.stringify(a)); c=c+"|"+encrypt.encrypt(a); } } return c; } else { var encrypt = new JSEncrypt(); encrypt.setPublicKey('-----BEGIN PUBLIC KEY-----' + spbKey + '-----END PUBLIC KEY-----'); return encrypt.encrypt(str); } } $(document).ready(function(){ $("#de2").click(function(){ //这是要发送的内容 var data = { "username":"admin", "password": "123465", "age":18 }; $("#inbox").text("待发送数据:" + JSON.stringify(data)); var result= EncryptPost(data, "/api/cer/ReciveDemoURL"); $("#outbox").text(result.Msg+result.content); }); $("#en").click(function() { if (($("#pbk").val() == "") || ($("#inbox").val() == "")) { alert("公钥和待加密字符串不能为空!"); } var txt = Base64.encode($("#inbox").val()); var pbk = Base64.decode($("#pbk").val()); var inbox=splitencrypt(txt,pbk); $("#inbox").val(inbox); }); $("#de").click(function(){ if (($("#inbox").val() == "") ||($("#prk").val() == "")){ alert("待解密密文和私钥都不能为空"); } $.ajax({ beforeSend: function (xhr) { }, type: "post", url: '/api/cer/UnRSA', data:{Base64EncodeString:Base64.encode($("#inbox").val()),Base64PRKey:$("#prk").val()},//发送前BASE64编码,减少出错。收到后记得decode回来 dataType: "json", success: function (data) { if(data.Result!="Error") { $("#outbox").val(Base64.decode(data.content));} else{ $("#outbox").val("解密失败!"+data.content); } }, error: function (msg) { alert('遇到网络错误'); } }); }); $("#getkey").click(function () { $.ajax({ beforeSend: function (xhr) { }, type: "get", async: false, url: '/api/cer/GetRsaPairKey', headers: { "RequestVerificationToken": $('@Html.AntiForgeryToken()').val() }, success: function (_data) { if (_data.spbKey != undefined) { $("#pbk").text(Base64.encode(_data.spbKey)); $("#prk").text(Base64.encode(_data.sprKey)); $("#inbox").text("AABBCCDDaabbccdd"); } else { alert('请刷新页面重试'); } }, error: function (msg) { alert('遇到网络错误'); } }); }); }); </script>
简单说明,点击登录的时候,AJAX用同步提交的方法向服务器临时拿回一个RSA公钥,然后将需要发送的内容加密后,分段加密后发送到服务器
原创代码,转载请注明出处,翻版必究。