Reputation: 1041
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
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
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
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
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
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