[原創(chuàng)] C# .NetCore 跨平臺(tái)RSA加密實(shí)現(xiàn)
注意,文中提到的代碼由于時(shí)間原因,沒(méi)有經(jīng)過(guò)嚴(yán)格跨平臺(tái)測(cè)試,只是編譯通過(guò),在本地windows平臺(tái)下測(cè)試正常。其它平臺(tái)大家自行測(cè)試,如有時(shí)間,本人后面測(cè)試完后再來(lái)更新此文。
相信很多朋友.NetCore下使用RSA時(shí),如下圖所示,都是用的這個(gè)第三方NuGet包
這個(gè)包用起來(lái)確實(shí)很方便,它可以很方便的生成密鑰對(duì)供我們調(diào)用,也可以很方便的實(shí)現(xiàn)RSA加密和解密。網(wǎng)上搜索到的結(jié)果基本上都是它的。本以為我也可以這樣風(fēng)平浪靜心安理得的一直使用,但是直到有一天,我把它引入了一個(gè)項(xiàng)目,發(fā)現(xiàn)它上面有個(gè)黃色的警告。提示更新,但明明已經(jīng)是最新版本了,這讓原本就九年義務(wù)教育不合格的我束手無(wú)策。。。項(xiàng)目發(fā)布以后也莫明其妙的報(bào)錯(cuò),網(wǎng)上查詢的結(jié)果是.NET版本不匹配之類的,懷疑和它有關(guān),于是決定替換了它。本以為這是一件很簡(jiǎn)單的事情,沒(méi)想到實(shí)現(xiàn)起來(lái)還是浪費(fèi)了不少時(shí)間。
本來(lái)以為網(wǎng)上有很多現(xiàn)成的輪子拿來(lái)直接用就OK了,沒(méi)想到搜索到的幾乎全是BouncyCastle.NetCore的, 國(guó)外技術(shù)論壇上面也搜索到了一些.NET自帶的System.Security.Cryptography的相關(guān)結(jié)果,但都是只言片語(yǔ),看的人云里霧里,頭昏奶漲的,不知其所以然。當(dāng)翻看了MS官方的文檔后看到MS這樣介紹,為了跨平臺(tái),
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
要盡量用基類開(kāi)發(fā),以更好的為平臺(tái)解耦。網(wǎng)上搜索的好多代碼都是以前ASP.NET框架下的,如果移植到。netcore下過(guò)來(lái),WINDOWS平臺(tái)下就算沒(méi)問(wèn)題,其它平臺(tái)下也會(huì)報(bào)錯(cuò),閱讀了一些老外的討論,再加上MS官網(wǎng)說(shuō)明,大概了解到,能用 System.Security.Cryptography.RSA.Create() 的時(shí)候,盡量用RSA.Create(),而 new RSACryptoServiceProvider(2048) 方法能不用就不用?;谝陨闲枨?,簡(jiǎn)單寫一個(gè)類,實(shí)現(xiàn)以下功能:
public class RSAService
{
private RSA rsa = RSA.Create();
public RSAService() { }
public RSAService(string PubKey_Base64String,string PriKey_Base64String)
//......省略若干行
}
初始化構(gòu)造函數(shù)RSAService,用公鑰私鑰初始化RSA對(duì)象
public (string, string) CreateKeyPair //向調(diào)用者返回一對(duì)密鑰對(duì)
public string PriKey()//返回成員rsa的私鑰
public string PubKey() //返回公鑰
public string Encrypt(string plainText) //對(duì)象對(duì)加密明文
public string Decrypt(string cipherText) //對(duì)象內(nèi)解密密文
public string Decrypt(string privateKey, string cipherText) //解密對(duì)象外密文,即,傳遞一條密鑰和一條密文,由密鑰解出密文
因?yàn)槲业男枨蠛芎?jiǎn)單,就是前端加密,后端能解密就可以了,所以代碼也是相對(duì)簡(jiǎn)單。但是在調(diào)試的過(guò)程中其實(shí)還是浪費(fèi)了不少周折。因?yàn)榍芭_(tái)是用JS加密,用AJAX發(fā)送的,JS用/js/jsencrypt.js 這一個(gè)好像就夠了。因?yàn)镽SA算法所限,加密密文不可以超過(guò)117個(gè)(1024位時(shí)),所以長(zhǎng)的明文要分段加密才可以。另外,.NETCORE生成的密鑰也分很多類型,什么PEM之類的,還有PKCS1\PKCS8之類的,PKCS8又分帶密碼的,不帶密碼的,拼寫也相近,眼神不好,一看走眼就浪費(fèi)半天時(shí)間,大家調(diào)試的時(shí)候注意一點(diǎn),這里值得強(qiáng)調(diào)的一點(diǎn)就是,.NETCORE生成的PKCS1的密鑰對(duì),返回前臺(tái)JS調(diào)用的時(shí)候,加密直接返回FALSE,為此浪費(fèi)了我不少時(shí)間,開(kāi)始以為是自己COPY密鑰的時(shí)候是因?yàn)楦袷睫D(zhuǎn)換引起的,所以一頓查找,浪費(fèi)不少時(shí)間,后來(lái)才發(fā)現(xiàn),是PKCS1的密鑰不受JS支持,所以我們返回的時(shí)候要向前臺(tái)返回PKCS8格式的。廢話不多說(shuō),以下是代碼:

<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('遇到網(wǎng)絡(luò)錯(cuò)誤');
}
});
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(){
//這是要發(fā)送的內(nèi)容
var data = {
"username":"admin",
"password": "123465",
"age":18
};
$("#inbox").text("待發(fā)送數(shù)據(jù):" + 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()},//發(fā)送前BASE64編碼,減少出錯(cuò)。收到后記得decode回來(lái)
dataType: "json",
success: function (data) {
if(data.Result!="Error") { $("#outbox").val(Base64.decode(data.content));}
else{
$("#outbox").val("解密失敗!"+data.content);
}
},
error: function (msg) {
alert('遇到網(wǎng)絡(luò)錯(cuò)誤');
}
});
});
$("#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('請(qǐng)刷新頁(yè)面重試');
}
},
error: function (msg) {
alert('遇到網(wǎng)絡(luò)錯(cuò)誤');
}
});
});
});
</script>
簡(jiǎn)單說(shuō)明,點(diǎn)擊登錄的時(shí)候,AJAX用同步提交的方法向服務(wù)器臨時(shí)拿回一個(gè)RSA公鑰,然后將需要發(fā)送的內(nèi)容加密后,分段加密后發(fā)送到服務(wù)器
原創(chuàng)代碼,轉(zhuǎn)載請(qǐng)注明出處,翻版必究。