RSA非对称加密算法实现:C#
阅读原文时间:2023年07月08日阅读:1

  RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。

  RSA解决了对称加密的一个不足,比如AES算法加密和解密时使用的是同一个秘钥,因此这个秘钥不能公开,因此对于需要公开秘钥的场合,我们需要在加密和解密过程中使用不同的秘钥,加密使用的公钥可以公开,解密使用的私钥要保密,这就是非对称加密的好处。 

  常用的开发语言来实现RSA加密:

  RSA非对称加密算法实现:Java

  RSA非对称加密算法实现:C#

  RSA非对称加密算法实现:Golang

  RSA非对称加密算法实现:Python

  公钥与私钥

  公钥与私钥是成对的,一般的,我们认为的是公钥加密、私钥解密、私钥签名、公钥验证,有人说成私钥加密,公钥解密时不对的。

  公钥与私钥的生成有多种方式,可以通过程序生成(下文具体实现),可以通过openssl工具:  

# 生成一个私钥,推荐使用1024位的秘钥,秘钥以pem格式保存到-out参数指定的文件中,采用PKCS1格式  
openssl genrsa -out rsa.pem 1024  
# 生成与私钥对应的公钥,生成的是Subject Public Key,一般配合PKCS8格式私钥使用  
openssl rsa -in rsa.pem -pubout -out rsa.pub  

  RSA生成公钥与私钥一般有两种格式:PKCS1和PKCS8,上面的命令生成的秘钥是PKCS1格式的,而公钥是Subject Public Key,一般配合PKCS8格式私钥使用,所以就可能会涉及到PKCS1和PKCS8之间的转换:

# PKCS1格式私钥转换为PKCS8格式私钥,私钥直接输出到-out参数指定的文件中  
openssl pkcs8 -topk8 -inform PEM -in rsa.pem -outform pem -nocrypt -out rsa\_pkcs8.pem  
# PKCS8格式私钥转换为PKCS1格式私钥,私钥直接输出到-out参数指定的文件中  
openssl rsa -in rsa\_pkcs8.pem -out rsa\_pkcs1.pem

# PKCS1格式公钥转换为PKCS8格式公钥,转换后的内容直接输出  
openssl rsa -pubin -in rsa.pub -RSAPublicKey\_out  
# PKCS8格式公钥转换为PKCS1格式公钥,转换后的内容直接输出  
openssl rsa -RSAPublicKey\_in -pubout -in rsa.pub

  现实中,我们往往从pem、crt、pfx文件获取公私和私钥,crt、pfx的制作可以参考:简单的制作ssl证书,并在nginx和IIS中使用,或者使用现成的:https://pan.baidu.com/s/1MJ5YmuZiLBnf-DfNR_6D7A (提取码:c6tj),密码都是:123456

  C#实现

  为了方便读取pem、crt、pfx文件中的公私和私钥,这里我使用了第三方的包:Portable.BouncyCastle,可以使用NuGet安装:Install-Package Portable.BouncyCastle

  接着,这里我封装了一个RsaHelper辅助类来实现各种RSA加密的过程:  

  

