.NET RSA非对称加密的前后端实现(.NET 8 Web API + 微信小程序)


前言

在涉及到身份验证和机密数据的传输时,敏感数据明文传输本身是具有极大风险被泄露的。即使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}`);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值