Chr*_*ris 6 .net c# security aes
我在加密大量字符串的应用程序中遇到一些性能问题。大多数 CPU 使用发生在我从名为 Encrypt() 的公共方法调用私有方法 getAes() 时:
public static class CryptKeeper
{
const int HASH_SIZE = 32; //SHA256
/// <summary>
/// Encrypts a string message. Includes integrity checking.
/// </summary>
public static string Encrypt(string messageToEncrypt, string sharedSecret, string salt)
{
// Prepare message with hash
var messageBytes = Encoding.UTF8.GetBytes(messageToEncrypt);
var hashedMessageBytes = new byte[HASH_SIZE + messageBytes.Length];
var hash = Utilities.GenerateSha256Hash(messageBytes, 0, messageBytes.Length);
Buffer.BlockCopy(hash, 0, hashedMessageBytes, 0, HASH_SIZE);
Buffer.BlockCopy(messageBytes, 0, hashedMessageBytes, HASH_SIZE, messageBytes.Length);
// Encrypt message
using (var aes = getAes(sharedSecret, Encoding.UTF8.GetBytes(salt)))
{
aes.GenerateIV();
using (var encryptor = aes.CreateEncryptor())
{
var encryptedBytes = encryptor.TransformFinalBlock(hashedMessageBytes, 0, hashedMessageBytes.Length);
// Add the initialization vector
var result = new byte[aes.IV.Length + encryptedBytes.Length];
Buffer.BlockCopy(aes.IV, 0, result, 0, aes.IV.Length);
Buffer.BlockCopy(encryptedBytes, 0, result, aes.IV.Length, encryptedBytes.Length);
return Convert.ToBase64String(result);
}
}
}
public static string Decrypt(string encryptedMessage, string sharedSecret, string salt)
{
if (encryptedMessage == null) return null;
using (var aes = getAes(sharedSecret, Encoding.UTF8.GetBytes(salt)))
{
var iv = new byte[aes.IV.Length];
Buffer.BlockCopy(Convert.FromBase64String(encryptedMessage), 0, iv, 0, iv.Length);
aes.IV = iv;
using (var decryptor = aes.CreateDecryptor())
{
var decryptedBytes = decryptor.TransformFinalBlock(Convert.FromBase64String(encryptedMessage), iv.Length, Convert.FromBase64String(encryptedMessage).Length - iv.Length);
// Check hash
var hash = Utilities.GenerateSha256Hash(decryptedBytes, HASH_SIZE, decryptedBytes.Length - HASH_SIZE);
var existingHash = new byte[HASH_SIZE];
Buffer.BlockCopy(decryptedBytes, 0, existingHash, 0, HASH_SIZE);
if (!existingHash.compareBytesTo(hash))
{
throw new CryptographicException("Message hash invalid.");
}
// Hash is valid, we're done
var res = new byte[decryptedBytes.Length - HASH_SIZE];
Buffer.BlockCopy(decryptedBytes, HASH_SIZE, res, 0, res.Length);
return Encoding.UTF8.GetString(res);
}
}
}
private static Aes getAes(string sharedSecret, byte[] salt)
{
var aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Key = new Rfc2898DeriveBytes(sharedSecret, salt, 129).GetBytes(aes.KeySize / 8);
return aes;
}
}
Run Code Online (Sandbox Code Playgroud)
我尝试通过缓存 AES 对象来提高性能,但我进入了不熟悉的领域:
public static class CryptKeeper
{
const int HASH_SIZE = 32; //SHA256
private static Aes aes;
/// <summary>
/// Encrypts a string message. Includes integrity checking.
/// </summary>
public static string Encrypt(string messageToEncrypt, string sharedSecret, string salt)
{
// unchanged
}
public static string Decrypt(string encryptedMessage, string sharedSecret, string salt)
{
// unchanged
}
private static Aes getAes(string sharedSecret, byte[] salt)
{
if (aes != null) return aes;
var aesNew = Aes.Create();
aesNew.Mode = CipherMode.CBC;
aesNew.Key = new Rfc2898DeriveBytes(sharedSecret, salt, 129).GetBytes(aesNew.KeySize / 8);
return aes = aesNew;
}
}
Run Code Online (Sandbox Code Playgroud)
我收到此错误:
安全句柄已在 System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success) 在 System.StubHelpers.StubHelpers.SafeHandleAddRef(SafeHandle pHandle, Boolean& success) 在 System.Security.Cryptography.CapiNative.UnsafeNativeMethods.CryptGenRandom(SafeCspHandle hProv, Int32 dwLen, Byte[] pbBuffer) 在 System.Security.Cryptography.AesCryptoServiceProvider.GenerateIV() 在 Obr.Lib.CryptKeeper.Encrypt(String messageToEncrypt, String SharedSecret, String salt) 在 ...CryptKeeper.cs:Obr 的第 28 行...HtmlRenderer.cs 中的 .Lib.HtmlRenderer.renderLawCitation(RenderContext renderContext, XElement xElement):第 1472 行
我知道 Encrypt() 中的 using() 语句将处理 AES,这会导致它损坏。我不想进一步排除故障,除非我知道重复使用是安全的。如果重复使用是安全的,那么最好的方法是什么?
更新:我通过保持 AES 对象更长时间来解决性能问题。我删除了 static 关键字,并使该类成为一次性的。现在的样子是这样的:
public class CryptKeeper : IDisposable
{
const int HASH_SIZE = 32; //SHA256
private readonly Aes aes;
public CryptKeeper(string sharedSecret, string salt)
{
aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Key = new Rfc2898DeriveBytes(sharedSecret, Encoding.UTF8.GetBytes(salt), 129).GetBytes(aes.KeySize / 8);
}
/// <summary>
/// Encrypts a string message. Includes integrity checking.
/// </summary>
public string Encrypt(string messageToEncrypt)
{
// Prepare message with hash
var messageBytes = Encoding.UTF8.GetBytes(messageToEncrypt);
var hashedMessageBytes = new byte[HASH_SIZE + messageBytes.Length];
var hash = Utilities.GenerateSha256Hash(messageBytes, 0, messageBytes.Length);
Buffer.BlockCopy(hash, 0, hashedMessageBytes, 0, HASH_SIZE);
Buffer.BlockCopy(messageBytes, 0, hashedMessageBytes, HASH_SIZE, messageBytes.Length);
// Encrypt message
aes.GenerateIV();
using (var encryptor = aes.CreateEncryptor())
{
var encryptedBytes = encryptor.TransformFinalBlock(hashedMessageBytes, 0, hashedMessageBytes.Length);
// Add the initialization vector
var result = new byte[aes.IV.Length + encryptedBytes.Length];
Buffer.BlockCopy(aes.IV, 0, result, 0, aes.IV.Length);
Buffer.BlockCopy(encryptedBytes, 0, result, aes.IV.Length, encryptedBytes.Length);
return Convert.ToBase64String(result);
}
}
public string Decrypt(string encryptedMessage)
{
if (encryptedMessage == null) return null;
var iv = new byte[aes.IV.Length];
Buffer.BlockCopy(Convert.FromBase64String(encryptedMessage), 0, iv, 0, iv.Length);
aes.IV = iv;
using (var decryptor = aes.CreateDecryptor())
{
var decryptedBytes = decryptor.TransformFinalBlock(Convert.FromBase64String(encryptedMessage), iv.Length, Convert.FromBase64String(encryptedMessage).Length - iv.Length);
// Check hash
var hash = Utilities.GenerateSha256Hash(decryptedBytes, HASH_SIZE, decryptedBytes.Length - HASH_SIZE);
var existingHash = new byte[HASH_SIZE];
Buffer.BlockCopy(decryptedBytes, 0, existingHash, 0, HASH_SIZE);
if (!existingHash.compareBytesTo(hash))
{
throw new CryptographicException("Message hash invalid.");
}
// Hash is valid, we're done
var res = new byte[decryptedBytes.Length - HASH_SIZE];
Buffer.BlockCopy(decryptedBytes, HASH_SIZE, res, 0, res.Length);
return Encoding.UTF8.GetString(res);
}
}
bool disposed;
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
aes.Dispose();
}
}
disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Run Code Online (Sandbox Code Playgroud)
我这样调用它:
using (cryptKeeper = new CryptKeeper(Repository.AppSettings["SharedSecret"], Repository.AppSettings["Salt"]))
{
renderingReport.Rendering = renderSegmentNav(currentUser.UserOwnsProduct(productId), book, renderingReport, currentSegment);
}
Run Code Online (Sandbox Code Playgroud)
这极大地提高了性能。之前对 MVC 控制器的调用需要在其结果中构建许多加密链接,总共花费了 2.7 秒。对于重用 AES 的新代码,总共需要 0.3 秒。
我可以确认该代码可以工作并且速度要快得多。我只是想确认,出于安全原因,以这种方式重用 AES 并不是一个坏主意。根据一点谷歌搜索,我每次调用GenerateIV()的事实是好的,而且我找不到任何东西说我不应该重复使用AES,只要我喜欢。
一般来说,您可以重用在 Java 和 C# 中实现加密算法的对象。但是,您应该确保始终将加密器和解密器保持在正确的状态。除非特别指定,否则不应将这些类用于多线程目的。
请注意,您遇到速度变慢的原因是Rfc2898DeriveBytes. 这个方法故意慢一些。您可以重复使用从中获得的密钥Rfc2898DeriveBytes,但应确保不重复使用任何 IV,IV 应该是随机的。Rfc2898DeriveBytes多次调用派生字节当然没有意义。
最后,在本地缓存持有 AES 密钥的对象可能会有些好处。首先,如果不需要的话,您不需要任何额外的密钥对象,其次,AES 首先根据给定密钥计算子密钥,这需要少量时间(尽管远不及计算子密钥所需的时间)。执行Rfc2898DeriveBytes)。
话又说回来,如果这会使您的设计变得不必要地复杂化,请不要这样做。优势还不够大。