加密字符串时重用 AES 对象是否安全?

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,只要我喜欢。

Maa*_*wes 5

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

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

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

话又说回来,如果这会使您的设计变得不必要地复杂化,请不要这样做。优势还不够大。