我在加密大量字符串的应用中遇到了一些性能问题。当我从名为Encrypt()的公共方法调用私有方法getAes()时,大多数CPU使用都会发生:
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;
}
}
我试图通过缓存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;
}
}
我收到此错误:
安全手柄已关闭 在System.Runtime.InteropServices.SafeHandle.DangerousAddRef(布尔和成功)at at System.StubHelpers.StubHelpers.SafeHandleAddRef(SafeHandle pHandle,布尔和成功)at at System.Security.Cryptography.CapiNative.UnsafeNativeMethods.CryptGenRandom(SafeCspHandle hProv,Int32 dwLen,Byte [] pbBuffer)at System.Security.Cryptography.AesCryptoServiceProvider.GenerateIV()at Obr.Lib.CryptKeeper.Encrypt(String messageToEncrypt,String sharedSecret,String salt)in ... CryptKeeper.cs:28行at at Obr.Lib.HtmlRenderer.renderLawCitation(RenderContext renderContext,XElement xElement)in ... HtmlRenderer.cs:line 1472
据我所知,Encrypt()中的using()语句将处理AES并导致它中断。除非我知道重复使用是安全的,否则我不想进一步排除故障。如果重复使用是安全的,那么最好的方法是什么?
更新:我通过更长时间地保持AES对象解决了性能问题。我删除了静态关键字,并使该类一次性使用。以下是它现在的样子:
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);
}
}
我这样调用它:
using (cryptKeeper = new CryptKeeper(Repository.AppSettings["SharedSecret"], Repository.AppSettings["Salt"]))
{
renderingReport.Rendering = renderSegmentNav(currentUser.UserOwnsProduct(productId), book, renderingReport, currentSegment);
}
这极大地提高了性能。之前对MVC控制器的调用需要在其结果中构建许多加密链接,总共需要2.7秒。使用重新使用AES的新代码,总共需要0.3秒。
我可以确认代码是否有效并且速度更快。我只是想确认,出于安全原因,以这种方式重用AES并不是一个糟糕的IDEA。根据谷歌搜索的一点点,我每次调用GenerateIV()的事实都很好,我找不到任何说我不应该重复使用AES的事情。
答案 0 :(得分:4)
通常,您可以在Java和C#中重用实现加密算法的对象。但是,您应确保始终使加密器和解密器保持正确状态。除非特别指定,否则不应将这些类用于多线程目的。
请注意,您遇到速度减慢的原因是Rfc2898DeriveBytes
内的PBKDF2功能。这种方法故意慢。您可以重用从Rfc2898DeriveBytes
获得的密钥,但是您应该确保不重复使用任何IV,IV应该是随机的。当然,多次调用Rfc2898DeriveBytes
派生字节是没有意义的。
最后,在本地缓存持有AES密钥的对象可能会有所帮助。首先,如果你不需要,你不需要任何额外的关键对象,其次,AES首先从给定的密钥计算子密钥,这需要花费很少的时间(尽管不太接近它所需的时间)执行Rfc2898DeriveBytes
)。
然后,如果不必要地使您的设计复杂化,请不要这样做。优点还不够大。