5

我在加密大量字符串的应用程序中遇到了一些性能问题。大多数 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.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) 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) 在 ...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 并不是一个坏主意。根据一些谷歌搜索,我每次都调用 GenerateIV() 的事实很好,而且我找不到任何说我不应该重复使用 AES 只要我喜欢。

4

1 回答 1

4

通常,您可以重用在 Java 和 C# 中实现加密算法的对象。但是,您应该确保始终将加密器和解密器保持在正确的状态。除非特别指定,否则不应将这些类用于多线程目的。

请注意,您遇到速度变慢的原因是由于Rfc2898DeriveBytes. 这种方法是故意慢的。您可以重复使用从中获得的密钥,Rfc2898DeriveBytes但您应该确保不重复使用任何 IV,IV 应该是随机的。Rfc2898DeriveBytes多次调用派生字节当然没有意义。

最后,在本地缓存持有 AES 密钥的对象可能会有所帮助。首先,如果您不需要,您不需要任何额外的密钥对象,其次,AES 首先从给定的密钥计算子密钥,这需要少量时间(尽管远不及它花费的时间执行Rfc2898DeriveBytes)。

再说一次,如果它不必要地使您的设计复杂化,请不要这样做。优势还不够大。

于 2012-12-16T17:56:44.063 回答