RSA 為非對稱加密標準,適用於金鑰交換、數位簽章、小量資料加密。受限於金鑰長度(如 2048 bits),單次加密資料上限約 245 位元組(OAEP padding)。本文示範完整的檔案加密流程:金鑰生成/匯出/匯入、大檔案分塊加密、資源回收、以及常見坑點與效能優化。
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public static class RsaHelper
{
// 金鑰大小:2048 位元組為現代最低建議
private const int KeySize = 2048;
// OAEP padding 下最大加密塊大小 = (KeySize/8) - 42 (2048->214 bytes)
private static int MaxEncryptBlockSize => KeySize / 8 - 42;
// 解密塊大小 = KeySize/8 (256 bytes)
private static int DecryptBlockSize => KeySize / 8;
///
/// 產生新金鑰對並匯出 XML 字串
///
public static (string PublicKeyXml, string PrivateKeyXml) GenerateKeys()
{
using (var rsa = new RSACryptoServiceProvider(KeySize))
{
return (rsa.ToXmlString(false), rsa.ToXmlString(true));
}
}
///
/// 將金鑰寫入檔案
///
public static void SaveKeys(string publicPath, string privatePath,
string publicXml, string privateXml)
{
File.WriteAllText(publicPath, publicXml, Encoding.UTF8);
File.WriteAllText(privatePath, privateXml, Encoding.UTF8);
// 實務上私鑰應加密儲存(如 DPAPI、PFX、HSM)
}
///
/// 從檔案載入公鑰
///
public static RSA LoadPublicKey(string path)
{
var rsa = RSA.Create();
rsa.FromXmlString(File.ReadAllText(path, Encoding.UTF8));
return rsa;
}
///
/// 從檔案載入私鑰
///
public static RSA LoadPrivateKey(string path)
{
var rsa = RSA.Create();
rsa.FromXmlString(File.ReadAllText(path, Encoding.UTF8));
return rsa;
}
public static byte[] EncryptString(RSA rsa, string plainText, Encoding encoding = null)
{
encoding ??= Encoding.UTF8;
byte[] data = encoding.GetBytes(plainText);
return rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
}
public static string DecryptString(RSA rsa, byte[] cipherText, Encoding encoding = null)
{
encoding ??= Encoding.UTF8;
byte[] plain = rsa.Decrypt(cipherText, RSAEncryptionPadding.OaepSHA256);
return encoding.GetString(plain);
}
///
/// 加密檔案:分塊讀取、加密、寫入。輸出格式:[塊長度(4 bytes)][密文]...
///
public static void EncryptFile(RSA rsa, string inputPath, string outputPath)
{
using (var fsIn = new FileStream(inputPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var fsOut = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
var buffer = new byte[MaxEncryptBlockSize];
int bytesRead;
while ((bytesRead = fsIn.Read(buffer, 0, buffer.Length)) > 0)
{
byte[] block = (bytesRead == buffer.Length) ? buffer : buffer[..bytesRead]; // C# 8 range
byte[] encrypted = rsa.Encrypt(block, RSAEncryptionPadding.OaepSHA256);
// 寫入塊長度 (Big-Endian, 4 bytes) + 密文
fsOut.Write(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(encrypted.Length)));
fsOut.Write(encrypted);
}
}
}
///
/// 解密檔案:讀取塊長度、讀取密文、解密、寫入
///
public static void DecryptFile(RSA rsa, string inputPath, string outputPath)
{
using (var fsIn = new FileStream(inputPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var fsOut = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
var lenBuffer = new byte[4];
while (true)
{
// 讀取塊長度
int readLen = fsIn.Read(lenBuffer, 0, 4);
if (readLen == 0) break; // EOF
if (readLen != 4) throw new EndOfStreamException("長度標頭不完整");
int blockLen = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(lenBuffer, 0));
if (blockLen <= 0 || blockLen > DecryptBlockSize)
throw new CryptographicException($"無效塊長度:{blockLen}");
// 讀取密文
var cipherBlock = new byte[blockLen];
int totalRead = 0;
while (totalRead < blockLen)
{
int r = fsIn.Read(cipherBlock, totalRead, blockLen - totalRead);
if (r == 0) throw new EndOfStreamException("密文資料不完整");
totalRead += r;
}
// 解密並寫入
byte[] plainBlock = rsa.Decrypt(cipherBlock, RSAEncryptionPadding.OaepSHA256);
fsOut.Write(plainBlock);
}
}
}
RSA 加密大檔案極慢(非對稱運算昂貴)。工業標準做法:
1. 隨機產生 AES-256 金鑰 + IV
2. 用 AES 加密檔案(極快、串流式)
3. 用 RSA 加密 AES 金鑰(僅 32 bytes)
4. 輸出:[RSA加密的AES金鑰長度][RSA加密的AES金鑰][IV][AES密文]
public static void EncryptFileHybrid(string inputPath, string outputPath, string publicKeyPath)
{
using (var rsa = LoadPublicKey(publicKeyPath))
using (var aes = Aes.Create())
{
aes.KeySize = 256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.GenerateKey();
aes.GenerateIV();
// RSA 加密 AES Key
byte[] encryptedKey = rsa.Encrypt(aes.Key, RSAEncryptionPadding.OaepSHA256);
using (var fsOut = new FileStream(outputPath, FileMode.Create))
{
// 寫入:Key長度(4) + Key + IV(16) + 密文
fsOut.Write(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(encryptedKey.Length)));
fsOut.Write(encryptedKey);
fsOut.Write(aes.IV);
using (var fsIn = new FileStream(inputPath, FileMode.Open))
using (var cs = new CryptoStream(fsOut, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
fsIn.CopyTo(cs); // 串流式加密,記憶體恆定
}
}
}
}
public static void DecryptFileHybrid(string inputPath, string outputPath, string privateKeyPath)
{
using (var rsa = LoadPrivateKey(privateKeyPath))
using (var aes = Aes.Create())
{
aes.KeySize = 256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (var fsIn = new FileStream(inputPath, FileMode.Open))
{
// 讀取 Key 長度
var lenBuf = new byte[4];
fsIn.ReadExactly(lenBuf);
int keyLen = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(lenBuf, 0));
// 讀取加密 Key
var encKey = new byte[keyLen];
fsIn.ReadExactly(encKey);
aes.Key = rsa.Decrypt(encKey, RSAEncryptionPadding.OaepSHA256);
// 讀取 IV
aes.IV = new byte[16];
fsIn.ReadExactly(aes.IV);
// 解密串流
using (var fsOut = new FileStream(outputPath, FileMode.Create))
using (var cs = new CryptoStream(fsIn, aes.CreateDecryptor(), CryptoStreamMode.Read))
{
cs.CopyTo(fsOut);
}
}
}
}
| 方案 | 100MB 檔案耗時 | CPU 佔用 | 適用場景 |
|---|---|---|---|
| 純 RSA 分塊 | ~45 分鐘 | 極高 | 極小檔案 (<1KB)、金鑰交換 |
| RSA + AES 混合 | ~1.2 秒 | 低 | 任意大小檔案、生產環境標準 |
RSAEncryptionPadding.OaepSHA256(非 PKCS#1 v1.5,防填充神谕攻擊)。ProtectedData、PFX、HSM、Azure Key Vault)。RSA.Create() / Aes.Create() 使用系統 CSPRNG。AesGcm)。RSA、Aes 實作 IDisposable,務必 using。RSACryptoServiceProvider 僅限 Windows(依賴 CAPI/CNG)。RSA.Create(),自動選擇平台原生實作(Windows CNG、Linux OpenSSL、macOS Secure Transport)。rsa.ExportSubjectPublicKeyInfoPem() / ExportPkcs8PrivateKeyPem() (.NET 5+)。RSA 非對稱加密是現代密碼基礎設施的基石。關鍵要訣:
RSA.Create()、AesGcm、ReadExactly 讓程式碼更安全、跨平台、可維護。掌握上述模式,即可在 C# 中建構符合產業標準的加密儲存、安全傳輸、授權驗證等核心功能。