using Org.BouncyCastle.Utilities.IO.Pem;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace ConsoleApp1
{
public class RsaHelper
{

    #region key  
    /// <summary>  
    /// 将秘钥保存到Pem文件  
    /// </summary>  
    /// <param name="isPrivateKey"></param>  
    /// <param name="buffer"></param>  
    /// <param name="pemFileName"></param>  
    /// <returns></returns>  
    public static void WriteToPem(byte\[\] buffer, bool isPrivateKey, string pemFileName)  
    {  
        PemObject pemObject = new PemObject(isPrivateKey ? "RSA PRIVATE KEY" : "RSA PUBLIC KEY", buffer);  
        if (File.Exists(pemFileName))  
        {  
            File.Delete(pemFileName);  
        }  
        using (var fileStream = new FileStream(pemFileName, FileMode.OpenOrCreate, FileAccess.Write))  
        {  
            using (var sw = new StreamWriter(fileStream))  
            {  
                var writer = new PemWriter(sw);  
                writer.WriteObject(pemObject);  
                sw.Flush();  
            }  
        }  
    }  
    /// <summary>  
    /// 从Pem文件中读取秘钥  
    /// </summary>  
    /// <param name="pemFileName"></param>  
    /// <returns></returns>  
    public static byte\[\] ReadFromPem(string pemFileName)  
    {  
        using (var fileStream = new FileStream(pemFileName, FileMode.Open, FileAccess.Read))  
        {  
            using (var sw = new StreamReader(fileStream))  
            {  
                var writer = new PemReader(sw);  
                return writer.ReadPemObject().Content;  
            }  
        }  
    }

    /// <summary>  
    /// 从xml中读取秘钥  
    /// </summary>  
    /// <param name="xml"></param>  
    /// <param name="isPrivateKey"></param>  
    /// <param name="usePkcs8"></param>  
    /// <returns></returns>  
    public static byte\[\] ReadFromXml(string xml, bool isPrivateKey, bool usePkcs8)  
    {  
        using (var rsa = new RSACryptoServiceProvider())  
        {  
            rsa.FromXmlString(xml);  
            if (isPrivateKey)  
            {  
                return usePkcs8 ? rsa.ExportPkcs8PrivateKey() : rsa.ExportRSAPrivateKey();  
            }  
            return usePkcs8 ? rsa.ExportSubjectPublicKeyInfo() : rsa.ExportRSAPublicKey();  
        }  
    }  
    /// <summary>  
    /// 将秘钥保存到xml中  
    /// </summary>  
    /// <param name="buffer"></param>  
    /// <param name="isPrivateKey"></param>  
    /// <param name="usePkcs8"></param>  
    /// <returns></returns>  
    public static string WriteToXml(byte\[\] buffer, bool isPrivateKey, bool usePkcs8)  
    {  
        using (var rsa = CreateRSACryptoServiceProvider(buffer, isPrivateKey, usePkcs8))  
        {  
            return rsa.ToXmlString(isPrivateKey);  
        }  
    }

    /// <summary>  
    /// 获取RSA非对称加密的Key  
    /// </summary>  
    /// <param name="publicKey"></param>  
    /// <param name="privateKey"></param>  
    public static void GenerateRsaKey(bool usePKCS8, out byte\[\] publicKey, out byte\[\] privateKey)  
    {  
        using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())  
        {  
            rsa.KeySize = 1024;//1024位  
            if (usePKCS8)  
            {  
                //使用pkcs8填充方式导出  
                publicKey = rsa.ExportSubjectPublicKeyInfo();//公钥  
                privateKey = rsa.ExportPkcs8PrivateKey();//私钥  
            }  
            else  
            {  
                //使用pkcs1填充方式导出  
                publicKey = rsa.ExportRSAPublicKey();//公钥  
                privateKey = rsa.ExportRSAPrivateKey();//私钥  
            }  
        }  
    }  
    /// <summary>  
    /// 从Pfx文件获取RSA非对称加密的Key  
    /// </summary>  
    /// <param name="pfxFileName"></param>  
    /// <param name="publicKey"></param>  
    /// <param name="privateKey"></param>  
    public static void ReadFromPfx(string pfxFileName, string password, out byte\[\] publicKey, out byte\[\] privateKey)  
    {  
        X509Certificate2 x509Certificate2 = new X509Certificate2(pfxFileName, password, X509KeyStorageFlags.Exportable);  
        publicKey = x509Certificate2.GetRSAPublicKey().ExportRSAPublicKey();  
        privateKey = x509Certificate2.GetRSAPrivateKey().ExportRSAPrivateKey();  
    }  
    /// <summary>  
    /// 从Crt文件中读取公钥  
    /// </summary>  
    /// <param name="crtFileName"></param>  
    /// <param name="password"></param>  
    /// <returns></returns>  
    public static byte\[\] ReadPublicKeyFromCrt(string crtFileName, string password)  
    {  
        X509Certificate2 x509Certificate2 = new X509Certificate2(crtFileName, password, X509KeyStorageFlags.Exportable);  
        var publicKey = x509Certificate2.GetRSAPublicKey().ExportRSAPublicKey();  
        return publicKey;  
    }  
    #endregion

    #region Pkcs1 and Pkcs8

    /// <summary>  
    /// Pkcs1转Pkcs8  
    /// </summary>  
    /// <param name="isPrivateKey"></param>  
    /// <param name="buffer"></param>  
    /// <returns></returns>  
    public static byte\[\] Pkcs1ToPkcs8(bool isPrivateKey, byte\[\] buffer)  
    {  
        using (var rsa = new RSACryptoServiceProvider())  
        {  
            if (isPrivateKey)  
            {  
                rsa.ImportRSAPrivateKey(buffer, out \_);  
                return rsa.ExportPkcs8PrivateKey();  
            }  
            else  
            {  
                rsa.ImportRSAPublicKey(buffer, out \_);  
                return rsa.ExportSubjectPublicKeyInfo();  
            }  
        }  
    }  
    /// <summary>  
    /// Pkcs8转Pkcs1  
    /// </summary>  
    /// <param name="isPrivateKey"></param>  
    /// <param name="buffer"></param>  
    /// <returns></returns>  
    public static byte\[\] Pkcs8ToPkcs1(bool isPrivateKey, byte\[\] buffer)  
    {  
        using (var rsa = new RSACryptoServiceProvider())  
        {  
            if (isPrivateKey)  
            {  
                rsa.ImportPkcs8PrivateKey(buffer, out \_);  
                return rsa.ExportRSAPrivateKey();  
            }  
            else  
            {  
                rsa.ImportSubjectPublicKeyInfo(buffer, out \_);  
                return rsa.ExportRSAPublicKey();  
            }  
        }  
    }

    #endregion

    #region RSA

    /// <summary>  
    /// 获取一个RSACryptoServiceProvider  
    /// </summary>  
    /// <param name="isPrivateKey"></param>  
    /// <param name="buffer"></param>  
    /// <param name="usePkcs8"></param>  
    /// <returns></returns>  
    public static RSACryptoServiceProvider CreateRSACryptoServiceProvider(byte\[\] buffer, bool isPrivateKey, bool usePkcs8 = false)  
    {  
        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();  
        if (isPrivateKey)  
        {  
            if (usePkcs8)  
                rsa.ImportPkcs8PrivateKey(buffer, out \_);  
            else  
                rsa.ImportRSAPrivateKey(buffer, out \_);  
        }  
        else  
        {  
            if (usePkcs8)  
                rsa.ImportSubjectPublicKeyInfo(buffer, out \_);  
            else  
                rsa.ImportRSAPublicKey(buffer, out \_);  
        }  
        return rsa;  
    }

    /// <summary>  
    /// RSA公钥加密  
    /// </summary>  
    /// <param name="value">待加密的明文</param>  
    /// <param name="publicKey">公钥</param>  
    /// <param name="usePkcs8">是否使用pkcs8填充</param>  
    /// <returns></returns>  
    public static string RsaEncrypt(string value, byte\[\] publicKey, bool usePkcs8 = false)  
    {  
        if (string.IsNullOrEmpty(value)) return value;

        using (RSACryptoServiceProvider rsa = CreateRSACryptoServiceProvider(publicKey, false, usePkcs8))  
        {  
            var buffer = Encoding.UTF8.GetBytes(value);  
            buffer = rsa.Encrypt(buffer, false);

            //使用hex格式输出数据  
            StringBuilder result = new StringBuilder();  
            foreach (byte b in buffer)  
            {  
                result.AppendFormat("{0:x2}", b);  
            }  
            return result.ToString();  
            //或者使用下面的输出  
            //return BitConverter.ToString(buffer).Replace("-", "").ToLower();  
        }  
    }  
    /// <summary>  
    /// RSA私钥解密  
    /// </summary>  
    /// <param name="value">密文</param>  
    /// <param name="privateKey">私钥</param>  
    /// <param name="usePkcs8">是否使用pkcs8填充</param>  
    /// <returns></returns>  
    public static string RsaDecrypt(string value, byte\[\] privateKey, bool usePkcs8 = false)  
    {  
        if (string.IsNullOrEmpty(value)) return value;

        using (RSACryptoServiceProvider rsa = CreateRSACryptoServiceProvider(privateKey, true, usePkcs8))  
        {  
            //转换hex格式数据为byte数组  
            var buffer = new byte\[value.Length / 2\];  
            for (var i = 0; i < buffer.Length; i++)  
            {  
                buffer\[i\] = (byte)Convert.ToInt32(value.Substring(i \* 2, 2), 16);  
            }  
            buffer = rsa.Decrypt(buffer, false);  
            return Encoding.UTF8.GetString(buffer);  
        }  
    }  
    /// <summary>  
    /// RSA私钥生成签名  
    /// </summary>  
    /// <param name="value">原始值</param>  
    /// <param name="publicKey">公钥</param>  
    /// <param name="halg">签名hash算法:SHA,SHA1,MD5,SHA256,SHA384,SHA512</param>  
    /// <param name="usePkcs8">是否使用pkcs8填充</param>  
    /// <returns></returns>  
    public static string Sign(string value, byte\[\] privateKey, string halg = "MD5", bool usePkcs8 = false)  
    {  
        if (string.IsNullOrEmpty(value)) return value;

        using (RSACryptoServiceProvider rsa = CreateRSACryptoServiceProvider(privateKey, true, usePkcs8))  
        {  
            byte\[\] buffer = Encoding.UTF8.GetBytes(value);  
            buffer = rsa.SignData(buffer, HashAlgorithm.Create(halg));

            //使用hex格式输出数据  
            StringBuilder result = new StringBuilder();  
            foreach (byte b in buffer)  
            {  
                result.AppendFormat("{0:x2}", b);  
            }  
            return result.ToString();  
            //或者使用下面的输出  
            //return BitConverter.ToString(buffer).Replace("-", "").ToLower();  
        }  
    }  
    /// <summary>  
    /// RSA公钥验证签名  
    /// </summary>  
    /// <param name="value">原始值</param>  
    /// <param name="publicKey">公钥</param>  
    /// <param name="signature">签名</param>  
    /// <param name="halg">签名hash算法:SHA,SHA1,MD5,SHA256,SHA384,SHA512</param>  
    /// <param name="usePkcs8">是否使用pkcs8填充</param>  
    /// <returns></returns>  
    public static bool Verify(string value, byte\[\] publicKey, string signature, string halg = "MD5", bool usePkcs8 = false)  
    {  
        if (string.IsNullOrEmpty(value)) return false;

        using (RSACryptoServiceProvider rsa = CreateRSACryptoServiceProvider(publicKey, false, usePkcs8))  
        {  
            //转换hex格式数据为byte数组  
            var buffer = new byte\[signature.Length / 2\];  
            for (var i = 0; i < buffer.Length; i++)  
            {  
                buffer\[i\] = (byte)Convert.ToInt32(signature.Substring(i \* 2, 2), 16);  
            }  
            return rsa.VerifyData(Encoding.UTF8.GetBytes(value), HashAlgorithm.Create(halg), buffer);  
        }  
    }  
    #endregion  
}  

}

