Hash-Werte aus Dateien ermitteln, und dann...?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

im unten stehenden Quelltext lese ich eine *.Zip-Datei aus. Mittels der namelist()-Methode lasse ich mir in der For-Schleife alle Dateien aus dem aktuellen Archiv ermitteln. Anschließend werden die einzelnen Dateien in der For-Schleife mit dem With-Statement geöffnet, und vom Inhalt der
jeweiligen Datei einen SHA521-Wert erzeugt, und anshcließend wird dieser Hash-Wert einer zuvor erstellte leere Liste hinzufügt. Sobald die For-Schleife ihre Arbeit getan hat, wird die Liste an die hash_all-Funktion übergeben. Und nun stecke ich gedanklich fest. Ich habe ja nun eine ganze Reihe von Hash-Werten in der Liste. Und aus all diesen Hash-Werten möchte ich am Ende sozusagen einen Hash-Wert bekommen. Denn auf diesem Weg möchte ich dann ein Archiv "absichern". Zumindest war das meine Idee.

Code: Alles auswählen

# Import zipfile library
import zipfile

# Import hashlib library (md5 method is part of it)
import hashlib

def hash_all(prompt):
    print "my prompt", prompt

def hash_file_content(file_name):
    # Create a empty list for all hashes
    list_hashes = []

    file_path = zipfile.ZipFile(file_name, "r")

    for name in file_path.namelist():
        #print "All files are: ",name
        try:
            # Open,close, read file and calculate SHA512 on its contents
            # Notice: Its should also open the file in binary mode (ex. rb),
            # otherwise it might gets problems 
            with open(name,'rb') as file_to_check:
                data = file_to_check.read()
                # pipe contents of the file through 
                sha_512_returned = hashlib.sha512(data).hexdigest()
                print "Hashed (", sha_512_returned ,") from content of file", name
                list_hashes.append(sha_512_returned)
        except Exception:
            #print "Exception", Exception
            pass
    #print "list_hashes", list_hashes
    hash_all(list_hashes)

if __name__ == "__main__":      
    zip_file_name = raw_input("Enter path completly: ")
    hash_file_content(zip_file_name)

__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Stattdessen kannst du auch einfach *ein* SHA512 Objekt erzeugen, alle Dateiinhalte mit "update" hinzufuegen, und gut ist. Oder du sparst dir gleich das gepfriemel in dem Archiv, und hashst das gesamte Archiv.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo __deeds__,

das mit der update-Funktion ist mir gar nicht eingefallen. Aber deine Anmerkung, aus dem gesamtem Archiv einen Hash-Wert zu erstellen: Ist dies nicht etwas unsicher, weil dann aus den Dateinamen der jeweiligen Dateien im Archiv ein Hash-Wert erzeugt wird? Ich wollte nämlich die Inhalte der jeweiligen Dateien heranziehen und daraus Hash-Werte erstellen. Während du eine Antwortmöglichkeit geschrieben hast, habe ich das irgendwie versucht so zu lösen: In der hash_all_hashes-Funktion wird die Liste noch einmal durch die For-Schleife geschickt, und die einzelnen Elemente der Liste werden dann wie in einem 'Array' in die zuvor erstelle total_hash-Variable gepackt, und aus dem Inhalt dieser Variable wird dann ein einziger Hash-Wert erzeugt.

Neben der Tatsache, dass mein Vorgang ziemlich aufgebläht wirkt und dazu auch noch umständlich, frage ich mich, ob ich irgendwelche Speicher-Probleme bekommen könnte, wenn ich alle Elemente in eine Variable stopfe? Angenommen, ein Archiv ist knapp 100 MB groß, und hat weit über 500 einzelne Dateien. Käme ich mit meinem Vorhaben in ein Konflikt - bezüglich des Speichers?

Code: Alles auswählen

7# Import zipfile library
import zipfile

# Import hashlib library (md5 method is part of it)
import hashlib

def hash_all_hashes(prompt):
    #print "my prompt", prompt
    total_hash = ""
    for hash_value in prompt:
        total_hash = total_hash + hash_value
        
    sha_512_returned = hashlib.sha512(total_hash).hexdigest()
    return sha_512_returned


def hash_file_content(file_name):
    # Create a empty list for all hashes
    list_hashes = []

    file_path = zipfile.ZipFile(file_name, "r")

    for name in file_path.namelist():
        #print "All files are: ",name
        try:
            # Open,close, read file and calculate SHA512 on its contents
            # Notice: Its should also open the file in binary mode (ex. rb),
            # otherwise it might gets problems 
            with open(name,'rb') as file_to_check:
                data = file_to_check.read()
                # pipe contents of the file through 
                sha_512_returned = hashlib.sha512(data).hexdigest()
                #print "Hashed (", sha_512_returned ,") from content of file", name
                list_hashes.append(sha_512_returned)
        except Exception:
            #print "Exception", Exception
            pass
    #print "list_hashes", list_hashes
    result = hash_all_hashes(list_hashes)
    return result

