たくさんの文字列を暗号化するアプリでパフォーマンスの問題が発生しています。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;
}
}
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.Security.Cryptography.CapiNative.UnsafeNativeMethods.CryptGenRandom(SafeCspHandle h Int32 dwLen、Byte [] pbBuffer)at System.Security.Cryptography.AesCryptoServiceProvider.GenerateIV()at Obr.Lib.CryptKeeper.Encrypt(String messageToEncrypt、String sharedSecret、String salt)in ... CryptKeeper.cs:line 28 at Obr .Lib.HtmlRenderer.renderLawCitation(RenderContext renderContext、XElement xElement)in ... HtmlRenderer.cs:line 1472
Encrypt()のusing()ステートメントがAESを破棄し、それが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);
}
}
私はそれを次のように呼び出します:
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の再利用は、セキュリティ上の理由から悪いアイデアではないことを確認したいだけです。ちょっとしたグーグル検索によると、毎回GenerateIV()を呼び出しているという事実は良いことであり、AESを好きなだけ再利用してはいけないと言っているものは何も見つかりません。