RSA非對稱加密演算法實現:C#
RSA是1977年由羅納德·李維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)一起提出的。當時他們三人都在麻省理工學院工作。RSA就是他們三人姓氏開頭字母拼在一起組成的。
RSA解決了對稱加密的一個不足,比如AES演算法加密和解密時使用的是同一個祕鑰,因此這個祕鑰不能公開,因此對於需要公開祕鑰的場合,我們需要在加密和解密過程中使用不同的祕鑰,加密使用的公鑰可以公開,解密使用的私鑰要保密,這就是非對稱加密的好處。
常用的開發語言來實現RSA加密:
公鑰與私鑰
公鑰與私鑰是成對的,一般的,我們認為的是公鑰加密、私鑰解密、私鑰簽名、公鑰驗證,有人說成私鑰加密,公鑰解密時不對的。
公鑰與私鑰的生成有多種方式,可以通過程式生成(下文具體實現),可以通過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}"); } } }