james
james

Reputation: 1041

C# - AES Decryption performance slow

I've currently got a folder full of 1280x720 AES encrypted bitmaps.

I'm trying to create a player to loop through the folder decrypt the images and show them in a image box (at reasonable speed)

Importantly I don't want the files to be decrypted to the drive then played. I want to decrypt them in memory only.

Currently the decryption takes about 100ms per image (frame). But I would like to try and get this down to about 10ms if possible.

The above was profiled on a 3.0ghz iCore7

Currently I'm running everything on the UI thread . I thought maybe if I multi-threaded the decrypting I could probably get the speed I wanted, then I will have to store a lot in memory. But I would rather see if I could just make the actual decryption faster.

Here is the decryption function:

private byte[] DecryptFile(string inputFile, string skey)
{

MemoryStream output1 = new MemoryStream();

// ok for tests..
Rfc2898DeriveBytes k2 = new Rfc2898DeriveBytes(skey, new byte[] { 10, 10, 10, 10, 10, 10, 10, 10 });

try
{
    using (RijndaelManaged aes = new RijndaelManaged())
    {
        byte[] key = k2.GetBytes(16);

        /* This is for demostrating purposes only. 
         * Ideally yu will want the IV key to be different from your key and you should always generate a new one for each encryption in other to achieve maximum security*/
        byte[] IV =  k2.GetBytes(16);

        byte[] cript = File.ReadAllBytes(inputFile);


        using (MemoryStream fsCrypt = new MemoryStream(cript))
        {

                using (ICryptoTransform decryptor = aes.CreateDecryptor(key, IV))
                {
                    using (CryptoStream cs = new CryptoStream(fsCrypt, decryptor, CryptoStreamMode.Read))
                    {
                        cs.CopyTo(output1);
                    }
            }
        }
    }
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message);
}

return output1.ToArray() ;
}

Is there a more efficient way to decrypt than the above function?

Upvotes: 4

Views: 11464

Answers (5)

Lukáš Koten
Lukáš Koten

Reputation: 1253

I got it almost two time faster when using byte array strictly as a output instead of yours MemoryStream.

My code sample:

byte[] ds = new byte[data.Length];
byte[] decryptedData;
using (Aes aes = CreateAes(key, iv, cipherMode, paddingMode))
{
    using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
    {
        int i = 0;
        using (MemoryStream msDecrypt = new MemoryStream(data))
        {
            using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
            {
                int k;
                while ((k = csDecrypt.ReadByte()) != -1)
                {
                    ds[i++] = (byte)k;
                }
            }
        }
        decryptedData = new byte[i];
        Buffer.BlockCopy(ds, 0, decryptedData, 0, i);
    }
}
return decryptedData;

Upvotes: 0

Scott Chamberlain
Scott Chamberlain

Reputation: 127583

Rfc2898DeriveBytes is designed to be intentionally slow, it used to slow down brute force hacking attempts. Since every file looks like it is using the same key and IV (btw, to address the IV issue you say in the comments, just store the IV has the first bytes in the file itself. The IV does not need to be kept secret, only the key does)

Here is a updated version with a few other tweaks too.

private IEnumerable<byte[]> DecryptFiles(IEnumerable<string> inputFiles, string skey)
{
    //Only performing the key calculation once.
    Rfc2898DeriveBytes k2 = new Rfc2898DeriveBytes(skey, new byte[] { 10, 10, 10, 10, 10, 10, 10, 10 });
    byte[] key = k2.GetBytes(16)

    foreach(var inputFile in inputFiles)
    {
        yield return DecryptFile(inputFile, key);
    }
}

private byte[] DecryptFile(string inputFile, byte[] key)
{
    var output1 = new MemoryStream();

    try
    {
        //If you are going to use AES, then use AES, also the CSP is faster than the managed version.
        using (var aes = new AesCryptoServiceProvider ())
        {
            //No need to copy the file in to memory first, just read it from the hard drive.
            using(var fsCrypt = File.OpenRead(inputFile))
            {
                //Gets the IV from the header of the file, you will need to modify your Encrypt process to write it.
                byte[] IV = GetIV(fsCrypt);

                //You can chain consecutive using statements like this without brackets.
                using (var decryptor = aes.CreateDecryptor(key, IV))
                using (var cs = new CryptoStream(fsCrypt, decryptor, CryptoStreamMode.Read))
                {
                    cs.CopyTo(output1);
                }
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }

    return output1.ToArray();
}

//This function assumes you wrote a 32 bit length then the array that was the read length long.
private static byte[] GetIV(Stream fileStream)
{
    var reader = new BinaryReader(fileStream);
    var keyLength = reader.ReadInt32();
    return reader.ReadBytes(keyLength);
}

Upvotes: 6

Idle_Mind
Idle_Mind

Reputation: 39132

This is what I use in my system...not sure if it will be faster or not. It just seems like you're doing an un-necessary amount of reading and copying:

Public Shared Function ReadImageAES(ByVal FileName As String) As Image
    Dim img As Image = Nothing
    Try
        Using fs As New FileStream(FileName, FileMode.Open)
            Using cs As New CryptoStream(fs, Crypto.AES.CreateDecryptor, CryptoStreamMode.Read)
                img = Image.FromStream(cs)
            End Using
        End Using
    Catch ex As Exception
        img = Nothing
        'Debug.Print("ReadImageAES()failed: " & ex.ToString)
    End Try
    Return img
End Function

Public Shared Sub WriteImageAES(ByVal FileName As String, ByVal img As Image, Optional ByVal NewUserKey As String = Nothing)
    If Not IsNothing(img) Then
        Try
            If File.Exists(FileName) Then
                File.Delete(FileName)
            End If
            Using fs As New FileStream(FileName, FileMode.OpenOrCreate)
                Dim Key() As Byte
                If IsNothing(NewUserKey) Then
                    Key = Crypto.AES.Key
                Else
                    Key = Crypto.SHA256Hash(NewUserKey)
                End If
                Using cs As New CryptoStream(fs, Crypto.AES.CreateEncryptor(Key, Crypto.AES.IV), CryptoStreamMode.Write)
                    Dim bmp As New Bitmap(img)
                    bmp.Save(cs, System.Drawing.Imaging.ImageFormat.Jpeg)
                    bmp.Dispose()
                    cs.FlushFinalBlock()
                End Using
            End Using
        Catch ex As Exception
            'Debug.Print("WriteImageAES() Failed: " & ex.ToString)
        End Try
    Else
        MessageBox.Show(FileName, "GetImage() Failed")
    End If
End Sub

Here's my Crypto class, which was originally written to only target .Net 2.0:

Imports System.IO
Imports System.Text
Imports System.Security.Cryptography
Public Class Crypto

    Private Shared _UserKey As String = ""
    Private Shared _SHA256 As New SHA256Managed
    Private Shared _AES As New RijndaelManaged
    Private Const _IV As String = "P5acZMXujMRdmYvFXYfncS7XhrsPNfHkerTnWVT6JcfcfHFDwa" ' <--- this can be anything you want

    Public Shared Property UserKey() As String
        Get
            Return Crypto._UserKey
        End Get
        Set(ByVal value As String)
            Crypto._UserKey = value
            Crypto._AES.KeySize = 256
            Crypto._AES.BlockSize = 256
            Crypto._AES.Key = Crypto.SHA256Hash(Crypto._UserKey)
            Crypto._AES.IV = Crypto.SHA256Hash(Crypto._IV)
            Crypto._AES.Mode = CipherMode.CBC
        End Set
    End Property

    Public Shared Function SHA256Hash(ByVal value As String) As Byte()
        Return Crypto._SHA256.ComputeHash(ASCIIEncoding.ASCII.GetBytes(value))
    End Function

    Public Shared ReadOnly Property AES() As RijndaelManaged
        Get
            Return Crypto._AES
        End Get
    End Property

End Class

Upvotes: 0

Floris
Floris

Reputation: 46395

If you want to "roll your own", you might want to take a look at this paper describing new instructions on the i7 that are geared towards optimal AES performance. If your C# library doesn't take advantage of those you might get a nice speed boost.

From this paper, it seems that the best you can hope to achieve with a 256 byte key is about 0.32 cycles / byte - for a 1MB file and a 3 GHz processor using all cores with hyperthreading, that would be roughly 0.1 ms per file. That's 1000x faster than you are seeing - so yes, you seem to be well off the "top speed" mark. Same paper claims about 6x slower for single core - still much faster than you are seeing.

I would go looking for another library before writing my own, though. Take a look at the one that Intel provides: http://software.intel.com/en-us/articles/download-the-intel-aesni-sample-library

Upvotes: 0

wollnyst
wollnyst

Reputation: 1931

You can use the AesCryptoServiceProvider which is faster.

In contrast to RijndaelManaged which is a pure managed implementation, AesCryptoServiceProvider uses the Windows Crypto API.

Upvotes: 11

Related Questions