快速非对称文件加密
非对称加密通常被认为优于对称加密,用于将消息传输到其他方。这主要是因为它消除了与交换共享密钥相关的许多风险,并确保拥有公钥的任何人都可以为预期收件人加密邮件,只有该收件人才能对其进行解密。不幸的是,非对称加密算法的主要缺点是它们比它们的对称同类慢得多。因此,文件的非对称加密,尤其是大文件,通常是计算密集型过程。
为了提供安全性和性能,可以采用混合方法。这需要加密随机生成用于对称加密的密钥和初始化向量。然后使用非对称算法对这些值进行加密,并将其写入输出文件,然后用于对称地加密源数据并将其附加到输出。
这种方法提供了高度的性能和安全性,因为数据使用对称算法(快速)加密,密钥和 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");
}