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

时间:2012-11-29 19:42:38

标签: c# .net security aes

我在加密大量字符串的应用中遇到了一些性能问题。当我从名为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的事情。

1 个答案:

答案 0 :(得分:4)

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

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

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

然后,如果不必要地使您的设计复杂化,请不要这样做。优点还不够大。

相关问题