首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# RSA 非對稱加密實戰:檔案加密、金鑰管理與大資料分塊處理

     編輯:關於C語言

前言

RSA 為非對稱加密標準,適用於金鑰交換、數位簽章、小量資料加密。受限於金鑰長度(如 2048 bits),單次加密資料上限約 245 位元組(OAEP padding)。本文示範完整的檔案加密流程:金鑰生成/匯出/匯入、大檔案分塊加密、資源回收、以及常見坑點與效能優化。

核心類別:RSACryptoServiceProvider


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;

1. 金鑰生成與 XML 匯出/匯入


    /// 
    /// 產生新金鑰對並匯出 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;
    }

2. 字串/小資料加密解密


    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);
    }

3. 大檔案分塊加密(核心邏輯)


    /// 
    /// 加密檔案:分塊讀取、加密、寫入。輸出格式:[塊長度(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);
            }
        }
    }

4. 實務架構:混合加密(RSA + AES)

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);
            }
        }
    }
}

5. 效能對比與選型建議

方案100MB 檔案耗時CPU 佔用適用場景
純 RSA 分塊~45 分鐘極高極小檔案 (<1KB)、金鑰交換
RSA + AES 混合~1.2 秒低任意大小檔案、生產環境標準

6. 安全性檢查清單

  • ✅ 使用 RSAEncryptionPadding.OaepSHA256(非 PKCS#1 v1.5,防填充神谕攻擊)。
  • ✅ 金鑰長度 ≥ 2048 bits(建議 3072/4096)。
  • ✅ 私鑰加密儲存(DPAPI ProtectedData、PFX、HSM、Azure Key Vault)。
  • ✅ 亂數來源:RSA.Create() / Aes.Create() 使用系統 CSPRNG。
  • ✅ 完整性驗證:生產環境建議加入 HMAC-SHA256 或改用 AES-GCM(.NET 6+ AesGcm)。
  • ✅ 記憶體清理:RSA、Aes 實作 IDisposable,務必 using。

7. 跨平台注意事項

  • RSACryptoServiceProvider 僅限 Windows(依賴 CAPI/CNG)。
  • .NET Core / .NET 5+ 請使用 RSA.Create(),自動選擇平台原生實作(Windows CNG、Linux OpenSSL、macOS Secure Transport)。
  • XML 格式金鑰跨平台相容;若需 PEM/DER 格式,可用 rsa.ExportSubjectPublicKeyInfoPem() / ExportPkcs8PrivateKeyPem() (.NET 5+)。

總結

RSA 非對稱加密是現代密碼基礎設施的基石。關鍵要訣:

  1. 不要直接用 RSA 加密大檔案——一定要用混合加密 (RSA-AES)。
  2. 金鑰管理比演算法更重要——私鑰保護、輪換、撤銷機制才是安全核心。
  3. 拥抱新 API——RSA.Create()、AesGcm、ReadExactly 讓程式碼更安全、跨平台、可維護。

掌握上述模式,即可在 C# 中建構符合產業標準的加密儲存、安全傳輸、授權驗證等核心功能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved