Justin G Justin G - 4 years ago 240
Vb.net Question

VB.NET Display progress of file decryption?

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
Case CryptoAction.ActionEncrypt
cscryptostream = New CryptoStream(fsOutput, _
csprijndael.CreateEncryptor(bytkey, bytiv), _
Case CryptoAction.ActionDecrypt
cscryptostream = New CryptoStream(fsOutput, _
csprijndael.CreateDecryptor(bytkey, bytiv), _
End Select
While lngbytesprocessed < lngfilelength
intbytesincurrentblock = fsInput.Read(bytbuffer, 0, 4096)
cscryptostream.Write(bytbuffer, 0, intbytesincurrentblock)
lngbytesprocessed = lngbytesprocessed + CLng(intbytesincurrentblock)
End While
Catch ex As Exception

End Try
End Sub

Is I need to get the percentage of this process being done as an integer. I am going to use a background worker, so I need to call for this sub from the background worker and be able to keep refreshing a progress bar that the background worker reports to. Is this possible?
Thanks in advance.

Answer Source

There are a couple of things you can do to make your cryptor more efficient and other issues:

  • A method like encryptordecryptfile which then requires a "mode" argument to know which action to take means it really might be better off as 2 methods
  • The way you are going, you will be raising a blizzard of ProgressChanged events which the ProgressBar wont be able to keep up with given the animation. A 700K file will result in 170 or so progress reports of tiny amounts
  • Some of the crypto steps can be incorporated
  • You have a lot of things not being disposed of; you could run out of resources if you run a number of files thru it in a loop.

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 EncryptFile method:

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)

        ' 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)
            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
                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))
                        End If
                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

Note A
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 
    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.

Note B
Using blocks close and dispose of resources for you. Basically, if something has a Dispose method, then you should wrap it in a Using block.

Note C
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.

Note D
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 Invoke:

Delegate Sub ProgressReportDelegate(value As Int32)

Private Sub ReportProgress(v As Int32)
    If progBar.InvokeRequired Then
        progBar.Invoke(Sub() progBar.Value = v)
        progBar.Value = v
    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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download