SHA1 Hash berechnen mit Progressbar

  • VB.NET

Es gibt 15 Antworten in diesem Thema. Der letzte Beitrag () ist von Kangaroo.

    SHA1 Hash berechnen mit Progressbar

    Guten Abend Zusammen,

    seit ein paar Wochen schaue ich mir jetzt schon VB.net an und bin aktuell an einem kleinen Problem hängen geblieben.

    Es geht dabei um folgendes: Das (Test-)Programm soll lediglich den SHA1 Hash einer Datei berechnen.

    Soweit eigentlich kein Problem, jedoch wird es problematisch wenn das Programm den Hash von bis zu 20 GB großen Dateien berechnen muss.
    Den eigentlichen FileStream habe ich schonmal in einen eigenen Thread gepackt. (das es nicht zu Hängern kommt) Nun würde ich gerne den Vortschritt der SHA1 Berechnung in einer Progressbar anzeigen.

    Code bis jetzt:
    Imports
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Security.Cryptography
    2. Imports System.Text
    3. Imports System.IO
    4. Imports System.Threading

    Public Class Form1
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private thrSHA1Hash As Thread
    3. Dim GlobalResult As String
    4. Dim GlobaleDateilänge As Double
    5. Dim sFile As String
    6. Private Sub btnGetFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnGetFile.Click
    7. Dim MyOpenFileDialog As New OpenFileDialog()
    8. If MyOpenFileDialog.ShowDialog() = DialogResult.OK Then
    9. TextBox1.Text = MyOpenFileDialog.FileName.ToString()
    10. End If
    11. End Sub
    12. Private Sub btnCheck_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCheck.Click
    13. sFile = TextBox1.Text
    14. thrSHA1Hash = New Threading.Thread(AddressOf SHA1FileHash)
    15. thrSHA1Hash.Start()
    16. Timer1.Enabled = True
    17. End Sub
    18. Private Sub SHA1FileHash()
    19. Dim SHA1 As New SHA1CryptoServiceProvider
    20. Dim Hash As Byte()
    21. Dim Result As String = ""
    22. Dim Tmp As String = ""
    23. Dim FN As New FileStream(sFile, FileMode.Open, FileAccess.Read, FileShare.Read, 8192)
    24. Dim SHA1StreamReader As StreamReader = New StreamReader(FN)
    25. Dim Dateilänge As Double = Double.Parse(SHA1StreamReader.ReadToEnd.Length) ''Bringt bei größeren Dateien: Eine nicht behandelte Ausnahme des Typs "System.OutOfMemoryException" ist in mscorlib.dll aufgetreten.
    26. 'Dim AktuelleStelle As Integer = MD5StreamReader.Read.ToString Double.Parse(MD5StreamReader.ReadToEnd()) 'So etwas in die Richtung...
    27. GlobaleDateilänge = Dateilänge
    28. SHA1.ComputeHash(FN)
    29. FN.Close()
    30. Hash = SHA1.Hash
    31. For i As Integer = 0 To Hash.Length - 1
    32. Tmp = Hex(Hash(i))
    33. If Len(Tmp) = 1 Then Tmp = "0" & Tmp
    34. Result += Tmp
    35. Next
    36. GlobalResult = Result
    37. End Sub
    38. Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick 'TimerInterval = 500
    39. 'If (Not thrMD5Hash.IsAlive) Then
    40. TextBox2.Text = GlobalResult
    41. TextBox3.Text = GlobaleDateilänge
    42. 'Timer1.Enabled = False
    43. 'End If
    44. End Sub
    45. End Class

    Form1


    In der ersten Zeile unter "Hash:" wird der SHA1 Hash angezeigt, sobald dieser fertig ist.
    In der zweiten Zeile unter "Hash:" wird die InsDateigröße der zu Scannenden Datei ausgegeben, allerdings erst wenn der Hash berechnet wurde.
    Die Progressbar hat momentan noch keine Funktion.

    Ein Codebeispiel wie man das bereits vorhandene ergänzen müsste bzw. wie man per StreamReader o.ä. den FileStream auslesen kann (auch bei großen Dateien) wäre sehr hilfreich! :)
    Bzw. wie man die ausgelesenen Daten aus dem Thread in die Progressbar bekommt.

    MfG, FireEmerald
    @FireEmerald
    Ich kenn mich mit Hash's nicht sonderlich gut aus. Aber hier:

    VB.NET-Quellcode

    1. Hash = SHA1.Hash
    2. For i As Integer = 0 To Hash.Length - 1
    3. Tmp = Hex(Hash(i))
    4. If Len(Tmp) = 1 Then Tmp = "0" & Tmp
    5. Result += Tmp
    6. Next

    Hasht du doch, oder ?
    Setzte vor der Schleife Progressbar1.Maximum = Hash.lenght (oder.count weiß gerade nicht)
    Und dann in der Schleife ProgressBar1.Value += 1
    Ach und:

    VB.NET-Quellcode

    1. Using ofd as new OpenFileDialog
    2. '...
    3. End Using

    ist eleganter.
    Darf ich eig. Werbung machen ? :P Falls dir die ProgressBar zu langweilig ist: [Beta] NeonProgress
    epische eigenwerbung :D dann darf ich aber auch ran:
    [Release] MetroSuite [UPDATE 08.02.2013]- Holt euch den modernen Windows 8 Style.
    Anyways:
    Pack das ganze einfach in einen backgroundworker (sofern der code funktioniert). Mit hilfe von dem kannst du ganz leicht den fortschritt in einer progressbar anzeigen.

    Edit://
    [VB.NET] Fortschritt eines BGW in Progressbar anzeigen
    [VB 2008] Backgroundworker

    -------
    [VB.NET] Multithreading mit BackgroundWorker
    Mfg: Gather
    Private Nachrichten bezüglich VB-Fragen werden Ignoriert!


    LaMiy schrieb:

    Darf ich eig. Werbung machen ?
    Nein, ich möchte keine andere Progressbar. :thumbdown: sondern eher eine passende Antwort auf mein Problem.

    LaMiy schrieb:

    Ich kenn mich mit Hash's nicht sonderlich gut aus. Aber hier:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Hash = SHA1.Hash
    2. For i As Integer = 0 To Hash.Length - 1
    3. Tmp = Hex(Hash(i))
    4. If Len(Tmp) = 1 Then Tmp = "0" & Tmp
    5. Result += Tmp
    6. Next

    Hasht du doch, oder ?
    Ich hab hier schon absichtlich das ganze in Grundlagen gepostet, da ich diese Zeile nicht fehlerfrei verstehe.

    LaMiy schrieb:

    Setzte vor der Schleife Progressbar1.Maximum = Hash.lenght (oder.count weiß gerade nicht)
    Und dann in der Schleife ProgressBar1.Value += 1
    Du kannst nicht direkt aus einem Thread, die Progressbar setzen. Korrigiere mich wenn ich da falsch liege.

    Gather schrieb:

    Pack das ganze einfach in einen backgroundworker (sofern der code funktioniert). Mit hilfe von dem kannst du ganz leicht den fortschritt in einer progressbar anzeigen.
    Der Hash Code funktioniert schon, nur habe ich mich noch nie mit backgroundworker auseinandergesetzt.
    Folglich fällt es mir jetzt recht schwer mit diesem das ganze zum laufen zu bekommen. ?(

    Könnte jemand von euch ein Beispiel posten wie dieses hier ?
    Dim Dateilänge As Double = Double.Parse(SHA1StreamReader.ReadToEnd.Length) ''Bringt bei größeren Dateien: Eine nicht behandelte Ausnahme des Typs "System.OutOfMemoryException" ist in mscorlib.dll aufgetreten.

    .readToEnd ist keine gute Idee, da Du in diesem Statement ddie ganze Datei auf einmal in den Arbeitsspeicher liest: das ist gerade nicht die Idee eines Streams. Stream.length sollte auch tun.

    Der meiste zeitliche Aufwand ist die Berechnung des Hash selber. Da die SHA1 Klasse dabei keine Events kennt und/oder einen Progress meldet , kannst Du wohl währenddessen nur eine Progressbar im 'Marquee Style' anzeigen.

    LaMiy schrieb:

    Hasht du doch, oder ?
    nein tut er nicht, er formatiert nur den Hash für die Ausgabe.

    Dim hashString As String = BitConverter.ToString(hash).Replace("-"c, "") täte es auch.

    Kangaroo schrieb:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Hash = SHA1.Hash
    2. For i As Integer = 0 To Hash.Length - 1
    3. Tmp = Hex(Hash(i))
    4. If Len(Tmp) = 1 Then Tmp = "0" & Tmp
    5. Result += Tmp
    6. Next

    nein tut er nicht, er formatiert nur den Hash für die Ausgabe.
    So etwas in die Richtung dachte ich mir schon bei dem Abschnitt. Danke für die Information. :) Dim Dateilänge As Double = Double.Parse(SHA1StreamReader.Stream.Length), funktioniert nicht...


    Kangaroo schrieb:

    Dim hashString As String = BitConverter.ToString(hash).Replace("-"c, "") täte es auch.
    Ok, du erstellst damit einen String, der wiederrum aus dem Hash besteht, welcher mit ("-"c, "") modifiziert wird.
    Also was bleibt nach dem Replacen genau noch in dem String drinnen? Verstehe nicht ganz wie ich aus dieser Zeile die Daten für die Progressbar nehmen soll.

    Die Erstellung des Hashs wird ja in diesem Schritt gestartet, durch den FileStream:

    VB.NET-Quellcode

    1. Dim FN As New FileStream(sFile, FileMode.Open, FileAccess.Read, FileShare.Read, 8192)

    Nun bearbeitet der Sub ja den Code von Oben nach Unten. Sprich der FileStream, welcher für die Progressbar abgefragt werden muss, endet hier:

    VB.NET-Quellcode

    1. SHA1.ComputeHash(FN)
    2. FN.Close()

    Folglich muss der Code zum abfragen in den Abschnitt dazwischen.

    VB.NET-Quellcode

    1. Dim FN As New FileStream(sFile, FileMode.Open, FileAccess.Read, FileShare.Read, 8192)
    2. 'HIER
    3. SHA1.ComputeHash(FN)
    4. FN.Close()
    5. Hash = SHA1.Hash
    6. For i As Integer = 0 To Hash.Length - 1
    7. Tmp = Hex(Hash(i))
    8. If Len(Tmp) = 1 Then Tmp = "0" & Tmp
    9. Result += Tmp
    10. Next

    Nun die Frage: Wie ließt man den Dim FN As New FileStream(sFile, FileMode.Open, FileAccess.Read, FileShare.Read, 8192) aus, sprich

    - einmal die komplette Länge und (Dim Dateilänge As Double = Double.Parse(SHA1StreamReader.Stream.Length), funktioniert nicht)
    - die aktuelle Position im Stream?

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „FireEmerald“ ()

    Kangaroo schrieb:

    Die Erstelllung des Hash erfolgt in genau 1 Statement: dim hash() as byte=SHA1.ComputeHash(FN)
    Du meinst diesen Abschnitt: SHA1.ComputeHash(FN), korrekt? Gibt es hierfür irgend eine Möglichkeit den Vortschritt in einer Progressbar anzuzeigen?

    Kangaroo schrieb:

    Dim hashString As String = BitConverter.ToString(hash).Replace("-"c, "") täte es auch.
    Mir stellt sich nach wie vor die Frage was mir dies im Zusammenhang mit der Progressbar weiterhilft.
    Ich frage mich wirklich ob Du ein Wort von dem verstanden hast, was ich geschrieben habe :huh:

    Deine Methode läuft grob folgendermassen ab:
    1. HashAlgorithm deklarieren, in diesem Fall SHA
    2. Filestream deklarieren
    3. Länge des Streams durch Einlesen in den Artbeitsspeicher ermitteln (Blödsinn)
    4. den Hashwert (256Bit) in 1 Schritt errechnen: ComputeHash
    5. den Hashwert(256Bit) als String aufbereiten

    ad 1) Hashalgorithmus deklarieren: macht man bitte sehr mit einem Using-Statement

    ad 2) Filestream deklarieren: ebenfalls mit einem Using-Statement

    ad 3) Länge des Streams berechnen
    Die Länge einer Datei ist ein Long-Wert (64bit) und kein Double. Diesen ruft man unter Filestream.Length ab, ohne dass der Stream vollständig gelesen werden muss. Und Dein Double.Parse Konstrukt hast Du ohne Sinn und Verständnis irgendwoher kopiert, was soll das bitte bewirken ?

    ad 4) solange Du die eigentlich Berechnung in einem einzigen Statement machst, wirst Du nie einen Fortschritt berechnen können. Also kommt für Dich nur eine Progressbar mit Marquee Style in Frage. Das sollte Deinem momentanen Wissenstand entsprechen.
    Ansonsten lese den Stream blockweise und berechne den Hash mit TransformBlock+TransformFinalBlock.

    ad 5) Hashwert als (Hex)-String anzeigen: BitConverter statt Deiner (mies codierten) For Schleife.

    Kangaroo schrieb:

    ad 1) & ad 2) Hashalgorithmus deklarieren: macht man bitte sehr mit einem Using-Statement
    Wusste ich nicht bzw. Using habe ich bis jetzt noch nie benutzt.

    Kangaroo schrieb:

    ad 3) Länge des Streams berechnen
    Sollte klar sein.

    Kangaroo schrieb:

    ad 4) Also kommt für Dich nur eine Progressbar mit Marquee Style in Frage.
    Hinweis zu Windows XP Home Edition, Windows XP Professional x64 Edition, Windows Server 2003: Der Marquee-Stil wird nur auf diesen Plattformen unterstützt. - Wenn dies so ist bringt mir Marquee Style nichts.

    Kangaroo schrieb:

    Ansonsten lese den Stream blockweise und berechne den Hash mit TransformBlock+TransformFinalBlock.
    Naja ...

    Kangaroo schrieb:

    ad 5) Hashwert als (Hex)-String anzeigen: BitConverter statt Deiner (mies codierten) For Schleife.
    Dim hashString As String = BitConverter.ToString(hash).Replace("-"c, "")

    FireEmerald schrieb:

    Hinweis zu Windows XP Home Edition, Windows XP Professional x64 Edition, Windows Server 2003: Der Marquee-Stil wird nur auf diesen Plattformen unterstützt. - Wenn dies so ist bringt mir Marquee Style nichts.
    Sondern ? Der Style funktioniert ebenfalls unter Win7, über das Compact Framework kann ich nichts sagen. Warum probierst Du es nicht einfach aus, anstatt Dich hier nur füttern zu lassen ?

    FireEmerald schrieb:

    Naja ...
    ?
    Warum ich es nicht ausprobiere?

    Ganz einfach, weil ich noch im Geschäft sitze.

    Das "Naja" soll heißen ich werde es probieren, glaube aber nicht, dass ich es auch zum funktionieren bringen werde. Wird sich rausstellen.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „FireEmerald“ ()

    FireEmerald schrieb:

    Warum ich es nicht ausprobiere? Ganz einfach, weil auf ich noch im Geschäft sitze.
    Das ist natürlich ein Argument :rolleyes:

    FireEmerald schrieb:

    Das "Naja" soll heißen ich werde es probieren, glaube aber nicht, dass ich es auch zum funktionieren bringen werde. Wird sich rausstellen.
    Wenn Du weisst wie man einen Stream blockweise liest, so kann das TransFormBlock relativ leicht eingebaut werden

    Mach Dir mal eine TestForm mit 1 Button (btnStart) und 1 Progressbar (Progressbar1)

    "Klasse FileHash"

    VB.NET-Quellcode

    1. '############################################################################################
    2. '######## CLASS: FileHash ######### -----
    3. '############################################################################################
    4. '/// History:
    5. '/// -
    6. Imports System.Security.Cryptography
    7. Imports System.Threading
    8. ''' <summary>Computes a Filehash asynchroneously. Events are raised on the calling thread, no invoke necessary</summary>
    9. Public Class FileHash
    10. '############################################################
    11. '### MEMBER & PROPERTIES #####
    12. '############################################################
    13. ' EVENTS
    14. Public Event HashStarted(ByVal fileSize As Long)
    15. Public Event HashProgress(ByVal transformedBytes As Long, ByVal totalBytes As Long)
    16. Public Event HashFinished()
    17. ' PUBLIC PROPERTIES
    18. Const BLOCKSIZE As Integer = 256 * 256
    19. Public ReadOnly Property Hash As Byte()
    20. Get
    21. Return _hash
    22. End Get
    23. End Property
    24. Protected _hash As Byte() = {}
    25. Public ReadOnly Property HashString As String
    26. Get
    27. Return BitConverter.ToString(_hash).Replace("-"c, "")
    28. End Get
    29. End Property
    30. ' private
    31. Private _context As SynchronizationContext = Nothing
    32. Private _workerThread As Thread
    33. '############################################################
    34. '### Initialize / Dispose #####
    35. '############################################################
    36. Public Sub New()
    37. ' save context of calling thread: can be nothing
    38. _context = SynchronizationContext.Current
    39. End Sub
    40. '############################################################
    41. '### METHODS #####
    42. '############################################################
    43. ' starts the hashing algorithm
    44. Public Sub ComputeHashAsync(ByVal filePath As String)
    45. ' start thread as backgroundthread
    46. _workerThread = New Thread(Sub() computeHashAsyncWorker(filePath)) With {.IsBackground = True}
    47. _workerThread.Start()
    48. End Sub
    49. ' Worker thread
    50. Private Sub computeHashAsyncWorker(ByVal filePath As String)
    51. ' start hashing
    52. Using SHA1 As New SHA1CryptoServiceProvider, _
    53. fs As New IO.FileStream(filePath, IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
    54. ' get filesize
    55. Dim fileSize As Long = fs.Length
    56. ' start: raiseevent on GUI thread
    57. If _context IsNot Nothing Then _context.Send(Sub() RaiseEvent HashStarted(fileSize), Nothing) Else RaiseEvent HashStarted(fileSize)
    58. ' declare buffer + other vars
    59. Dim readBuffer(blocksize - 1) As Byte, readBytes As Integer
    60. Dim transformBuffer As Byte(), transformBytes As Integer, transformBytesTotal As Long = 0
    61. ' read first block
    62. readBytes = fs.Read(readBuffer, 0, blocksize)
    63. ' read + transform blockwise
    64. While readBytes > 0
    65. ' save last read
    66. transformBuffer = readBuffer
    67. transformBytes = readBytes
    68. ' read buffer
    69. readBuffer = New Byte(blocksize - 1) {}
    70. readBytes = fs.Read(readBuffer, 0, blocksize)
    71. ' transform
    72. Select Case readBytes
    73. Case 0 : SHA1.TransformFinalBlock(transformBuffer, 0, transformBytes)
    74. Case Else : SHA1.TransformBlock(transformBuffer, 0, transformBytes, transformBuffer, 0)
    75. End Select
    76. ' inform about progress here
    77. transformBytesTotal += transformBytes
    78. ' raise progress event on GUI-Thread
    79. If _context IsNot Nothing Then _context.Send(Sub() RaiseEvent HashProgress(transformBytesTotal, fileSize), Nothing) Else RaiseEvent HashProgress(transformBytesTotal, fileSize)
    80. End While
    81. ' all done
    82. _hash = SHA1.Hash
    83. If _context IsNot Nothing Then _context.Send(Sub() RaiseEvent HashFinished(), Nothing) Else RaiseEvent HashFinished()
    84. End Using
    85. End Sub
    86. End Class


    "Aufruf in der Form"

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private WithEvents SHAHash As New FileHash ' die Hash Klasse
    3. Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click
    4. ' start hashing async
    5. SHAHash.ComputeHashAsync("D:\Test")
    6. End Sub
    7. ' Hash Started
    8. Private Sub SHAHash_ComputeHashStarted(ByVal fileSize As Long) Handles SHAHash.HashStarted
    9. btnStart.Enabled = False ' disable start button
    10. ProgressBar1.Value = 0
    11. End Sub
    12. ' Hash Progress
    13. Private Sub SHAHash_ComputeHashProgress(ByVal transformedBytes As Long, ByVal totalBytes As Long) Handles SHAHash.HashProgress
    14. Dim percentage As Double = transformedBytes / totalBytes
    15. ProgressBar1.Value = CInt(ProgressBar1.Maximum * percentage) ' update progressbar
    16. btnStart.Text = String.Format("Hashing File [{0:N0} from {1:N0} Bytes]", transformedBytes, totalBytes)
    17. End Sub
    18. ' Hash finished
    19. Private Sub SHAHash_ComputeHashFinished() Handles SHAHash.HashFinished
    20. btnStart.Enabled = True
    21. btnStart.Text = String.Format("Start [Last Hash={0}]", SHAHash.HashString.ToLower)
    22. End Sub
    23. End Class


    Das blockweise Lesen + Transform ist in der FileHash Klasse in einen Thread ausgelagert (Sub computeHashAsyncWorker).
    Die 3 Events Start, Progress, Finished werden dabei userfreundlich auf dem GUI-Thread ausgegeben, insofern ist kein Invoken mehr nötig.

    Da die CryptoServiceProvider alle von der Basisklasse HashAlgorithm abstammen, kann die Klasse leicht erweitert werden um zum Beispiel einen MD5 Hash zu errechnen . Dazu wäre nur 1 zusätzliche Property nötig die den Algorithmus spezifiziert.

    FireEmerald schrieb:

    Falls du diesen selbst geschrieben hast, ist das wirklich eine tolle Leistung.
    Ich poste keinen fremden Code unter meinem eigenen Namen. Wie gesagt ist das nur eine Ergänzung eines normalen Stream Blockreads, wobei das Switchen auf den UI-Thread vom Verständnis her wohl noch das Schwierigste ist. ( -> Invoke aus einer DLL heraus - aber wie?

    Ansonsten lent man meiner Meinung nach von Codebeispielen am meisten, wenn man denn wirklich lernen will und nicht nur BlackBox - Code kopieren. Ein paar Anwmerkungen:
    - Threads sollten (entgegen hier geposteten Beispielen) immer per Event oder Callbacks die übergeordnete Klasse informieren
    - Using Statements immer bei der Deklaration von Klassen verwenden die unmanaged Handles verwenden (File, Pens, Brushes, DC, windows ...), da dann auch bei einer Exception ein sauberes Dispose gewährleistet ist
    - Events die Argumente besitzen sollten eigentlich von System.Eventargs abgeleitet werden, hier der Einfachheit halber nicht gemacht
    - beim Switchen des Threads auf einen anderen Thread wurde .Send verwendet: damit wartet der thread bis das Event ausgeführt wurde
    - mit _context.Post würde er nicht warten und die Ausführung wohl beschleunigt, allerdings kann es dan die üblichen Synchronisationprobleme geben
    - bei Dateien unter der Blocksize (~32k) bringt die Progressbar nicht viel, sollte klar sein warum

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Kangaroo“ ()