RSA加密算法是一种非对称加密算法。在公开密钥加密和电子商业中RSA被广泛使用.
RSA算法是第一个能同时用于加密和数字签名的算法.
也易于理解和操作。RSA是被研究得最广泛的公钥算法,从提出到现今的三十多年里,经历了各种攻击的考验,逐渐为人们接受,截止2017年被普遍认为是最优秀的公钥方案之一
已公开的或已知的攻击方法编辑
1,针对RSA最流行的攻击一般是基于大数因数分解。1999年,RSA-155 (512 bits)被成功分解,花了五个月时间(约8000 MIPS年)和224 CPU hours在一台有3.2G中央内存的Cray C916计算机上完成。
2,秀尔算法
量子计算里的秀尔算法能使穷举的效率大大的提高。由于RSA算法是基于大数分解(无法抵抗穷举攻击),因此在未来量子计算能对RSA算法构成较大的威胁。一个拥有N量子比特的量子计算机,每次可进行2^N次运算,理论上讲,密钥为1024位长的RSA算法,用一台512量子比特位的量子计算机在1秒内即可破解。
当然在目前2019年 量子技术还不成熟的情况下. 我们暂时先不考虑安全性, 因为大多数的加密行为保护的都是没有多大价值的信息. 真正值得保护的高价值信息. 会有很多套算法一起用. 破解成本远超过所得收益.这就够了…
网上流行的方法是RSA 的密钥是固定,估计很多程序员直接就用了网上的密钥…这样是极度不安全的. 为了安全我们必须自己生成RSA密钥.
首先来看主程序C#端
RSACryptoHelper.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace Common
{
/// <summary>
/// RAS加解密算法
/// 先用 RSACryptoHelper.CreateKey(); 创建一对密钥,第一个是私钥,第二个是公钥. 公钥发给客户端,客户端用js和公钥加密,密文到服务器 用私钥解密
/// 对应的前台js加密算法库为jsencrypt.js
/// 参考 https://cdn.bootcss.com/jsencrypt/3.0.0-beta.1/jsencrypt.js
/// 参考 https://blog.youkuaiyun.com/zhangjianying/article/details/79873392
/// </summary>
/// <example>
///
/// var keys= RSACryptoHelper.CreateKey();//创建一对密钥,第一个是私钥,第二个是公钥.公钥发给客户端,密文到服务器 用私钥解密
///
/// var miwen = RSACryptoHelper.RSA_Encrypt("被加密的文本",keys[0]);//公钥加密
/// var yuanwen = RSACryptoHelper.RSA_Decrypt(miwen,keys[1]);//私钥解密
///
/// </example>
public static class RSACryptoHelper
{
/// <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 = Convert.ToBase64String(sp.ExportCspBlob(true));//private key
string publicKey = 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>
/// 加密 用的是PEM格式的密钥
/// </summary>
/// <param name="str_Plain_Text">要加密的数据</param>
/// <param name="str_Public_PEMKey">Xml格式的公钥</param>
/// <returns></returns>
public static string Encrypt_PEMKey(string str_Plain_Text, string str_Public_PEMKey)
{
using (RSACryptoServiceProvider RSA = RSA_PEM.FromPEM(str_Public_PEMKey))
{
return Encrypt(str_Plain_Text, RSA);
}
}
/// <summary>
/// 加密 用的是Xml格式的密钥
/// </summary>
/// <param name="str_Plain_Text">要加密的数据</param>
/// <param name="str_Public_XmlKey">Xml格式的公钥</param>
/// <returns></returns>
public static string Encrypt_XmlKey(string str_Plain_Text, string str_Public_XmlKey)
{
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
RSA.FromXmlString(str_Public_XmlKey);//载入公钥
return Encrypt(str_Plain_Text, RSA);
}
}
private static string Encrypt(string str_Plain_Text, RSACryptoServiceProvider RSA)
{
var data = Encoding.UTF8.GetBytes(str_Plain_Text);
int buffersize = (RSA.KeySize / 8) - 11;
var buffer = new byte[buffersize];
using (MemoryStream input = new MemoryStream(data), output = new MemoryStream())
{
while (true)
{
int readsize = input.Read(buffer, 0, buffersize);
if (readsize <= 0)
{
break;
}
var temp = new byte[readsize];
Array.Copy(buffer, 0, temp, 0, readsize);
var EncBytes = RSA.Encrypt(temp, false);
output.Write(EncBytes, 0, EncBytes.Length);
}
return Convert.ToBase64String(output.ToArray());
}
}
/// <summary>
/// 解密 用的是Xml格式的密钥
/// </summary>
/// <param name="str_Cypher_Text">密文</param>
/// <param name="str_Private_Key">密钥</param>
/// <returns></returns>
public static string Decrypt_XmlKey(string str_Cypher_Text, string str_Private_XmlKey)
{
using (var RSA = new RSACryptoServiceProvider())
{
RSA.FromXmlString(str_Private_XmlKey);
return Decrypt(str_Cypher_Text, RSA);
}
}
/// <summary>
/// 解密 用的是PEM格式的密钥
/// </summary>
/// <param name="str_Cypher_Text">密文</param>
/// <param name="str_Private_Key">密钥</param>
/// <returns></returns>
public static string Decrypt_PEMKey(string str_Cypher_Text, string str_Private_PEMKey)
{
//using (var RSA = new RSACryptoServiceProvider())
using (var RSA = RSA_PEM.FromPEM(str_Private_PEMKey))
{
return Decrypt(str_Cypher_Text, RSA);
}
}
private static string Decrypt(string str_Cypher_Text, RSACryptoServiceProvider RSA)
{
var data = Convert.FromBase64String(str_Cypher_Text);
//RSA.FromXmlString(str_Private_Key);
int buffersize = RSA.KeySize / 8;
var buffer = new byte[buffersize];
using (MemoryStream input = new MemoryStream(data),
output = new MemoryStream())
{
while (true)
{
int readsize = input.Read(buffer, 0, buffersize);
if (readsize <= 0)
{
break;
}
var temp = new byte[readsize];
Array.Copy(buffer, 0, temp, 0, readsize);
var DecBytes = RSA.Decrypt(temp, false);
output.Write(DecBytes, 0, DecBytes.Length);
}
return Encoding.UTF8.GetString(output.ToArray());
}
}
}
}
第二个重要的文件来自国内的一位大神, 他写的PEMS密钥转换代码, 使得C#默认的RSACryptoServiceProvider 算法支持 PEM 密钥. 也就是PKCS#1、PKCS#8格式格式的密钥
他的博客 https://www.cnblogs.com/xiangyuecn/p/9922325.html
他的这个项目地址 https://github.com/xiangyuecn/RSA-csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Common
{
/// <summary>
/// RSA PEM格式秘钥对的解析和导出
/// </summary>
public class RSA_PEM {
/// <summary>
/// 用PEM格式密钥对创建RSA,支持PKCS#1、PKCS#8格式的PEM
/// </summary>
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.youkuaiyun.com/jiayanhui2877/article/details/47187077
//https://blog.youkuaiyun.com/xuanshao_/article/details/51679824
//https://blog.youkuaiyun.com/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 + "-----";
}
}
}
}
第三个文件是一个辅助类 RSA_Unit .cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Common
{
/// <summary>
/// 封装的一些通用方法
/// </summary>
public class RSA_Unit {
static public string Base64EncodeBytes(byte[] byts) {
return Convert.ToBase64String(byts);
}
static public byte[] Base64DecodeBytes(string str) {
try {
return 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 LoginController : MK.AppCode.MVC.BaseController
{
// GET: /BaseApp/Login/
public ActionResult Index()
{
CreateRSAKey();
return View();
}
public void CreateRSAKey()
{
var RSAKey = Common.RSACryptoHelper.CreateKey_PEM_PKCS1();
Session["RSAPrivateKey"] = RSAKey[0];//私钥自己存好,等密码回来以后用来解密.
ViewBag.RSAPublicKey = RSAKey[1]; //公钥发到客户端, 客户端用来加密.
}
[HttpPost]
public ActionResult Index(string sloginname, string sloginpwd, string securityCode, string RSAKey)
{
string RSAPrivateKey = Session["RSAPrivateKey"] as string;
sloginname = Common.RSACryptoHelper.Decrypt_PEMKey(sloginname, RSAPrivateKey); //为防止黑客在网络传输中 窃取明文用户名和密码
sloginpwd = Common.RSACryptoHelper.Decrypt_PEMKey(sloginpwd, RSAPrivateKey); //为防止黑客在网络传输中 窃取明文用户名和密码
//刷新新的Key
CreateRSAKey();
//....
//....
}
}
前台界面则需要引入
项目地址 http://www.travistidwell.com/jsencrypt
github: https://github.com/travist/jsencrypt
也可以直接复制csdn的代码.
在表单提交前.用JSEncrypt 进行加密.
<script src="http://passport.cnblogs.com/scripts/jsencrypt.min.js"></script>
<form id="loginform" action="/BaseApp/Login/Index" method="post">
<textarea id="RSAPublicKey" style="display:none;">@ViewBag.RSAPublicKey</textarea>
<input type="hidden" id="sloginname" name="sloginname" />
<input type="hidden" id="sloginpwd" name="sloginpwd" />
<input type="hidden" id="securityCode" name="securityCode" />
<div class="form-group has-feedback">
<input type="text" class="form-control" id="userName" placeholder="请输入帐号" tabindex="1" onkeydown="gotonext()">
<span class="glyphicon glyphicon-envelope form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<input type="password" class="form-control" id="password" placeholder="请输入密码" tabindex="2" />
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<input type="text" name="verifyCode" required style="width:200px;" tabindex="3" />
<img src="~/BaseApp/Login/GetSecurityCode" id="secImg" onclick="RefreshIMG()" title="单击刷新" />
</div>
<div class="row">
<!-- /.col -->
<div class="col-xs-12">
<button type="submit" class="btn btn-success btn-block btn-flat MKbtn" name="btnLogin" tabindex="4">登录</button>
</div>
<!-- /.col -->
</div>
</form>
<Scrpt>
$('#loginform').on("submit", function () {
debugger;
var PUBLIC_KEY = $("#RSAPublicKey").val();
var $userName = $('#userName').val();
var $password = $('#password').val();
var $verifyCode = $('input[name="verifyCode"]').val();
if ($userName == "" || $password == "" || $verifyCode == "")
{
alert("用户名、密码、验证码不能为空!");
return false;
}
//使用公钥加密
var encrypt = new JSEncrypt();
encrypt.setPublicKey(PUBLIC_KEY);
$('input[name="sloginname"]').val(encrypt.encrypt($userName));
$('input[name="sloginpwd"]').val(encrypt.encrypt($password));
return true;
});
</script>