if __name__ == "__main__":      
    zip_file_name = raw_input("Enter path completly: ")
    my_hashed = hash_file_content(zip_file_name)
    print "my_hashed", my_hashed

Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Sophus: nein, Du verwendest dabei nicht die Dateinamen, sondern den Inhalt des Archivs.
Sei "fn" der Pfad zum zip-Archiv:

Code: Alles auswählen

with open(fn, 'br') as f:
    digest = hashlib.sha512(f.read()).hexdigest()
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo kbr,

deine Variante ist ja um einiges kürzer als meine. Das gefällt mir. Aber eine Sache verstehe ich noch nicht ganz. Der Inhalt eines Archivs besteht ja aus Dateien, richtig? Und somit sind die Dateinamen sozusagen der Inhalt oder? Ich betrachte das so, wenn ich ein Archiv öffne, sehe ich ja noch nicht die Inhalte der jeweiligen Inhalte der Dateien, sondern nur die Dateinamen. Und deswegen ging ich davon aus, dass man dabei die Dateinamen nimmt, und daraus einen Hash-Wert erzeugt. Oder werden bei deiner Variante alle Inhalte der Dateien eingelesen und daraus dann ein Hash-Wert erzeugt?
BlackJack

@Sophus: Die Variante von kbr öffnet kein Archiv sondern irgendeine beliebige Datei und berechnet einen Hash über die Bytes in dieser Datei. Die beliebige Datei kann auch eine Archivdatei sein, aber das ist für das vorhaben ja völlig irrelevant.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo BlackJack,

dann ist die Variante von kbr nicht ganz sicher? Ich meine, in meiner Variante, auch wenn sie nicht perfekt ist, wird jede einzelne Datei eingelesen, dessen Inhalt raus gelesen, daraus einen Wert erstellen, und am Ende aus all den Werten einen Wert erzeugt. Nur war mein Anliegen, dass dieser Weg zu Probleme führen könnte, wenn ein Archiv über 100MB groß ist, und über 200 Dateien beinhaltet. Wäre das möglich?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: was ist Dein Ziel, das Du erreichen willst? Dass eine Datei (sei es nun ein Archiv oder sonstirgendwas) nicht verändert wird, oder dass der Inhalt der Dateien, die irgendwie zu einem Archiv gepackt werden, nicht verändert werden?

Probleme mit dem Speicher bekommst Du nicht, wenn Du die update-Methode benutzt:

Code: Alles auswählen

with open(archivname, 'rb') as data:
    sha = hashlib.sha512()
    for chunk in iter(lambda: data.read(65536), bool):
        sha.update(chunk)
    digest = sha.hexdigest()
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Sirius3,

letzteres. Die Dateien, die zum Archiv gepackt wurden, dürfen nicht verändert werden. Ich dachte dabei sehr stark an eine Art "CheckSum". Mir geht es darum, dass ich am Ende sagen kann, dass ist ein "bekanntes" Archiv, und kein fremdes Archiv.

Dein Vorschlag gefällt mir sehr gut. Werde ich mir gleich mal einstudieren.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Wenn es dir schon um die Dateien und nicht erst um das ganze geht, darfst du nicht erst das Archiv pruefen. Es sei denn auch Dateinamen und die anderen Metadaten sind fuer deine Gleichheit relevant.
BlackJack

@Sophus: Also willst Du doch eigentlich ersteres: Wenn sich das Archiv verändert hat, dann ist da doch der Fall mit enthalten das sich eine Datei in dem Archiv geändert hat. Denn wenn man den Inhalt einer Datei im Archiv verändert, verändert das ja das Archiv selbst. Also warum so umständlich und nicht einfach einen Hash-Wert über die gesamte Archivdatei erstellen?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Das ist eine verdammt gute Frage. Ich ging davon aus, dass die Inhalte der Dateien und das Archiv als Ganzes nicht miteinander zutun haben. Ich betrachte das Archiv als eine Art Container, in der alle Dateien gepackt werden. Ich ging nicht davon aus, sobald der Inhalt einer Datei ändern, dass dies sich auf das Archiv auswirkt. Jedoch habe ich mich gerade mit Sirius3s Variante auseinander gesetzt, und diese scheint nicht zu funktionieren. Die Variante nimmt eine Datei aus dem Archiv vor, und "macht" dann nichts mehr? Es kommt keine Fehlermeldung. Hier der Quelltext:

Code: Alles auswählen

def new_one_digest(prompt):
    
    file_path = zipfile.ZipFile(prompt, "r")

    for name in file_path.namelist():
        print "NAME", name
        try:
            with open(name, 'rb') as data:
                sha = hashlib.sha512()
                for chunk in iter(lambda: data.read(95536), bool):
                    sha.update(chunk)
                digest = sha.hexdigest()
                print "New DIGEST", digest
        except Exception:
            pass
    
