RSA非对称加密

什么是非对称加密

明文字符—>秘钥—>暗文字符—>秘钥(相同)—>明文字符:对称加密

明文字符—>秘钥1(公钥)—>暗文字符—>秘钥2(私钥)—>明文字符:非对称加密

公钥和私钥是通过同一个算法计算出来的,它们绑在一块,有关联。

用户情景:登录网站

  • 登录入口—》发送明文密码—-》服务器:容易被人拦截
  • 登录入口—-发起异步请求—-服务器(生成公钥和秘钥)–把公钥发给客户端—客户端使用公钥加密—-发送暗文—》服务器(知道是哪个用户)—使用私钥解密—》得到明文—判断密码是否正确

就算被人拦截,拿到的也是暗文,安全性很高。

RSA公钥加密算法是1977年由Ron Rivest、Adi Shamirh和LenAdleman在(美国麻省理工学院)开发的。RSA取名来自开发他们三者的名字。


生成公钥和私钥

封装一个RSA秘钥类:

public class RSASecretKey
{
    public string PublicKey { get; set; }
    public string PrivateKey { get; set; }
}Code language: JavaScript (javascript)

在main函数中生成私钥和公钥,它们是一对的:

static void Main(string[] args)
{
    var rSASecretKey = GenerateRSASecretKey(2048);
}

static RSASecretKey GenerateRSASecretKey(int keySize)
{
    var rsaKey = new RSASecretKey();
    using (var rsa = new RSACryptoServiceProvider(keySize))
    {
        rsaKey.PrivateKey = rsa.ToXmlString(true);
        rsaKey.PublicKey = rsa.ToXmlString(false);
    }
    return rsaKey;
}Code language: JavaScript (javascript)

建议使用2048以上的秘钥尺寸。


使用公钥加密

封装使用公钥的加密方法:

/// <summary>
/// 加密方法
/// </summary>
/// <param name="xmlPublicKey">公钥(XML格式)</param>
/// <param name="content">明文字符串</param>
/// <returns></returns>
static string RSAEncrypt(string xmlPublicKey, string content)
{
    string encryptedContent = string.Empty;
    using (var rsa = new RSACryptoServiceProvider())
    {
        rsa.FromXmlString(xmlPublicKey);
        byte[] encryptedData = rsa.Encrypt(Encoding.Default.GetBytes(content), false);
        encryptedContent = Convert.ToBase64String(encryptedData);
    }
    return encryptedContent;
}Code language: PHP (php)

在main函数中调用:

string secret = RSAEncrypt(rSASecretKey.PublicKey, "北盟网校");Code language: JavaScript (javascript)

加密过程通常发生在客户端。


使用私钥解密

客户端把暗文传到服务器,服务器使用这对秘钥的私钥进行解密。私钥只有服务器端记录,要保密,不能泄露。

解密方法封装:

/// <summary>
/// 解密方法
/// </summary>
/// <param name="xmlPrivateKey">私钥xml格式</param>
/// <param name="content">解密的暗文(已经被加密过的字符串)</param>
/// <returns></returns>
static string RSADecrypt(string xmlPrivateKey, string content)
{
    string decryptedContent = string.Empty;
    using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
    {
        rsa.FromXmlString(xmlPrivateKey);
        byte[] decryptedData = rsa.Decrypt(Convert.FromBase64String(content), false);
        decryptedContent = Encoding.GetEncoding("gb2312").GetString(decryptedData);
    }
    return decryptedContent;
}Code language: PHP (php)

在main函数中调用:

string mw = RSADecrypt(rSASecretKey.PrivateKey, secret);

完整过程代码

using System;
using System.Security.Cryptography;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var rSASecretKey = GenerateRSASecretKey(2048);
            string secret = RSAEncrypt(rSASecretKey.PublicKey, "北盟网校");
            string mw = RSADecrypt(rSASecretKey.PrivateKey, secret);
        }

        static RSASecretKey GenerateRSASecretKey(int keySize)
        {
            var rsaKey = new RSASecretKey();
            using (var rsa = new RSACryptoServiceProvider(keySize))
            {
                rsaKey.PrivateKey = rsa.ToXmlString(true);
                rsaKey.PublicKey = rsa.ToXmlString(false);
            }
            return rsaKey;
        }

        static string RSAEncrypt(string xmlPublicKey, string content)
        {
            string encryptedContent = string.Empty;
            using (var rsa = new RSACryptoServiceProvider())
            {
                rsa.FromXmlString(xmlPublicKey);
                byte[] encryptedData = rsa.Encrypt(Encoding.Default.GetBytes(content), false);
                encryptedContent = Convert.ToBase64String(encryptedData);
            }
            return encryptedContent;
        }

        static string RSADecrypt(string xmlPrivateKey, string content)
        {
            string decryptedContent = string.Empty;
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.FromXmlString(xmlPrivateKey);
                byte[] decryptedData = rsa.Decrypt(Convert.FromBase64String(content), false);
                decryptedContent = Encoding.GetEncoding("gb2312").GetString(decryptedData);
            }
            return decryptedContent;
        }
    }
}Code language: JavaScript (javascript)

