詳解c#與js的rsa加密互通
ASN.1
抽象語法表示(標(biāo)記)ASN.1(Abstract Syntax Notation One )一種數(shù)據(jù)定義語言,描述了對(duì)數(shù)據(jù)進(jìn)行表示、編碼、傳輸和解碼的數(shù)據(jù)格式。網(wǎng)絡(luò)管理系統(tǒng)中的管理信息庫(MIB)、應(yīng)用程序的數(shù)據(jù)結(jié)構(gòu)、協(xié)議數(shù)據(jù)單元(PDU)都是用ASN.1定義的。
可以理解為ASN.1是對(duì)密鑰結(jié)構(gòu)定義的一種規(guī)范
密鑰結(jié)構(gòu)類型
PKCS#1
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
PKCS#8
PublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
PublicKey BIT STRING ; 其中的BIT STRING是某個(gè)算法自己指定的二進(jìn)制格式
; RSA算法的話,就是上面的RSAPublicKey
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
PrivateKeyInfo ::= SEQUENCE {
version Version,
algorithm AlgorithmIdentifier,
PrivateKey BIT STRING
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
密鑰編碼類型
der格式
二進(jìn)制格式
pem格式
把der格式的數(shù)據(jù)用base64編碼后,然后再在頭尾加上一段“-----”開始的標(biāo)記
證書類型
X.509證書
X.509只包含公鑰,沒有私鑰,這種證書一般公開發(fā)布,可用于放在客服端使用,用于加密、驗(yàn)簽
PKCS#12證書
因?yàn)閄.509證書只包含公鑰,但有些時(shí)候我們需要把私鑰和公鑰合并成一個(gè)證書,放在服務(wù)端使用,用于解密、簽名。
PKCS#12就定義了這樣一種證書,它既包含了公鑰有包含了私鑰。典型的入pfx、p12證書就是PKCS#12證書。
PKCS#7證書
當(dāng)你收到一個(gè)網(wǎng)站的證書后,你需要驗(yàn)證其真實(shí)性。因?yàn)橐粋€(gè)X.509證書包含了公鑰、持有人信息、簽名。為了驗(yàn)證其真實(shí)性,你需要簽證其簽名,而驗(yàn)證簽名則需要簽發(fā)的CA機(jī)構(gòu)的公鑰證書。同樣原理,當(dāng)你拿到CA機(jī)構(gòu)的公鑰證書后,你也需要驗(yàn)證該CA機(jī)構(gòu)的真實(shí)性,而驗(yàn)證該CA機(jī)構(gòu)的證書,你需要該CA上級(jí)機(jī)構(gòu)的CA公鑰證書...以此類推,你需要一直驗(yàn)證到根證書為止。所以為了驗(yàn)證一個(gè)網(wǎng)站證書的真實(shí)性,你需要的不僅一張證書,而是一個(gè)證書鏈。而PKCS#7就定義了這樣一個(gè)證書鏈的類型結(jié)構(gòu)。典型如p7b后綴名的證書就是這樣的格式。
證書后綴
.cer/.crt:存放公鑰,沒有私鑰,就是一個(gè)X.509證書,二進(jìn)制形式存放
.pfx/.p12:存放公鑰和私鑰,通常包含保護(hù)密碼,二進(jìn)制方式
證書與密鑰關(guān)系
數(shù)字證書和私鑰是匹配的關(guān)系。就好比鑰匙牌和鑰匙的關(guān)系。在數(shù)字證書簽發(fā)的時(shí)候,數(shù)字證書簽發(fā)系統(tǒng)(CA系統(tǒng)),在生成數(shù)字證書的同時(shí),還會(huì)隨機(jī)生成一對(duì)密鑰,一個(gè)私鑰,一個(gè)公鑰。數(shù)字證書標(biāo)示用戶身份, 相匹配的私鑰和公鑰,則是用來保障用戶身份的可認(rèn)證性。就好比咱們拿著一串鑰匙,每個(gè)鑰匙上都標(biāo)明有時(shí)某某房間的鑰匙,但是否是真的,還需要看能不能打開相應(yīng)的房門。
密鑰生成
/// <summary>
/// 取得私鑰和公鑰 XML 格式,返回?cái)?shù)組第一個(gè)是私鑰,第二個(gè)是公鑰.
/// </summary>
/// <param name="size">密鑰長度,默認(rèn)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 格式,返回?cái)?shù)組第一個(gè)是私鑰,第二個(gè)是公鑰.
/// </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>
/// 導(dǎo)出PEM PKCS#1格式密鑰對(duì),返回?cái)?shù)組第一個(gè)是私鑰,第二個(gè)是公鑰.
/// </summary>
public static string[] CreateKey_PEM_PKCS1(int size = 1024)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(size);
string privateKey = RSA_PEM.ToPEM(rsa, false, false);
string publicKey = RSA_PEM.ToPEM(rsa, true, false);
return new string[] { privateKey, publicKey };
}
/// <summary>
/// 導(dǎo)出PEM PKCS#8格式密鑰對(duì),返回?cái)?shù)組第一個(gè)是私鑰,第二個(gè)是公鑰.
/// </summary>
public static string[] CreateKey_PEM_PKCS8(int size = 1024, bool convertToPublic = false)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(size);
string privateKey = RSA_PEM.ToPEM(rsa, false, true);
string publicKey = RSA_PEM.ToPEM(rsa, true, true);
return new string[] { privateKey, publicKey };
}
后端加/解密方法使用
/// <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 = RSA_PEM.FromPEM(PublicKeyString);
break;
default:
throw new Exception("不支持的密鑰類型");
}
//加密塊最大長度限制,如果加密數(shù)據(jù)的長度超過 秘鑰長度/8-11,會(huì)引發(fā)長度不正確的異常,所以進(jìn)行數(shù)據(jù)的分塊加密
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 = RSA_PEM.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;
}
}
}
前端加密方法
注:jsencrypt默認(rèn)PKCS#1結(jié)構(gòu),生成密鑰時(shí)需要注意
<script src="http://passport.cnblogs.com/scripts/jsencrypt.min.js"></script>
var encryptor = new JSEncrypt() // 創(chuàng)建加密對(duì)象實(shí)例
//之前ssl生成的公鑰,復(fù)制的時(shí)候要小心不要有空格
var pubKey = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1QQRl0HlrVv6kGqhgonD6A9SU6ZJpnEN+Q0blT/ue6Ndt97WRfxtS'+
'As0QoquTreaDtfC4RRX4o+CU6BTuHLUm+eSvxZS9TzbwoYZq7ObbQAZAY+SYDgAA5PHf1wNN20dGMFFgVS/y0ZWvv1UNa2laEz0I8Vmr5ZlzIn88GkmSiQIDAQAB-----END PUBLIC KEY-----'
encryptor.setPublicKey(pubKey)//設(shè)置公鑰
var rsaPassWord = encryptor.encrypt('要加密的內(nèi)容') // 對(duì)內(nèi)容進(jìn)行加密
c#pem格式轉(zhuǎn)換
注:c#的RSACryptoServiceProvider默認(rèn)只支持xml格式的密鑰解析
public class RSA_Unit
{
static public string Base64EncodeBytes(byte[] byts)
{
return System.Convert.ToBase64String(byts);
}
static public byte[] Base64DecodeBytes(string str)
{
try
{
return System.Convert.FromBase64String(str);
}
catch
{
return null;
}
}
/// <summary>
/// 把字符串按每行多少個(gè)字?jǐn)嘈?
/// </summary>
static public string TextBreak(string text, int line)
{
var idx = 0;
var len = text.Length;
var str = new StringBuilder();
while (idx < len)
{
if (idx > 0)
{
str.Append('\n');
}
if (idx + line >= len)
{
str.Append(text.Substring(idx));
}
else
{
str.Append(text.Substring(idx, line));
}
idx += line;
}
return str.ToString();
}
}
static public class Extensions
{
/// <summary>
/// 從數(shù)組start開始到指定長度復(fù)制一份
/// </summary>
static public T[] sub<T>(this T[] arr, int start, int count)
{
T[] val = new T[count];
for (var i = 0; i < count; i++)
{
val[i] = arr[start + i];
}
return val;
}
static public void writeAll(this Stream stream, byte[] byts)
{
stream.Write(byts, 0, byts.Length);
}
}
點(diǎn)擊并拖拽以移動(dòng)
public class RSA_PEM
{
public static RSACryptoServiceProvider FromPEM(string pem)
{
var rsaParams = new CspParameters();
rsaParams.Flags = CspProviderFlags.UseMachineKeyStore;
var rsa = new RSACryptoServiceProvider(rsaParams);
var param = new RSAParameters();
var base64 = _PEMCode.Replace(pem, "");
var data = RSA_Unit.Base64DecodeBytes(base64);
if (data == null)
{
throw new Exception("PEM內(nèi)容無效");
}
var idx = 0;
//讀取長度
Func<byte, int> readLen = (first) =>
{
if (data[idx] == first)
{
idx++;
if (data[idx] == 0x81)
{
idx++;
return data[idx++];
}
else if (data[idx] == 0x82)
{
idx++;
return (((int)data[idx++]) << 8) + data[idx++];
}
else if (data[idx] < 0x80)
{
return data[idx++];
}
}
throw new Exception("PEM未能提取到數(shù)據(jù)");
};
//讀取塊數(shù)據(jù)
Func<byte[]> readBlock = () =>
{
var len = readLen(0x02);
if (data[idx] == 0x00)
{
idx++;
len--;
}
var val = data.sub(idx, len);
idx += len;
return val;
};
//比較data從idx位置開始是否是byts內(nèi)容
Func<byte[], bool> eq = (byts) =>
{
for (var i = 0; i < byts.Length; i++, idx++)
{
if (idx >= data.Length)
{
return false;
}
if (byts[i] != data[idx])
{
return false;
}
}
return true;
};
if (pem.Contains("PUBLIC KEY"))
{
/****使用公鑰****/
//讀取數(shù)據(jù)總長度
readLen(0x30);
if (!eq(_SeqOID))
{
throw new Exception("PEM未知格式");
}
//讀取1長度
readLen(0x03);
idx++;//跳過0x00
//讀取2長度
readLen(0x30);
//Modulus
param.Modulus = readBlock();
//Exponent
param.Exponent = readBlock();
}
else if (pem.Contains("PRIVATE KEY"))
{
/****使用私鑰****/
//讀取數(shù)據(jù)總長度
readLen(0x30);
//讀取版本號(hào)
if (!eq(_Ver))
{
throw new Exception("PEM未知版本");
}
//檢測(cè)PKCS8
var idx2 = idx;
if (eq(_SeqOID))
{
//讀取1長度
readLen(0x04);
//讀取2長度
readLen(0x30);
//讀取版本號(hào)
if (!eq(_Ver))
{
throw new Exception("PEM版本無效");
}
}
else
{
idx = idx2;
}
//讀取數(shù)據(jù)
param.Modulus = readBlock();
param.Exponent = readBlock();
param.D = readBlock();
param.P = readBlock();
param.Q = readBlock();
param.DP = readBlock();
param.DQ = readBlock();
param.InverseQ = readBlock();
}
else
{
throw new Exception("pem需要BEGIN END標(biāo)頭");
}
rsa.ImportParameters(param);
return rsa;
}
static private Regex _PEMCode = new Regex(@"--+.+?--+|\s+");
static private byte[] _SeqOID = new byte[] { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
static private byte[] _Ver = new byte[] { 0x02, 0x01, 0x00 };
/// <summary>
/// 將RSA中的密鑰對(duì)轉(zhuǎn)換成PEM格式,usePKCS8=false時(shí)返回PKCS#1格式,否則返回PKCS#8格式,如果convertToPublic含私鑰的RSA將只返回公鑰,僅含公鑰的RSA不受影響
/// </summary>
public static string ToPEM(RSACryptoServiceProvider rsa, bool convertToPublic, bool usePKCS8)
{
//https://www.jianshu.com/p/25803dd9527d
//https://www.cnblogs.com/ylz8401/p/8443819.html
//https://blog.csdn.net/jiayanhui2877/article/details/47187077
//https://blog.csdn.net/xuanshao_/article/details/51679824
//https://blog.csdn.net/xuanshao_/article/details/51672547
var ms = new MemoryStream();
//寫入一個(gè)長度字節(jié)碼
Action<int> writeLenByte = (len) =>
{
if (len < 0x80)
{
ms.WriteByte((byte)len);
}
else if (len <= 0xff)
{
ms.WriteByte(0x81);
ms.WriteByte((byte)len);
}
else
{
ms.WriteByte(0x82);
ms.WriteByte((byte)(len >> 8 & 0xff));
ms.WriteByte((byte)(len & 0xff));
}
};
//寫入一塊數(shù)據(jù)
Action<byte[]> writeBlock = (byts) =>
{
var addZero = (byts[0] >> 4) >= 0x8;
ms.WriteByte(0x02);
var len = byts.Length + (addZero ? 1 : 0);
writeLenByte(len);
if (addZero)
{
ms.WriteByte(0x00);
}
ms.Write(byts, 0, byts.Length);
};
//根據(jù)后續(xù)內(nèi)容長度寫入長度數(shù)據(jù)
Func<int, byte[], byte[]> writeLen = (index, byts) =>
{
var len = byts.Length - index;
ms.SetLength(0);
ms.Write(byts, 0, index);
writeLenByte(len);
ms.Write(byts, index, len);
return ms.ToArray();
};
if (rsa.PublicOnly || convertToPublic)
{
/****生成公鑰****/
var param = rsa.ExportParameters(false);
//寫入總字節(jié)數(shù),不含本段長度,額外需要24字節(jié)的頭,后續(xù)計(jì)算好填入
ms.WriteByte(0x30);
var index1 = (int)ms.Length;
//固定內(nèi)容
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
ms.writeAll(_SeqOID);
//從0x00開始的后續(xù)長度
ms.WriteByte(0x03);
var index2 = (int)ms.Length;
ms.WriteByte(0x00);
//后續(xù)內(nèi)容長度
ms.WriteByte(0x30);
var index3 = (int)ms.Length;
//寫入Modulus
writeBlock(param.Modulus);
//寫入Exponent
writeBlock(param.Exponent);
//計(jì)算空缺的長度
var byts = ms.ToArray();
byts = writeLen(index3, byts);
byts = writeLen(index2, byts);
byts = writeLen(index1, byts);
return "-----BEGIN PUBLIC KEY-----\n" + RSA_Unit.TextBreak(RSA_Unit.Base64EncodeBytes(byts), 64) + "\n-----END PUBLIC KEY-----";
}
else
{
/****生成私鑰****/
var param = rsa.ExportParameters(true);
//寫入總字節(jié)數(shù),后續(xù)寫入
ms.WriteByte(0x30);
int index1 = (int)ms.Length;
//寫入版本號(hào)
ms.writeAll(_Ver);
//PKCS8 多一段數(shù)據(jù)
int index2 = -1, index3 = -1;
if (usePKCS8)
{
//固定內(nèi)容
ms.writeAll(_SeqOID);
//后續(xù)內(nèi)容長度
ms.WriteByte(0x04);
index2 = (int)ms.Length;
//后續(xù)內(nèi)容長度
ms.WriteByte(0x30);
index3 = (int)ms.Length;
//寫入版本號(hào)
ms.writeAll(_Ver);
}
//寫入數(shù)據(jù)
writeBlock(param.Modulus);
writeBlock(param.Exponent);
writeBlock(param.D);
writeBlock(param.P);
writeBlock(param.Q);
writeBlock(param.DP);
writeBlock(param.DQ);
writeBlock(param.InverseQ);
//計(jì)算空缺的長度
var byts = ms.ToArray();
if (index2 != -1)
{
byts = writeLen(index3, byts);
byts = writeLen(index2, byts);
}
byts = writeLen(index1, byts);
var flag = " PRIVATE KEY";
if (!usePKCS8)
{
flag = " RSA" + flag;
}
return "-----BEGIN" + flag + "-----\n" + RSA_Unit.TextBreak(RSA_Unit.Base64EncodeBytes(byts), 64) + "\n-----END" + flag + "-----";
}
}
}
以上就是詳解c#與js的rsa加密互通的詳細(xì)內(nèi)容,更多關(guān)于c#與js的rsa加密互通的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
c#開發(fā)的程序安裝時(shí)動(dòng)態(tài)指定windows服務(wù)名稱
前段時(shí)間由于項(xiàng)目的需求,要在Windows里把同樣的組件制作成多個(gè)不同名稱的服務(wù),這些服務(wù)完成類似的功能,僅需要修改業(yè)務(wù)配置文件2012-06-06
使用C#實(shí)現(xiàn)對(duì)任意區(qū)域任意大小的截圖
這篇文章主要為大家詳細(xì)介紹了如何使用C#實(shí)現(xiàn)簡單的截圖功能,可以對(duì)任意區(qū)域任意大小的截圖,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01
SQL Server存儲(chǔ)過程在C#中調(diào)用的簡單實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于SQL Server存儲(chǔ)過程在C#中調(diào)用的簡單實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用SQL Server存儲(chǔ)過程具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
C#游戲開發(fā)之實(shí)現(xiàn)俄羅斯方塊游戲
這篇文章主要為大家詳細(xì)介紹了C#如何實(shí)現(xiàn)經(jīng)典俄羅斯方塊游戲,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下2023-01-01
C#如何遠(yuǎn)程讀取服務(wù)器上的文本內(nèi)容
這篇文章主要介紹了C#如何遠(yuǎn)程讀取服務(wù)器上的文本內(nèi)容,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01