if __name__ == "__main__":      
    zip_file_name = raw_input("Enter path completly: ")
    my_hashed_code = new_one_digest(zip_file_name)
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Sophus: Ein Archiv ist eine Datei, wie jede andere auch. Zur Ermittlung des Hashwertes liest Du diese daher einfach als Binärdatei und nicht als Zip-Datei. Dann funktioniert auch das Beispiel von Sirius3.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo kbr,

dann mache ich wirklich was falsch. Hier, wie ich Sirius3s Variante anwende:

Code: Alles auswählen

def new_one_digest(prompt):
    with open(prompt, 'rb') as data:
        sha = hashlib.sha512()
        for chunk in iter(lambda: data.read(65536), bool):
            sha.update(chunk)
        digest = sha.hexdigest()

    return digest
    
if __name__ == "__main__":      
    zip_file_name = raw_input("Enter path completly: ") # Enter path completly with file name with file extension!
    my_hashed_code = new_one_digest(zip_file_name)
    print "my hashed code: ", my_hashed_code
BlackJack

Sophus: Wie kann ein Archiv nichts mit dem Inhalt der Dateien zu tun haben die in dem Archiv stecken‽ Das ist doch wohl offensichtlich das sich das Archiv ändert wenn man eine Datei darin ändert, das Archiv besteht doch aus den Daten der Dateien die darin gespeichert sind + Metadaten die sonst im Dateisystem gespeichert sind.

Das letzte Argument von `iter()` ist falsch, das muss anstelle von `bool` eine leere Zeichenkette sein.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Nun, ich habe mir mal den Spaß erlaubt, und bei Sirius3s Variante überall eine Print-Anweisung anzusetzen, um mitverfolgen zu können, wie das Skript arbeitet. Mir fiel sehr stark auf, dass das Skript äußerst langsam arbeitet. Dabei ist meine Zip-Datei derzeit nur 34 MB groß. Ich vermute mal, dass Sirius3s Variante sehr stark ausgebremst wird?

Code: Alles auswählen

def new_one_digest(prompt):
    print "Starting to hash"
    print "Path", prompt
    with open(prompt, 'rb') as data:
        print "data", data
        sha = hashlib.sha512()
        for chunk in iter(lambda: data.read(65536), bool):
            print "chunk", chunk
            sha.update(chunk)
        digest = sha.hexdigest()

    return digest

Also bleibt mir nichts anderes übrig, als diese Variante zu nehmen?

Code: Alles auswählen

def hash_zip(prompt):

    with open(prompt, 'rb') as f:
        digest = hashlib.sha512(f.read()).hexdigest()
        print "digest", digest
        return digest
Denn so wie BlackJack und kbr es mir erklärt haben, verändert sich das Archiv ebenfalls, sobald sich eine Datei im Archiv ändert.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du hast *immer noch* einen Fehler in deiner Schleife, bei der du "bool" statt "''" uebergibst. Damit ist deine Abbruchbedingung falsch, und das fuehrt dann zu einer kleinen Endlosschleife - die dauert tatsaechlich etwas laenger.

Denn ansonsten sind die beiden Ansaetze gleich & es wuerde mich auch sehr wundern, wenn die Performance da gross unterschiedlich ist.

Was dass dann wiederum mit deiner Schlussfolgerung am Ende zu tun hat, weiss ich auch nicht...
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Um dieses letzte Argument in der iter()-Methode zu verstehen, versuche ich das mal nachzuvollziehen.

Code: Alles auswählen

for chunk in iter(lambda: data.read(65536), bool):
Innerhalb des Iteratorobjektes wird eine anonyme Funktion (Lambda) erzeugt. Hier hat lambda keine Argumentationsliste, die vor dem Doppelpunkt stehen. Anschließend folgt ein Ausdruck, und zwar, dass das Objekt data (hier: Zip-Datei) gelesen wird. In der read()Methode wird dann die Anzahl der Bytes übergeben, die gelesen werden sollen. Und in bool können nun zwei Werte, einmal True oder False enthalten sein. Eigentlich dient das letzte Argument der iter()-Methode dazu, um diese For-Schleife bei einer bestimmten Bedingung abzubrechen.

Habe ich das alles so richtig verstanden?
BlackJack

@Sophus: Das letzte Argument ist in dem gezeigten Quelltext der Typ `bool`, der kann nicht zwei Werte enthalten der ist selber *ein* Wert. Und zwar, wie schon gesagt, der *falsche* Wert. Richtig wäre eine leere Zeichenkette beziehungsweise Bytekette.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sophus hat geschrieben:Um dieses letzte Argument in der iter()-Methode zu verstehen, versuche ich das mal nachzuvollziehen.
Ein alternativer Ansatz: Dokumentation lesen:

Code: Alles auswählen

$ pydoc iter
Help on built-in function iter in module __builtin__:

iter(...)
    iter(collection) -> iterator
    iter(callable, sentinel) -> iterator

    Get an iterator from an object.  In the first form, the argument must
    supply its own iterator, or be a sequence.
    In the second form, the callable is called until it returns the sentinel.
(END)
Antworten