RsaHelper

  可以使用生成RSA的公私秘钥:  

//通过程序生成  
RsaHelper.GenerateRsaKey(usePKCS8, out publicKey, out privateKey);

  生成秘钥后,需要保存,一般保存到pem文件中:  

//保存到Pem文件,filePath是文件目录  
RsaHelper.WriteToPem(publicKey, false, Path.Combine(filePath, "rsa.pub"));  
RsaHelper.WriteToPem(privateKey, true, Path.Combine(filePath, "rsa.pem"));

  还以将公钥和私钥输出为xml格式:  

//保存到xml中  
string publicKeyXml = RsaHelper.WriteToXml(publicKey, false, usePKCS8);  
string privateKeyXml = RsaHelper.WriteToXml(privateKey, true, usePKCS8);

  保存到pem文件和xml中后,也可以从中读取:  

//从Pem文件获取,filePath是文件目录  
publicKey = RsaHelper.ReadFromPem(Path.Combine(filePath, "rsa.pub"));  
privateKey = RsaHelper.ReadFromPem(Path.Combine(filePath, "rsa.pem"));

//从xml中读取  
publicKey = RsaHelper.ReadFromXml(publicKeyXml, false, usePKCS8);  
privateKey = RsaHelper.ReadFromXml(privateKeyXml, true, usePKCS8);

  还可以从crt证书中读取公钥,而crt文件不包含私钥,因此需要单独获取私钥:

