I'm using this code to encrypt/decrypt files:
Public Shared Sub encryptordecryptfile(ByVal strinputfile As String, _
ByVal stroutputfile As String, _
ByVal bytkey() As Byte, _
ByVal bytiv() As Byte, _
ByVal direction As CryptoAction)
fsInput = New System.IO.FileStream(strinputfile, FileMode.Open, FileAccess.Read)
fsOutput = New System.IO.FileStream(stroutputfile, FileMode.OpenOrCreate, FileAccess.Write)
Dim bytbuffer(4096) As Byte
Dim lngbytesprocessed As Long = 0
Dim lngfilelength As Long = fsInput.Length
Dim intbytesincurrentblock As Integer
Dim cscryptostream As CryptoStream
Dim csprijndael As New System.Security.Cryptography.RijndaelManaged
Select Case direction
cscryptostream = New CryptoStream(fsOutput, _
csprijndael.CreateEncryptor(bytkey, bytiv), _
cscryptostream = New CryptoStream(fsOutput, _
csprijndael.CreateDecryptor(bytkey, bytiv), _
While lngbytesprocessed < lngfilelength
intbytesincurrentblock = fsInput.Read(bytbuffer, 0, 4096)
cscryptostream.Write(bytbuffer, 0, intbytesincurrentblock)
lngbytesprocessed = lngbytesprocessed + CLng(intbytesincurrentblock)
Catch ex As Exception
There are a couple of things you can do to make your cryptor more efficient and other issues:
encryptordecryptfilewhich then requires a "mode" argument to know which action to take means it really might be better off as 2 methods
ProgressChangedevents which the
ProgressBarwont be able to keep up with given the animation. A 700K file will result in 170 or so progress reports of tiny amounts
It might be worth noting that you can replace the entire
While block with
fsInput.CopyTo(cscryptostream) to process the file all at once. This doesnt allow progress reporting though. Its also not any faster.
Rather than a
BackgroundWorker (which will work fine), you might want to implement it as a
Task. The reason for this is that all those variables need to make their way from something like a button click to the
DoWork event where your method is actually called. Rather than using global variables or a class to hold them, a Task works a bit more directly (but does involve one extra step when reporting progress). First, a revised
Private Sub EncryptFile(inFile As String, outFile As String, pass As String, Optional reporter As ProgressReportDelegate = Nothing) Const BLOCKSIZE = 4096 Dim percentDone As Integer = 0 Dim totalBytes As Int64 = 0 Dim buffSize As Int32 ' Note A Dim key = GetHashedBytes(pass) Dim iv = GetRandomBytes(16) Dim cryptor As ICryptoTransform ' Note B Using fsIn As New FileStream(inFile, FileMode.Open, FileAccess.Read), fsOut As New FileStream(outFile, FileMode.OpenOrCreate, FileAccess.Write) fsOut.SetLength(0) ' Note C 'ToDo: work out optimal block size for Lg vs Sm files If fsIn.Length > (2 * BLOCKSIZE) Then ' use buffer size to limit to 20 progress reports buffSize = CInt(fsIn.Length \ 20) ' to multiple of 4096 buffSize = CInt(((buffSize + BLOCKSIZE - 1) / BLOCKSIZE) * BLOCKSIZE) ' optional, limit to some max size like 256k? 'buffSize = Math.Min(buffSize, BLOCK256K) Else buffSize = BLOCKSIZE End If Dim buffer(buffSize-1) As Byte ' Note D ' write the IV to "naked" fs fsOut.Write(iv, 0, iv.Length) Using rij = Rijndael.Create() rij.Padding = PaddingMode.ISO10126 Try cryptor = rij.CreateEncryptor(key, iv) Using cs As New CryptoStream(fsOut, cryptor, CryptoStreamMode.Write) Dim bytesRead As Int32 Do Until fsIn.Position = fsIn.Length bytesRead = fsIn.Read(buffer, 0, buffSize) cs.Write(buffer, 0, bytesRead) If reporter IsNot Nothing Then totalBytes += bytesRead percentDone = CInt(Math.Floor((totalBytes / fsIn.Length) * 100)) reporter(percentDone) End If Loop End Using Catch crEx As CryptographicException ' ToDo: Set breakpoint and inspect message Catch ex As Exception ' ToDo: Set breakpoint and inspect message End Try End Using End Using End Sub
One of the standard crypto tasks it could handle is creating the Key and IV arrays for you. These are pretty simple and could be shared/static members.
Public Shared Function GetHashedBytes(data As String) As Byte() Dim hBytes As Byte() ' or SHA512Managed Using hash As HashAlgorithm = New SHA256Managed() ' convert data to bytes: Dim dBytes = Encoding.UTF8.GetBytes(data) ' hash the result: hBytes = hash.ComputeHash(dBytes) End Using Return hBytes End Function Public Shared Function GetRandomBytes(size As Integer) As Byte() Dim data(size - 1) As Byte Using rng As New RNGCryptoServiceProvider ' fill the array rng.GetBytes(data) End Using Return data End Function
As will be seen later, you can store the IV in the encrypted file rather than saving and managing it in code.
Using blocks close and dispose of resources for you. Basically, if something has a
Dispose method, then you should wrap it in a
You dont want to report progress for every block read, that will just overwhelm the ProgressBar. Rather than another variable to keep track of when the progress has changed by some amount, this code starts by creating a buffer size which is 5% of the input file size so there will be about 20 reports (every 5%).
As the comments indicate, you may want to add some code to set minimum/maximum buffer size. Doing so would change the progress report frequency.
You can write the IV() to the filestream before you wrap it in the CryptoStream (and of course read it back first when Decrypting). This prevents you from having to store the IV.
The last part is kicking this off as a Task:
Dim t As Task t = Task.Run(Sub() EncryptFile(inFile, oFile, "MyWeakPassword", AddressOf ReportProgress)) ...
What a BGW does is execute the work on one thread, but progress is reported on the UI thread. As a
Task, all we need to do is use
Delegate Sub ProgressReportDelegate(value As Int32) Private Sub ReportProgress(v As Int32) If progBar.InvokeRequired Then progBar.Invoke(Sub() progBar.Value = v) Else progBar.Value = v progBar.Invalidate() End If End Sub
The Encryptor will work either directly or as a Task. For small files, you can omit the progress report entirely:
' small file, no progress report: EncryptFile(ifile, oFile, "MyWeakPassword") ' report progress, but run on UI thread EncryptFile(ifile, oFile, "MyWeakPassword", AddressOf ReportProgress) ' run as task Dim t As Task t = Task.Run(Sub() EncryptFile(ifile, oFile, "MyWeakPassword", AddressOf ReportProgress))
...and if you had a list of files to do, you could run them all at once and perhaps report total progress.