详解c#与js的rsa加密互通
作者:code2roc
ASN.1
抽象语法表示(标记)ASN.1(Abstract Syntax Notation One )一种数据定义语言,描述了对数据进行表示、编码、传输和解码的数据格式。网络管理系统中的管理信息库(MIB)、应用程序的数据结构、协议数据单元(PDU)都是用ASN.1定义的。
可以理解为ASN.1是对密钥结构定义的一种规范
密钥结构类型
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是某个算法自己指定的二进制格式 ; 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格式
二进制格式
pem格式
把der格式的数据用base64编码后,然后再在头尾加上一段“-----”开始的标记
证书类型
X.509证书
X.509只包含公钥,没有私钥,这种证书一般公开发布,可用于放在客服端使用,用于加密、验签
PKCS#12证书
因为X.509证书只包含公钥,但有些时候我们需要把私钥和公钥合并成一个证书,放在服务端使用,用于解密、签名。
PKCS#12就定义了这样一种证书,它既包含了公钥有包含了私钥。典型的入pfx、p12证书就是PKCS#12证书。
PKCS#7证书
当你收到一个网站的证书后,你需要验证其真实性。因为一个X.509证书包含了公钥、持有人信息、签名。为了验证其真实性,你需要签证其签名,而验证签名则需要签发的CA机构的公钥证书。同样原理,当你拿到CA机构的公钥证书后,你也需要验证该CA机构的真实性,而验证该CA机构的证书,你需要该CA上级机构的CA公钥证书...以此类推,你需要一直验证到根证书为止。所以为了验证一个网站证书的真实性,你需要的不仅一张证书,而是一个证书链。而PKCS#7就定义了这样一个证书链的类型结构。典型如p7b后缀名的证书就是这样的格式。
证书后缀
.cer/.crt:存放公钥,没有私钥,就是一个X.509证书,二进制形式存放
.pfx/.p12:存放公钥和私钥,通常包含保护密码,二进制方式
证书与密钥关系
数字证书和私钥是匹配的关系。就好比钥匙牌和钥匙的关系。在数字证书签发的时候,数字证书签发系统(CA系统),在生成数字证书的同时,还会随机生成一对密钥,一个私钥,一个公钥。数字证书标示用户身份, 相匹配的私钥和公钥,则是用来保障用户身份的可认证性。就好比咱们拿着一串钥匙,每个钥匙上都标明有时某某房间的钥匙,但是否是真的,还需要看能不能打开相应的房门。
密钥生成
/// <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 = RSA_PEM.ToPEM(rsa, false, false); string publicKey = RSA_PEM.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 = 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("不支持的密钥类型"); } //加密块最大长度限制,如果加密数据的长度超过 秘钥长度/8-11,会引发长度不正确的异常,所以进行数据的分块加密 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默认PKCS#1结构,生成密钥时需要注意
<script src="http://passport.cnblogs.com/scripts/jsencrypt.min.js"></script> var encryptor = new JSEncrypt() // 创建加密对象实例 //之前ssl生成的公钥,复制的时候要小心不要有空格 var pubKey = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1QQRl0HlrVv6kGqhgonD6A9SU6ZJpnEN+Q0blT/ue6Ndt97WRfxtS'+ 'As0QoquTreaDtfC4RRX4o+CU6BTuHLUm+eSvxZS9TzbwoYZq7ObbQAZAY+SYDgAA5PHf1wNN20dGMFFgVS/y0ZWvv1UNa2laEz0I8Vmr5ZlzIn88GkmSiQIDAQAB-----END PUBLIC KEY-----' encryptor.setPublicKey(pubKey)//设置公钥 var rsaPassWord = encryptor.encrypt('要加密的内容') // 对内容进行加密
c#pem格式转换
注:c#的RSACryptoServiceProvider默认只支持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> /// 把字符串按每行多少个字断行 /// </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> /// 从数组start开始到指定长度复制一份 /// </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); } } 点击并拖拽以移动 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内容无效"); } 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未能提取到数据"); }; //读取块数据 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内容 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")) { /****使用公钥****/ //读取数据总长度 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")) { /****使用私钥****/ //读取数据总长度 readLen(0x30); //读取版本号 if (!eq(_Ver)) { throw new Exception("PEM未知版本"); } //检测PKCS8 var idx2 = idx; if (eq(_SeqOID)) { //读取1长度 readLen(0x04); //读取2长度 readLen(0x30); //读取版本号 if (!eq(_Ver)) { throw new Exception("PEM版本无效"); } } else { idx = idx2; } //读取数据 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标头"); } 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中的密钥对转换成PEM格式,usePKCS8=false时返回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(); //写入一个长度字节码 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)); } }; //写入一块数据 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); }; //根据后续内容长度写入长度数据 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); //写入总字节数,不含本段长度,额外需要24字节的头,后续计算好填入 ms.WriteByte(0x30); var index1 = (int)ms.Length; //固定内容 // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1" ms.writeAll(_SeqOID); //从0x00开始的后续长度 ms.WriteByte(0x03); var index2 = (int)ms.Length; ms.WriteByte(0x00); //后续内容长度 ms.WriteByte(0x30); var index3 = (int)ms.Length; //写入Modulus writeBlock(param.Modulus); //写入Exponent writeBlock(param.Exponent); //计算空缺的长度 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); //写入总字节数,后续写入 ms.WriteByte(0x30); int index1 = (int)ms.Length; //写入版本号 ms.writeAll(_Ver); //PKCS8 多一段数据 int index2 = -1, index3 = -1; if (usePKCS8) { //固定内容 ms.writeAll(_SeqOID); //后续内容长度 ms.WriteByte(0x04); index2 = (int)ms.Length; //后续内容长度 ms.WriteByte(0x30); index3 = (int)ms.Length; //写入版本号 ms.writeAll(_Ver); } //写入数据 writeBlock(param.Modulus); writeBlock(param.Exponent); writeBlock(param.D); writeBlock(param.P); writeBlock(param.Q); writeBlock(param.DP); writeBlock(param.DQ); writeBlock(param.InverseQ); //计算空缺的长度 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加密互通的详细内容,更多关于c#与js的rsa加密互通的资料请关注脚本之家其它相关文章!