//从crt文件读取,filePath是文件目录  
publicKey = RsaHelper.ReadPublicKeyFromCrt(Path.Combine(filePath, "demo.crt"), "");  
privateKey = RsaHelper.ReadFromPem(Path.Combine(filePath, "demo.key"));

  pfx文件中包含了公钥和私钥,可以很方便就读取到:  

//从demo.pfx文件读取(demo.pfx采用的是pkcs1),filePath是文件目录  
RsaHelper.ReadFromPfx(Path.Combine(filePath, "demo.pfx"), "123456", out publicKey, out privateKey);

  有时候我们还可能需要进行秘钥的转换:  

// Pkcs8格式公钥转换为Pkcs1格式公钥  
publicKey = RsaHelper.Pkcs8ToPkcs1(false, publicKey);  
// Pkcs8格式私钥转换为Pkcs1格式私钥  
privateKey = RsaHelper.Pkcs8ToPkcs1(true, privateKey);  
// Pkcs1格式公钥转换为Pkcs8格式公钥  
publicKey = RsaHelper.Pkcs1ToPkcs8(false, publicKey);  
// Pkcs1格式私钥转换为Pkcs8格式私钥  
privateKey = RsaHelper.Pkcs1ToPkcs8(true, privateKey);

  有了公钥和私钥,接下就就能实现加密、解密、签名、验证签名等操作了:  