密钥格式的转换

客户端和服务器端可能使用不同语言开发。比如服务器端是C#,客户端是java,不同语言的公钥和私钥格式不一样。

C#中RSA密钥格式是XML的,而其他语言如java生成的RSA密钥是普通的Base64字符串,所以需要进行格式转换。

安装 BouncyCastle 这个Nuget包。

转换类完整代码

using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using System;
using System.Security.Cryptography;

namespace ConsoleApp1
{
    public class RSAKeyConverter
    {
        /// <summary>
        /// xml private key -> base64 private key string
        /// </summary>
        public static string FromXmlPrivateKey(string xmlPrivateKey)
        {
            string result = string.Empty;
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.FromXmlString(xmlPrivateKey);
                RSAParameters param = rsa.ExportParameters(true);
                RsaPrivateCrtKeyParameters privateKeyParam = new RsaPrivateCrtKeyParameters(
                    new BigInteger(1, param.Modulus), new BigInteger(1, param.Exponent),
                    new BigInteger(1, param.D), new BigInteger(1, param.P),
                    new BigInteger(1, param.Q), new BigInteger(1, param.DP),
                    new BigInteger(1, param.DQ), new BigInteger(1, param.InverseQ));
                PrivateKeyInfo privateKey = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKeyParam);
                result = Convert.ToBase64String(privateKey.ToAsn1Object().GetEncoded());
            }
            return result;
        }

        /// <summary>
        /// xml public key -> base64 public key string
        /// </summary>
        public static string FromXmlPublicKey(string xmlPublicKey)
        {
            string result = string.Empty;
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.FromXmlString(xmlPublicKey);
                RSAParameters p = rsa.ExportParameters(false);
                RsaKeyParameters keyParams = new RsaKeyParameters(
                    false, new BigInteger(1, p.Modulus), new BigInteger(1, p.Exponent));
                SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyParams);
                result = Convert.ToBase64String(publicKeyInfo.ToAsn1Object().GetEncoded());
            }
            return result;
        }

        /// <summary>
        /// base64 private key string -> xml private key
        /// </summary>
        public static string ToXmlPrivateKey(string privateKey)
        {
            RsaPrivateCrtKeyParameters privateKeyParams =
                PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey)) as RsaPrivateCrtKeyParameters;
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                RSAParameters rsaParams = new RSAParameters()
                {
                    Modulus = privateKeyParams.Modulus.ToByteArrayUnsigned(),
                    Exponent = privateKeyParams.PublicExponent.ToByteArrayUnsigned(),
                    D = privateKeyParams.Exponent.ToByteArrayUnsigned(),
                    DP = privateKeyParams.DP.ToByteArrayUnsigned(),
                    DQ = privateKeyParams.DQ.ToByteArrayUnsigned(),
                    P = privateKeyParams.P.ToByteArrayUnsigned(),
                    Q = privateKeyParams.Q.ToByteArrayUnsigned(),
                    InverseQ = privateKeyParams.QInv.ToByteArrayUnsigned()
                };
                rsa.ImportParameters(rsaParams);
                return rsa.ToXmlString(true);
            }
        }

        /// <summary>
        /// base64 public key string -> xml public key
        /// </summary>
        public static string ToXmlPublicKey(string pubilcKey)
        {
            RsaKeyParameters p =
                PublicKeyFactory.CreateKey(Convert.FromBase64String(pubilcKey)) as RsaKeyParameters;
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                RSAParameters rsaParams = new RSAParameters
                {
                    Modulus = p.Modulus.ToByteArrayUnsigned(),
                    Exponent = p.Exponent.ToByteArrayUnsigned()
                };
                rsa.ImportParameters(rsaParams);
                return rsa.ToXmlString(false);
            }
        }
    }
}Code language: JavaScript (javascript)

补充说明

  • FromXmlPrivateKey() 把C#的XML格式私钥转成Base64字符串(PKCS#8格式),供java等语言使用
  • FromXmlPublicKey() 把C#的XML格式公钥转成Base64字符串(X.509格式)
  • ToXmlPrivateKey() 把Base64私钥字符串转回C#的XML格式
  • ToXmlPublicKey() 把Base64公钥字符串转回C#的XML格式
  • 转换依赖 BouncyCastle 库,通过NuGet安装 Portable.BouncyCastleBouncyCastle.Cryptography
  • RSA加密有长度限制,明文长度不能超过密钥长度减去填充开销(2048位密钥约可加密245字节),超长文本需要分段加密或结合对称加密使用

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注