快速非對稱檔案加密
非對稱加密通常被認為優於對稱加密,用於將訊息傳輸到其他方。這主要是因為它消除了與交換共享金鑰相關的許多風險,並確保擁有公鑰的任何人都可以為預期收件人加密郵件,只有該收件人才能對其進行解密。不幸的是,非對稱加密演算法的主要缺點是它們比它們的對稱同類慢得多。因此,檔案的非對稱加密,尤其是大檔案,通常是計算密集型過程。
為了提供安全性和效能,可以採用混合方法。這需要加密隨機生成用於對稱加密的金鑰和初始化向量。然後使用非對稱演算法對這些值進行加密,並將其寫入輸出檔案,然後用於對稱地加密源資料並將其附加到輸出。
這種方法提供了高度的效能和安全性,因為資料使用對稱演算法(快速)加密,金鑰和 iv,隨機生成(安全)都由非對稱演算法(安全)加密。它還具有額外的優點,即在不同場合加密的相同有效載荷將具有非常不同的密文,因為每次都隨機生成對稱金鑰。
以下類演示了字串和位元組陣列的非對稱加密,以及混合檔案加密。
public static class AsymmetricProvider
{
#region Key Generation
public class KeyPair
{
public string PublicKey { get; set; }
public string PrivateKey { get; set; }
}
public static KeyPair GenerateNewKeyPair(int keySize = 4096)
{
// KeySize is measured in bits. 1024 is the default, 2048 is better, 4096 is more robust but takes a fair bit longer to generate.
using (var rsa = new RSACryptoServiceProvider(keySize))
{
return new KeyPair {PublicKey = rsa.ToXmlString(false), PrivateKey = rsa.ToXmlString(true)};
}
}
#endregion
#region Asymmetric Data Encryption and Decryption
public static byte[] EncryptData(byte[] data, string publicKey)
{
using (var asymmetricProvider = new RSACryptoServiceProvider())
{
asymmetricProvider.FromXmlString(publicKey);
return asymmetricProvider.Encrypt(data, true);
}
}
public static byte[] DecryptData(byte[] data, string publicKey)
{
using (var asymmetricProvider = new RSACryptoServiceProvider())
{
asymmetricProvider.FromXmlString(publicKey);
if (asymmetricProvider.PublicOnly)
throw new Exception("The key provided is a public key and does not contain the private key elements required for decryption");
return asymmetricProvider.Decrypt(data, true);
}
}
public static string EncryptString(string value, string publicKey)
{
return Convert.ToBase64String(EncryptData(Encoding.UTF8.GetBytes(value), publicKey));
}
public static string DecryptString(string value, string privateKey)
{
return Encoding.UTF8.GetString(EncryptData(Convert.FromBase64String(value), privateKey));
}
#endregion
#region Hybrid File Encryption and Decription
public static void EncryptFile(string inputFilePath, string outputFilePath, string publicKey)
{
using (var symmetricCypher = new AesManaged())
{
// Generate random key and IV for symmetric encryption
var key = new byte[symmetricCypher.KeySize / 8];
var iv = new byte[symmetricCypher.BlockSize / 8];
using (var rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(key);
rng.GetBytes(iv);
}
// Encrypt the symmetric key and IV
var buf = new byte[key.Length + iv.Length];
Array.Copy(key, buf, key.Length);
Array.Copy(iv, 0, buf, key.Length, iv.Length);
buf = EncryptData(buf, publicKey);
var bufLen = BitConverter.GetBytes(buf.Length);
// Symmetrically encrypt the data and write it to the file, along with the encrypted key and iv
using (var cypherKey = symmetricCypher.CreateEncryptor(key, iv))
using (var fsIn = new FileStream(inputFilePath, FileMode.Open))
using (var fsOut = new FileStream(outputFilePath, FileMode.Create))
using (var cs = new CryptoStream(fsOut, cypherKey, CryptoStreamMode.Write))
{
fsOut.Write(bufLen,0, bufLen.Length);
fsOut.Write(buf, 0, buf.Length);
fsIn.CopyTo(cs);
}
}
}
public static void DecryptFile(string inputFilePath, string outputFilePath, string privateKey)
{
using (var symmetricCypher = new AesManaged())
using (var fsIn = new FileStream(inputFilePath, FileMode.Open))
{
// Determine the length of the encrypted key and IV
var buf = new byte[sizeof(int)];
fsIn.Read(buf, 0, buf.Length);
var bufLen = BitConverter.ToInt32(buf, 0);
// Read the encrypted key and IV data from the file and decrypt using the asymmetric algorithm
buf = new byte[bufLen];
fsIn.Read(buf, 0, buf.Length);
buf = DecryptData(buf, privateKey);
var key = new byte[symmetricCypher.KeySize / 8];
var iv = new byte[symmetricCypher.BlockSize / 8];
Array.Copy(buf, key, key.Length);
Array.Copy(buf, key.Length, iv, 0, iv.Length);
// Decript the file data using the symmetric algorithm
using (var cypherKey = symmetricCypher.CreateDecryptor(key, iv))
using (var fsOut = new FileStream(outputFilePath, FileMode.Create))
using (var cs = new CryptoStream(fsOut, cypherKey, CryptoStreamMode.Write))
{
fsIn.CopyTo(cs);
}
}
}
#endregion
#region Key Storage
public static void WritePublicKey(string publicKeyFilePath, string publicKey)
{
File.WriteAllText(publicKeyFilePath, publicKey);
}
public static string ReadPublicKey(string publicKeyFilePath)
{
return File.ReadAllText(publicKeyFilePath);
}
private const string SymmetricSalt = "Stack_Overflow!"; // Change me!
public static string ReadPrivateKey(string privateKeyFilePath, string password)
{
var salt = Encoding.UTF8.GetBytes(SymmetricSalt);
var cypherText = File.ReadAllBytes(privateKeyFilePath);
using (var cypher = new AesManaged())
{
var pdb = new Rfc2898DeriveBytes(password, salt);
var key = pdb.GetBytes(cypher.KeySize / 8);
var iv = pdb.GetBytes(cypher.BlockSize / 8);
using (var decryptor = cypher.CreateDecryptor(key, iv))
using (var msDecrypt = new MemoryStream(cypherText))
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
using (var srDecrypt = new StreamReader(csDecrypt))
{
return srDecrypt.ReadToEnd();
}
}
}
public static void WritePrivateKey(string privateKeyFilePath, string privateKey, string password)
{
var salt = Encoding.UTF8.GetBytes(SymmetricSalt);
using (var cypher = new AesManaged())
{
var pdb = new Rfc2898DeriveBytes(password, salt);
var key = pdb.GetBytes(cypher.KeySize / 8);
var iv = pdb.GetBytes(cypher.BlockSize / 8);
using (var encryptor = cypher.CreateEncryptor(key, iv))
using (var fsEncrypt = new FileStream(privateKeyFilePath, FileMode.Create))
using (var csEncrypt = new CryptoStream(fsEncrypt, encryptor, CryptoStreamMode.Write))
using (var swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(privateKey);
}
}
}
#endregion
}
使用示例:
private static void HybridCryptoTest(string privateKeyPath, string privateKeyPassword, string inputPath)
{
// Setup the test
var publicKeyPath = Path.ChangeExtension(privateKeyPath, ".public");
var outputPath = Path.Combine(Path.ChangeExtension(inputPath, ".enc"));
var testPath = Path.Combine(Path.ChangeExtension(inputPath, ".test"));
if (!File.Exists(privateKeyPath))
{
var keys = AsymmetricProvider.GenerateNewKeyPair(2048);
AsymmetricProvider.WritePublicKey(publicKeyPath, keys.PublicKey);
AsymmetricProvider.WritePrivateKey(privateKeyPath, keys.PrivateKey, privateKeyPassword);
}
// Encrypt the file
var publicKey = AsymmetricProvider.ReadPublicKey(publicKeyPath);
AsymmetricProvider.EncryptFile(inputPath, outputPath, publicKey);
// Decrypt it again to compare against the source file
var privateKey = AsymmetricProvider.ReadPrivateKey(privateKeyPath, privateKeyPassword);
AsymmetricProvider.DecryptFile(outputPath, testPath, privateKey);
// Check that the two files match
var source = File.ReadAllBytes(inputPath);
var dest = File.ReadAllBytes(testPath);
if (source.Length != dest.Length)
throw new Exception("Length does not match");
if (source.Where((t, i) => t != dest[i]).Any())
throw new Exception("Data mismatch");
}