在C#中进行RSA非对称加密和解密

1、文章回顾

    在文章windows使用openssl生成公钥和私钥icon-default.png?t=M7J4https://www.luweidong.cn/details/72 中成功使用openssl生成了用于RSA加密和解密的公钥和私钥。

    在文章在JavaScript中进行RSA加密和解密icon-default.png?t=M7J4https://www.luweidong.cn/details/86 中成功使用JavaScript进行了RSA加密和解密。

    如果你习惯使用.net平台,根据以上的文章已经实现了在网页端使用JS进行RSA加密解密的功能,但是还有.net后端呢?只有后端与前端相互配合,才能实现通过RSA加密和解密的整个流程。今天这篇文章就是把这环得圆上。

2、关键代码

    示例基于.net6框架。关于RSA的实现函数都放在一个文件中。特别说明一下,该代码只支持windows平台。

    密钥生成的关键函数:

        #region 密钥生成
        /// <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 = ToPEM(rsa, false, false);
            string publicKey = 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 = ToPEM(rsa, false, true);
            string publicKey = ToPEM(rsa, true, true);
            return new string[] { privateKey, publicKey };
        }
        #endregion

    加密解密函数:

    加密块有最大长度限制,如果加密数据的长度超过私钥长度/8-11,会引发长度不正确的异常,所以进行数据的分块加密。

        #region 加密和解密
        /// <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 = FromPEM(PublicKeyString);
                    break;
                default:
                    throw new Exception("不支持的密钥类型");
            }            
            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 = 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;
                }
            }
        }
        #endregion

3、功能测试

    在Program中首先生成私钥和公钥分别保存在pri.txt和pub.txt文本中。

            //生成公钥和密钥并保存到文本中
            string[] a = RSA_Tools.CreateKey_PEM_PKCS8();
            string privString = a[0];//私钥
            string pubString = a[1];//公钥
            File.WriteAllText("pri.txt", privString);
            File.WriteAllText("pub.txt", pubString);
            Console.WriteLine($"privateKey:{privString}");
            Console.WriteLine($"publicKey:{pubString}");

    分别读取私钥和公钥进行加密和解密测试:

            string privString = File.ReadAllText("pri.txt");
            string pubString = File.ReadAllText("pub.txt");
            string text = "123";
            Console.WriteLine($"被加密字符:{text}");
            string j = RSA_Tools.RSAEncrypt(text, pubString, "PEM");
            Console.WriteLine($"加密后字符:{j}");
            string b = RSA_Tools.RSADecrypt(j, privString, "PEM");
            Console.WriteLine($"解密后字符:{b}");

    如图,加密文本“123”,再用加密后的文本解密得到“123,测试成功。

4、JavaScript和.net后端互联互通测试

    使用文章在JavaScript中进行RSA加密和解密 中的示例对原始数据进行RSA加密,再把加密后的字符串用.net后端的代码解密,测试是否成功。注意双方使用的公钥和私钥需一致。

    简单列出加密解密的流程:

    1).net后端生成公钥和私钥,自个保存私钥,把公钥发给前端。(私钥不在网络上传播)

    2)前端接收到公钥后,使用公钥加密数据,把加密后的数据返回后端。

    3)后端使用私钥解密前端的数据,得到原始数据。

    RSA非对称加密和解密的关键就体现于此,私钥是绝不在网络上传播的,公钥是主动公开的。使用公钥加密的数据只有用配对的私钥才能解密!真是神奇,的数学。

    这里讲解第二步,JavaScript使用公钥加密代码如下:(注意公钥是第一步中.net后端生成的)

    <script type="text/javascript">
        $(function () {
            var encryptor = new JSEncrypt()  // 创建加密对象实例
            //之前ssl生成的公钥,复制的时候要小心不要有空格
            var pubKey = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1QQRl0HlrVv6kGqhgonD6A9
SU6ZJpnEN+Q0blT/ue6Ndt97WRfxtS' +
                'As0QoquTreaDtfC4RRX4o+CU6BTuHLUm+eSvxZS9TzbwoYZq7ObbQAZAY+SYDgAA5PHf1wNN20dGMFFgVS
/y0ZWvv1UNa2laEz0I8Vmr5ZlzIn88GkmSiQIDAQAB-----END PUBLIC KEY-----'
            encryptor.setPublicKey(pubKey)//设置公钥
            var rsaPassWord = encryptor.encrypt('123')
            console.log(rsaPassWord)

        });
    </script>

     把原始数据”123“加密后得到”KoO8MxKRr4mu5Z3iG4cpF/se+8Axdd0ydX374CSmFPj+asXCa/bvHcr3NBUpB3YBxi/B9LVzuajgnne 4nYWj8NERS2L5OM2WH11irLLxZNqG98uD9OOx2zO9CjT8VaMWEo0dErvcu VAdZeXpgiD1CJh5sWNKZEioBiVVU/sApzo=“

    使用后端解密:

            string privString = File.ReadAllText("pri.txt");
            string j = "KoO8MxKRr4mu5Z3iG4cpF/se+8Axdd0ydX374CSmFPj+asXCa/bvHcr3NBUpB3YBxi/B9LVzuajgnne4n
YWj8NERS2L5OM2WH11irLLxZNqG98uD9OOx2zO9CjT8VaMWEo0dErvcuVAdZeXpgiD1CJh
5sWNKZEioBiVVU/sApzo=";
            string b = RSA_Tools.RSADecrypt(j, privString, "PEM");
            Console.WriteLine($"解密后字符:{b}");

解密得到原始数据”123“。

5、代码下载

    至此已经实现了前后端的RSA非对称加密和解密的功能,可以愉快地基于此实现数据的安全传输。以上示例基于服务端生产一对密钥对,其实也可以前端和后端各自生成一对密钥对,并向对方分享各自的公钥。在传输数据前,使用对方的公钥加密数据后再进行传输,安全性将大大提高!

    在微信小程序上搜索(密码盾)集成了开心一刻、程序员计算器、密码箱、习惯打卡、还款提醒、倒数纪念日、日记本、备忘录、日程提醒、端口映射、练打字等功能。前后端的数据传输就应用了RSA非对称加密和解密,有兴趣可体验一番!

    我已将代码上传,下载码是:D2BC8C3473

    下载码是啥?如何下载=》点击查看icon-default.png?t=M7J4https://www.luweidong.cn/details/88

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值