string encryptText = RsaHelper.RsaEncrypt(text, publicKey, usePKCS8);  
Console.WriteLine($"【{text}】经过【RSA】加密后:{encryptText}");

string decryptText = RsaHelper.RsaDecrypt(encryptText, privateKey, usePKCS8);  
Console.WriteLine($"【{encryptText}】经过【RSA】解密后:{decryptText}");

string signature = RsaHelper.Sign(text, privateKey, "MD5", usePKCS8);  
Console.WriteLine($"【{text}】经过【RSA】签名后:{signature}");

bool result = RsaHelper.Verify(text, publicKey, signature, "MD5", usePKCS8);  
Console.WriteLine($"【{text}】的签名【{signature}】经过【RSA】验证后结果是:{result}");

  完整的demo代码:  

using System;  
using System.IO;

namespace ConsoleApp1  
{  
    class Program  
    {  
        static void Main(string\[\] args)  
        {  
            string text = "上山打老虎";  
            bool usePKCS8 = true;// usePKCS8=true表示是否成PKCS8格式的公私秘钥,否则乘车PKCS1格式的公私秘钥  
            string filePath = Directory.GetCurrentDirectory();  
            Console.WriteLine($"文件路径:{filePath}");// 存放pem,crt,pfx等文件的目录  
            byte\[\] publicKey, privateKey;// 公钥和私钥

            //通过程序生成  
            RsaHelper.GenerateRsaKey(usePKCS8, out publicKey, out privateKey);  
            //从Pem文件获取,filePath是文件目录  
            //publicKey = RsaHelper.ReadFromPem(Path.Combine(filePath, "rsa.pub"));  
            //privateKey = RsaHelper.ReadFromPem(Path.Combine(filePath, "rsa.pem"));

            //从demo.pfx文件读取(demo.pfx采用的是pkcs1),filePath是文件目录  
            //RsaHelper.ReadFromPfx(Path.Combine(filePath, "demo.pfx"), "123456", out publicKey, out privateKey);  
            //从crt文件读取,filePath是文件目录  
            //publicKey = RsaHelper.ReadPublicKeyFromCrt(Path.Combine(filePath, "demo.crt"), "");  
            //privateKey = RsaHelper.ReadFromPem(Path.Combine(filePath, "demo.key"));

            //保存到Pem文件,filePath是文件目录  
            RsaHelper.WriteToPem(publicKey, false, Path.Combine(filePath, "rsa.pub"));  
            RsaHelper.WriteToPem(privateKey, true, Path.Combine(filePath, "rsa.pem"));

            //保存到xml中  
            string publicKeyXml = RsaHelper.WriteToXml(publicKey, false, usePKCS8);  
            string privateKeyXml = RsaHelper.WriteToXml(privateKey, true, usePKCS8);

            //从xml中读取  
            publicKey = RsaHelper.ReadFromXml(publicKeyXml, false, usePKCS8);  
            privateKey = RsaHelper.ReadFromXml(privateKeyXml, true, usePKCS8);

            // Pkcs8格式公钥转换为Pkcs1格式公钥  
            publicKey = RsaHelper.Pkcs8ToPkcs1(false, publicKey);  
            // Pkcs8格式私钥转换为Pkcs1格式私钥  
            privateKey = RsaHelper.Pkcs8ToPkcs1(true, privateKey);  
            // Pkcs1格式公钥转换为Pkcs8格式公钥  
            publicKey = RsaHelper.Pkcs1ToPkcs8(false, publicKey);  
            // Pkcs1格式私钥转换为Pkcs8格式私钥  
            privateKey = RsaHelper.Pkcs1ToPkcs8(true, privateKey);

            string encryptText = RsaHelper.RsaEncrypt(text, publicKey, usePKCS8);  
            Console.WriteLine($"【{text}】经过【RSA】加密后:{encryptText}");

            string decryptText = RsaHelper.RsaDecrypt(encryptText, privateKey, usePKCS8);  
            Console.WriteLine($"【{encryptText}】经过【RSA】解密后:{decryptText}");

            string signature = RsaHelper.Sign(text, privateKey, "MD5", usePKCS8);  
            Console.WriteLine($"【{text}】经过【RSA】签名后:{signature}");

            bool result = RsaHelper.Verify(text, publicKey, signature, "MD5", usePKCS8);  
            Console.WriteLine($"【{text}】的签名【{signature}】经过【RSA】验证后结果是:{result}");  
        }  
    }  
}