文章目录
前言
在涉及到身份验证和机密数据的传输时,敏感数据明文传输本身是具有极大风险被泄露的。即使HTTPS通过SSL/TLS提供了数据传输的加密,当时还是存在明文显示的阶段(到达客户端)。考虑到业务里涉及到的敏感数据,对数据的加密还是有必要的。本文主要介绍RSA加密,详细阐述如何通过代码实现前端(微信小程序)加密,后端(解密)。
一、RSA加密是什么?
RSA算法是一种非对称加密算法,与对称加密算法不同的是,RSA算法有两个不同的密钥,一个是公钥,一个是私钥。由罗纳德・李维斯特(Ron Rivest)、阿迪・萨莫尔(Adi Shamir)和伦纳德・阿德曼(Leonard Adleman)在 1977 年提出。
RSA 加密的安全性取决于 密钥(公钥 / 私钥)的保护,公钥公开是 RSA 的设计特性,攻击者无法通过公钥反推私钥(依赖大数分解的计算复杂性,目前 2048 位以上密钥难以破解)。私钥是 RSA 安全的核心,一旦泄露,所有基于该密钥的加密、签名都会被破解。
二、后端解密(.NET 8 Web API实现)
2.1 初始化构造函数
将RSA作为一个帮助类(RSAHelper)封装,加密解密分别通过_privateKeyRsaProvider和_publicKeyRsaProvider RSA对象调用服务。公钥和私钥保存在appsettings.json里,实例化的时候传入密钥字符串,并且指定哈希算法和文本编码。
public class RSAHelper
{
private readonly RSA _privateKeyRsaProvider; //私钥创建的RSA对象
private readonly RSA _publicKeyRsaProvider; //公钥创建的RSA对象
private readonly HashAlgorithmName _hashAlgorithmName; //指定使用哪种哈希算法
private readonly Encoding _encoding; //指定使用哪种编码
private readonly string _publicKey; //公钥
private readonly string _privateKey; //私钥
/// <summary>
/// 实例化RSAHelper
/// </summary>
/// <param name="rsaType">加密算法类型 RSA SHA1;RSA2 SHA256 密钥长度至少为2048</param>
/// <param name="encoding">编码类型</param>
/// <param name="_RSAkey">RSA密钥字符串对象</param>
public RSAHelper(RSAType rsaType, Encoding encoding, RSAKey? _RSAkey)
{
_encoding = encoding;
_publicKey = _RSAkey.PublicKey;
_privateKey = _RSAkey.PrivateKey;
if (!string.IsNullOrEmpty(_privateKey))
{
_privateKeyRsaProvider = CreateRsaProviderFromPrivateKey(_privateKey);
}
if (!string.IsNullOrEmpty(_publicKey))
{
_publicKeyRsaProvider = CreateRsaProviderFromPublicKey(_publicKey);
}
_hashAlgorithmName = rsaType == RSAType.RSA ? HashAlgorithmName.SHA1 : HashAlgorithmName.SHA256;
}
/***********************************************************************************************************/
2.2 使用私钥创建RSA实例
将传入的 Base64 编码的私钥字符串解码为字节数组 privateKeyBits,后续会基于这些字节数据解析 RSA 私钥的各个参数。RSAParameters 这个结构体用于存储 RSA 私钥的各个参数,如模数(Modulus)、指数(Exponent)、私钥指数(D)等。最终返回RSA实例。
public RSA CreateRsaProviderFromPrivateKey(string privateKey)
{
var privateKeyBits = Convert.FromBase64String(privateKey);
var rsa = RSA.Create();
var rsaParameters = new RSAParameters();
using (BinaryReader binr = new BinaryReader(new MemoryStream(privateKeyBits)))
{
byte bt = 0;
ushort twobytes = 0;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
else
throw new Exception("Unexpected value read binr.ReadUInt16()");
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102)
throw new Exception("Unexpected version");
bt = binr.ReadByte();
if (bt != 0x00)
throw new Exception("Unexpected value read binr.ReadByte()");
rsaParameters.Modulus = binr.ReadBytes(GetIntegerSize(binr));
rsaParameters.Exponent = binr.ReadBytes(GetIntegerSize(binr));
rsaParameters.D = binr.ReadBytes(GetIntegerSize(binr));
rsaParameters.P = binr.ReadBytes(GetIntegerSize(binr));
rsaParameters.Q = binr.ReadBytes(GetIntegerSize(binr));
rsaParameters.DP = binr.ReadBytes(GetIntegerSize(binr));
rsaParameters.DQ = binr.ReadBytes(GetIntegerSize(binr));
rsaParameters.InverseQ = binr.ReadBytes(GetIntegerSize(binr));
}
rsa.ImportParameters(rsaParameters);
return rsa;
}
private int GetIntegerSize(BinaryReader binr)
{
byte bt = 0;
int count = 0;
bt = binr.ReadByte();
if (bt != 0x02)
return 0;
bt = binr.ReadByte();
if (bt == 0x81)
count = binr.ReadByte();
else
if (bt == 0x82)
{
var highbyte = binr.ReadByte();
var lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = bt;
}
while (binr.ReadByte() == 0x00)
{
count -= 1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current);
return count;
}
2.3 解密
通过2.2根据私钥私钥创建的RSA对象解析客户端返回的加密字符串,完成解密。
public string Decrypt(string cipherText)
{
if (_privateKeyRsaProvider == null)
{
throw new Exception("_privateKeyRsaProvider is null");
}
return Encoding.UTF8.GetString(_privateKeyRsaProvider.Decrypt(Convert.FromBase64String(cipherText), RSAEncryptionPadding.Pkcs1));
}
2.4 DI容器的注入
注册RSAHelper的实例。并读取appsettings.json里的密钥。
Program.cs
builder.Services.AddTransient<RSAHelper>(provider =>
new RSAHelper(RSAType.RSA2, Encoding.UTF8,
builder.Configuration.GetSection("RSA").Get<RSAKey>()));
appsettings.json
"RSA": {
"PublicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9Mv9pn+yP9byekwRmPfx\r\namCTW9voZm81lSAhlTa7VLjwapWPkTSqS7EuETEnJxjnKAnyK8Yso6AQzFU9wmZ9\r\nrkZRNXHF/fjJqBaaN7zkwOgc+kKYR7ODjKj7Sossd+2Ojhiihrj8KZw/wdz5sXmb\r\n4Fty5qeHzd7k3KWbNm7l9lMbJl5vyEhBID1DaCBBeujjddWcY05kkrDXtM5G+YlF\r\nLYac3br1iHtOAGyKMWX4eIDPoD0/lcnkQp6LgcdFFJzzoPkftoAWe6C4YEAVYRD/\r\nuFIiJm+JBAXGLGX32qfRGes0pE1PsoJWdvg0vLigS+QXciXznMJ4xZHx4vO3kv1e\r\nCQIDAQAB",
"PrivateKey": "MIIEowIBAAKCAQEA9Mv9pn+yP9byekwRmPfxamCTW9voZm81lSAhlTa7VLjwapWP\r\nkTSqS7EuETEnJxjnKAnyK8Yso6AQzFU9wmZ9rkZRNXHF/fjJqBaaN7zkwOgc+kKY\r\nR7ODjKj7Sossd+2Ojhiihrj8KZw/wdz5sXmb4Fty5qeHzd7k3KWbNm7l9lMbJl5v\r\nyEhBID1DaCBBeujjddWcY05kkrDXtM5G+YlFLYac3br1iHtOAGyKMWX4eIDPoD0/\r\nlcnkQp6LgcdFFJzzoPkftoAWe6C4YEAVYRD/uFIiJm+JBAXGLGX32qfRGes0pE1P\r\nsoJWdvg0vLigS+QXciXznMJ4xZHx4vO3kv1eCQIDAQABAoIBAA+1opjDg58krmlq\r\nRcvVB41d1r8ToPmn496m0fjnKwTwHF+eqvi00kPqwivQxXQ1HSp83ulvvZAflsxi\r\nIQU/egvLz+zX2Q6oH1rV3n46tUxq6OcnjGm3i7bkZkH9pfQpTtdwHcf7CZWrBQ1u\r\nHqkExpvSipBiSm0MA5BkYU5QNhpFUWyNHgakOLWr5+LNrX3b3zuvgAMvw/1ZgpY2\r\nmd4KP9rkNqzaJoFbDPJjAP9vKrQLCB1XHqJjRzebNb9gIB4VvJgLrzjeXV9kJdTG\r\n6pH3knGUW9qSm4QClx3enTNBGJZuFYvWwEG9e8aTQVlF7e7CyewVxsZAZA2kM3nF\r\n9KlousECgYEA+wUmaTshEnLymsqmsTlBWnZjv5IQxVha1/CT0oVaQvcI1yhKY3/O\r\nzBfq6Se3aIg9dEpwNd/IToM/+J2qN2iHXuhEUWh6XTdErPvp7NHFeZfMjB2CMkxv\r\ndL4c4ajgrVH5IOLGxOp2w6wBStvxGDoBGR3H0MlIqcieJcKALPJ22iECgYEA+ac8\r\nGV/lppClTew4ZoNmvASPDWzV4/En+IJuxFX8R9qLX1p7RuAdz969aNQ6lUhAD2yN\r\nO8Z6AgplHGOYohqSDBlVi3O9nBCS71IXWdM7PzSTMcHdZqCSfaeUZIaUtERxWxV9\r\n1P8d+FlpUlPdqP0KcNETiMuVTYSFzMR3l9/SFukCgYEAqAMNb5elDi0j1plXMv2o\r\nkXz2TK3eTchSnYAn4Ntiy7l/EEumZqyP5KkJkV8E8pFl2KVOL7EF6A8PTT9yQ73S\r\nBaMevwJYDHZGoCXIr7rEGxuQTCQgFbx1SUVKHXwkTb21Kt6+veRU86j4AHvwBhAk\r\n1gPJzyQ6X3Go8IsbsXnkNSECgYAMAc8SKVqSOEK7DLFpvMgkItP+gU2IOQEDIsLR\r\nDpGjow8w9tkW/5Bvfvb9I7ftlhv+oenpBhK4JmfWhbPBmCWOD2nd2yMzbliiyf/1\r\nggNHdu/dZMWROgfRo02vyAk+4kvRZac3p015tFMscnwmtITcCDI4UhJMOCN3Rbu6\r\nwjkIgQKBgCZ8QfYCdpSJMUfYAOx7d5w4oNyCKJYO9NQOYPrS9tqwWwUp7OF07yAZ\r\nxjDwqCuCC/YTExFeG1qCJ60VXPjJ3cmLG7PrLYZ169gxEHuiI9TwUZ9sAN99R0/4\r\nmB3vdoeGvT/J2uRTV2bj13wAnOHDMPN9adk+/ExyfcTU54gecjWW"
},
2.5 调用RSAHelper服务
通过构造函数注入RSAHelper,根据业务需求调用RSA解密服务。
public class LoginController : ControllerBase
{
private readonly ILogger<LoginController> _logger;
private readonly ICustomJWTService _jwtService;
private readonly IWebUser _webUser;
private readonly RSAHelper _RSAHelper;
public LoginController(ILogger<LoginController> logger, ICustomJWTService jwtService, IWebUser webUser,RSAHelper RSAHelper)
{
_jwtService = jwtService;
_logger = logger;
_webUser = webUser;
_RSAHelper = RSAHelper;
}
/*********************************/
}
三、前端报文加密(微信小程序实现)
前端实现RSA加密一般使用jsencrypt。但是微信小程序里没法直接通过构建npm引入,需要自己手动引入。文章顶部提供了能在微信小程序里正常执行的jsencrypt.min.js源文件。
crypto.js
const JSEncrypt = require('../utils/encrypt/jsencrypt.min')
// 示例:公钥(实际使用中,请替换为你的密钥)
const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9Mv9pn+yP9byekwRmPfx
amCTW9voZm81lSAhlTa7VLjwapWPkTSqS7EuETEnJxjnKAnyK8Yso6AQzFU9wmZ9
rkZRNXHF/fjJqBaaN7zkwOgc+kKYR7ODjKj7Sossd+2Ojhiihrj8KZw/wdz5sXmb
4Fty5qeHzd7k3KWbNm7l9lMbJl5vyEhBID1DaCBBeujjddWcY05kkrDXtM5G+YlF
LYac3br1iHtOAGyKMWX4eIDPoD0/lcnkQp6LgcdFFJzzoPkftoAWe6C4YEAVYRD/
uFIiJm+JBAXGLGX32qfRGes0pE1PsoJWdvg0vLigS+QXciXznMJ4xZHx4vO3kv1e
CQIDAQAB
-----END PUBLIC KEY-----`;
/**
* 加密函数
* @param {string} data - 要加密的数据
* @returns {string} - 加密后的数据
*/
export function encryptData(data) {
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey);
return encryptor.encrypt(data);
}
// 加密登录账户密码报文
const _encryptStr = encryptData(`username=${account}&password=${password}`);