Passwort vor dem Entschlüsseln mit Crypto überprüfen

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 nachfolgenden, ausführbaren Quelltext möchte ich eine Datei verschlüsseln und wieder entschlüsseln. Ich benutze hierbei die Standard-Bibliothek Crypto, die quasi in Python 2.7.8 mitgeliefert wird. Soweit funktioniert alles. Die Datei wird ordnungsgemäß verschlüsselt und auch wieder entschlüsselt.

ABER: Wie kann ich überprüfen, ob der Benutzer ein falsches Passwort eingegeben hat? Ich habe mal das Spiel gespielt und einfach ein falsches Passwort beim Entschlüsseln eingegeben. Ich bekam keinerlei Fehlermeldung. Die Entschlüsselung wurde durchgeführt. Beim Versuch, die mit falschem Passwort entschlüsselte Datei zu öffnen, schlug fehl. Logisch. Die verschlüsselte Datei war nach dem Entschlüsselungs-Vorgang gelöscht. Das heißt jetzt nun, wenn der Benutzer sein Passwort falsch eingibt, dann kann er zu seiner Datei "Auf Wiedersehen" sagen.

Wie kann ich eine Art Exception abfangen und vorher überprüfen? Ich möchte es vermeiden, dass ich das Passwort vom Benutzer irgendwo auf seiner Festplatte dauerhaft speichere. Diese Variante erscheint mir sehr risikoreich.

Code: Alles auswählen

from Crypto.Hash import SHA256
from Crypto.Cipher import AES
import os, random, sys

from os import path

def encrypt(key, filename, outFile):
    chunksize = 64 * 1024
    filesize = str(os.path.getsize(filename)).zfill(16)
    IV = ''

    for i in range(16):
            IV += chr(random.randint(0, 0xFF))
    
    encryptor = AES.new(key, AES.MODE_CBC, IV)

    with open(filename, "rb") as infile:
        with open(outFile, "wb") as outfile:
            outfile.write(filesize)
            outfile.write(IV)
            while True:
                    chunk = infile.read(chunksize)
                    
                    if len(chunk) == 0:
                            break

                    elif len(chunk) % 16 !=0:
                            chunk += ' ' *  (16 - (len(chunk) % 16))

                    outfile.write(encryptor.encrypt(chunk))

def decrypt(key, filename, outFile):
    chunksize = 64 * 1024
   
    with open(filename, "rb") as infile:
            filesize = infile.read(16)
            IV = infile.read(16)

            decryptor = AES.new(key, AES.MODE_CBC, IV)
            
            with open(outFile, "wb") as outfile:
                    while True:
                            chunk = infile.read(chunksize)
                            if len(chunk) == 0:
                                    break

                            outfile.write(decryptor.decrypt(chunk))

                    outfile.truncate(int(filesize))

BASE_PATH = path.dirname(path.abspath(__file__))

choice = raw_input("Do you want to (E)ncrypt or (D)ecrypt? ")
encFiles = os.path.join(BASE_PATH, 'your_file_name') 

typed_out_file = raw_input('Enter out file_name: ')
outFile = os.path.join(BASE_PATH, typed_out_file) 
password = raw_input("Enter the password: ")  
 

if choice == "E":
    
    if os.path.basename(encFiles).startswith("(encrypted)"):
            print "%s is already encrypted" %str(Tfiles)
            pass

    elif encFiles == os.path.join(os.getcwd(), sys.argv[0]):
            pass 
    else:
            encrypt(SHA256.new(password).digest(), str(encFiles), outFile)
            print "Done encrypting %s" %str(encFiles)
            os.remove(encFiles)


elif choice == "D":
    
    filename = raw_input("Enter the filename to decrypt: ")
    out_file = raw_input("Enter the new filename: ")

    decrypt(SHA256.new(password).digest(), filename, out_file)
    print "Done decrypting %s" %filename
    os.remove(filename)

else:
	print "Please choose a valid command."
	sys.exit()
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die einfache Antwort ist "geht nicht".

Grundsaetzlich erstmal ist es nicht moeglich mit Bestimmtheit zu entscheiden, ob eine Datei, die durch eine Entschluesselung gelaufen ist, das gewuenschte Ergebnis darstellt. Denn was waere denn, wenn mein gewuenschter Inhalt ein randomisiertes one-time-pad ist? Das ist dann egal wie das Passwort aussieht immer zufaelliger Gibberish.

Praktisch kann man natuerlich ein bisschen tricksen, und einfach beim Verschluesseln einen Header (zB den String "MEINSUPERDUPERVERSCHLUESSELPROGRAMMGEILSTEVONWELT") voranstellen. Wenn der dann beim entschluesseln *nicht* da ist, war wohl das Passwort falsch. Kann man auch schon nach dem entschluesseln des ersten Blocks feststellen.

Theoretisch ist es natuerlich moeglich, dass eine Datei mit einem dazu passenden Passwort *genau diesen* Header auch ausspuckt, obwohl sie eigentlich nicht verschluesselt war. Aber das ist schon galaktische Lotterie verdaechtiges Pech, wenn so etwas passieren sollte.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@__deets__:

Das mit dem Header klingt schon einmal interessant. Wie sähe sowas in einem Beispiel aus? Gibt es dazu Beispiele?

Beim dauerhaften Abspeichern des Passwortes kam ich auf die Idee, ob es auch sicher wäre, dass "gehashte" Passwort zu speichern?

Zum Beispiel so:

Code: Alles auswählen

print "Hashing password"
given_pwd = 'abc'
hashed_pwd = pbkdf2_sha512.encrypt(given_pwd, rounds=200000, salt_size=16)
print "Hashed password", hashed_pwd
print "Result of verifying password", pbkdf2_sha512.verify(given_pwd, hashed_pwd)
Frage ist nur, wie sicher diese Variante ist? Ich meine, diese Variante erscheint mir sehr verlockend, weil man hierbei nur eine Text-Datei auslesen muss, und dann den ausgelesenen Hash-Wert mit dem aktuell eingegebenen Passwort vergleichen muss. Aber diese Variante besagt ja auch, dass die Datei immer in der "Nähe" sein muss. Angenommen, der Benutzer lässt seine Ultra_ach_so_wichtige-Datei verschlüsseln und will sie seinem Freund weitergeben. Der Freund muss dann zusätzlich die Datei bekommen, in welcher der Hash-Wert des Passwortes ist.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: dass Problem, dass die originale Datei weg ist, liegt in Zeile 82. Ernsthaft? Du importierst SHA256 und kommst nicht drauf, wie man die Korrektheit der entschlüsselten Daten prüfen könnte?
Der Initialisierungsvektor wird üblicherweise soerzeugt: ›iv = Random.new().read(AES.block_size)‹.
Deine Einrückungen sind kaputt. Es wird immer mit 4 Leerzeichen pro Ebene eingerückt. Du hältst Dich nicht an die Namenskonvention zur Variablenschreibweise. outFile und outfile sind wirklich schwer zu unterscheiden. Da sind zu viele Leerzeilen und pass-Anweisungen. Was das mit encFiles soll, versteh ich nicht. Dateinamen, die man irgendwo eingibt, will man normalerweise relativ von dem Verzeichnis aus, wo man das Programm gestartet hat und nicht relativ zu dem Verzeichnis, wo das Programm liegt.
BlackJack

@Sophus: Wenn man sich um Sicherheit Gedanken macht, dann nimmt man ein gängiges, gutes Programm zum Verschlüsseln, wie GPG, und bastelt sich keine Verschlüsselung selbst. Insbesondere wenn man keine Ahnung vom Thema hat, sich also umfassend damit beschäftigt hat.

Zur Frage im Betreff: Man macht das grundsätzlich nicht das Passwort *vor* dem Entschlüsseln zu prüfen, denn dann hängt die Sicherheit ja gar nicht mehr an der Verschlüsselung selbst. Die kann noch so gut, ja sogar perfekt sein, wenn das denn möglich wäre: Wenn man den Passwortvergleich knacken kann, dann ist das alles egal.

Entweder prüft man das gar nicht, der Benutzer wird das dann schon merken wenn die Daten keinen Sinn machen. Oder man berechnet eine Prüfsumme oder signiert die Daten vor dem Verschlüsseln und verschlüsselt Prüfsumme oder Signatur mit den Daten. Nach dem entschlüsseln kann man dann Prüfsumme und/oder Signatur prüfen, und wenn das nicht stimmt, war wohl das Passwort falsch.

Und egal was man macht: man löscht nicht einfach so die Ausgangsdatei. Das ist sehr, äh, unglücklich…
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3:

Das die Datei hinterher gelöscht werden soll, ist schon richtig. Nach meiner Auffassung braucht man die verschlüsselte Datei hinterher nicht, wenn alles korrekt entschlüsselt wurde. Wozu auch? Aber zu deinem Hinweis. Wie meinst du das? Die Korrektheit der entschlüsselten Daten prüfen? In meinem Beispiel wird ja das Passwort in ein SHA256-Wert gesetzt, und diese dann übergeben.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

ich habe noch einmal über Sirius3s und BlackJacks Antwort nachgedacht. Vor diesem Hintergrund habe ich die hashlib-Bibliothek hinzugezogen. Dann habe ich mir eine weitere Funktion eingebaut: sha256_checksum(). Beim Verschlüsseln wird vorher Check-Summe (SHA256) ausgelesen. Denn es ist ja wichtig zu wissen, wie die Datei vorher war. Diese Summe wird in dem unteren Beispiel in die hashed_chksum-Variable gespeichert. Und beim Entschlüsseln wird nach der Entschlüsselung der SHA256-Wert der Datei berechnet. Diese beiden, also der vorher in die Variable gespeicherter Wert und die nach der Entschlüsselung erzeugte Wert wird miteinander verglichen. Und wenn es Probleme gibt, muss etwas gemacht werden.

ABER: in der Realität bedeutet das, dass das Programm den SHA256-Wert, den er vor der Verschlüsselung berechnet hat, irgendwo speichern muss. In eine Datei. Denn mit diesem gespeicherten Wert soll ja später, im Zuge der Entschlüsselung, ein Vergleich angestellt werden. Das heißt hier, wenn Benutzer A seine verschlüsselte Datei weitergeben will, muss dieser dem Benutzer B auch die Datei mitgeben, in welcher der SHA256-Wert steckt. Ist dies keine Schwachstelle? Also so allgemein gefragt.

Code: Alles auswählen

from Crypto.Hash import SHA256
from Crypto.Cipher import AES
import os, random, sys

from os import path
import hashlib

hashed_chksum = None

BASE_PATH = path.dirname(path.abspath(__file__))

def sha256_checksum(filename, chunksize=65536):
    sha256 = hashlib.sha256()
    with open(filename, 'rb') as f:
        for block in iter(lambda: f.read(chunksize), b''):
            sha256.update(block)
    return sha256.hexdigest()

def encrypt(key, filename, outFile):
    hashed_chksum = sha256_checksum(filename)
    
    chunksize = 64 * 1024
    filesize = str(os.path.getsize(filename)).zfill(16)
    IV = ''

    for i in range(16):
            IV += chr(random.randint(0, 0xFF))
    
    encryptor = AES.new(key, AES.MODE_CBC, IV)

    with open(filename, "rb") as infile:
        with open(outFile, "wb") as outfile:
            outfile.write(filesize)
            outfile.write(IV)
            while True:
                    chunk = infile.read(chunksize)
                    
                    if len(chunk) == 0:
                            break

                    elif len(chunk) % 16 !=0:
                            chunk += ' ' *  (16 - (len(chunk) % 16))

                    outfile.write(encryptor.encrypt(chunk))

def decrypt(key, filename, outFile):
    chunksize = 64 * 1024
   
    with open(filename, "rb") as infile:
            filesize = infile.read(16)
            IV = infile.read(16)

            decryptor = AES.new(key, AES.MODE_CBC, IV)
            
            with open(outFile, "wb") as outfile:
                    while True:
                            chunk = infile.read(chunksize)
                            if len(chunk) == 0:
                                    break

                            outfile.write(decryptor.decrypt(chunk))

                    outfile.truncate(int(filesize))
                    
    return sha256_checksum(outFile)

while True:
    choice = raw_input("Do you want to (E)ncrypt or (D)ecrypt? ")
    encFiles = os.path.join(BASE_PATH, 'Islam_Theologie_Glauben.doc') 

    typed_out_file = raw_input('Enter out file_name: ')
    outFile = os.path.join(BASE_PATH, typed_out_file)
    
    password = raw_input("Enter the password: ")  
     

    if choice == "E":
        
        if os.path.basename(encFiles).startswith("(encrypted)"):
                print "%s is already encrypted" %str(Tfiles)
                pass

        elif encFiles == os.path.join(os.getcwd(), sys.argv[0]):
                pass 
        else:
                encrypt(SHA256.new(password).digest(), str(encFiles), outFile)
                print "Done encrypting %s" %str(encFiles)


    elif choice == "D":
        
        filename = raw_input("Enter the filename to decrypt: ")
        out_file = raw_input("Enter the new filename: ")

        result_decrypt = decrypt(SHA256.new(password).digest(), filename, out_file)
        if result_decrypt == hashed_chksum:
            print "Done decrypting {} successfully".format(filename)
        else:
            print "Something goes wrong"

    else:
            print "Please choose a valid command."
            sys.exit()
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: wie BlackJack schon geschrieben hat, hängt man einfach die Checksumme ans Ende der Datei mit an. Das Berechnen muß man auch nicht in einer eigenen Schleife machen, sondern kann das in der gleichen Schleife wie die Verschlüsselung erledigen.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

__deets__ hat geschrieben: Praktisch kann man natuerlich ein bisschen tricksen, und einfach beim Verschluesseln einen Header (zB den String "MEINSUPERDUPERVERSCHLUESSELPROGRAMMGEILSTEVONWELT") voranstellen. Wenn der dann beim entschluesseln *nicht* da ist, war wohl das Passwort falsch. Kann man auch schon nach dem entschluesseln des ersten Blocks feststellen.
Das ist glaub ich keine so gute Idee. Verschlüsselung wird sehr schnell problematisch wenn ein Angreifer den Inhalt erraten kann.

Wesentlich besser ist da der Checksum Ansatz, wo am Ende aber innerhalb des verschlüsselten Teils der Datei ein Hash steht. Ein brauchbarer Hash besteht schließlich aus zufälligen Zahlen.
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

@dasich guter Hinweis!
olman
User
Beiträge: 2
Registriert: Donnerstag 23. März 2017, 20:43

@Sophus

In deinem ersten Beitrag hast du geschrieben:
Ich habe mal das Spiel gespielt und einfach ein falsches Passwort beim Entschlüsseln eingegeben. Ich bekam keinerlei Fehlermeldung. Die Entschlüsselung wurde durchgeführt.
Ich konnte mir einfach nicht vorstellen, dass von Python eine Verschlüsselung mit Passwort-Schutz angeboten wird und der Passwort-Schutz dann nicht funktioniert. Da mich das Thema auch interessiert habe ich mal im Internet gesucht und ein kleines Skript gefunden, dass auch Crypto verwendet. Und da funktioniert der Passwortschutz einwandfrei. Bei Eingabe eines falschen Passwort wird nicht entschlüsselt. Ich habe das Skript nur geringfügig geändert und vor der Entschlüsselung eine Passwortabfrage eingebaut.

Code: Alles auswählen

from Crypto.Cipher import AES
import base64

# Text verschluesseln mit Password
msg_text = 'Meine geheime Nachricht'.rjust(32)
encode_key = '1234567890123456'            
cipher = AES.new(encode_key,AES.MODE_ECB) 
encoded = base64.b64encode(cipher.encrypt(msg_text))

# Text mit Passwordeingabe entschluesseln
decode_key = raw_input("Enter the password: ")
cipher = AES.new(decode_key,AES.MODE_ECB)
decoded = cipher.decrypt(base64.b64decode(encoded))
print decoded.strip()
print len(decoded)

Die Verschlüsselung ist zwar etwas anders als bei dir, aber der Passwortschutz sollte eigentlich identisch sein. Die letzte Zeile - print len(decoded) - habe ich nur eingefügt, weil bei falschem Passwort in IDLE gar nichts ausgedruckt wurde. In Spyder werden allerdings kryptische Zeichen ausgedruckt.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@olman: Danke für deine Antwort. Ich werde den Vorschlag von BlackJack und Sirius3 annehmen. Ich ermittel vor der Verschlüsselung die Prüfsumme der Datei, und hänge diese dann der verschlüsselten Datei am Ende an. So dass ich beim Entschlüsseln die Verschlüsselte Datei nur am Ende auslesen brauche, die dort hinterlegte Prüfsumme entnehme, dann die Datei wie gewöhnlich entschlüssle, und dann von der entschlüsselten Datei eine Prüfsumme berechne, und diese dann mit der vorherige Prüfsumme, die ich aus der Datei entnommen habe, miteinander vergleiche.
BlackJack

@olman: Python bietet gar keine Verschlüsselung an, weder mit noch ohne Passwort (wenn man mal rot13 aussen vor lässt :-)). Auch das externe `Crypto`-Modul tut das AFAIK nicht. Das liefert nur Bausteine mit der sich Leute die keine Ahnung von Kryptographie haben in den Fuss schiessen können, und Leute die Ahnung haben sich vielleicht etwas sinnvolles selbst basteln können.

Wobei auch beim ersten Beispiel der Passwort-Schutz funktioniert. Ohne das richtige Passwort sind die verschlüsselten Daten geschützt. Oder was verstehst Du unter einem funktionierenden Passwort-Schutz‽ Ich sehe jedenfalls keinen Unterschied zu Deinem Programm.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Wobei das Wort "Ent-/Verschlüsseln" sicherlich falsch wäre. Ich sehe das gerade in meinem Betreff. Denn am Ende wird ja nur ein Hash-Wert (hier SHA256) erzeugt. Oder irre ich mich da? Mir fällt nur auf, dass viele in Bezug auf SHA oder MD5 zu schnell von "Verschlüsseln" reden.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Wo wir beim in Fuß schießen sind: ECB ist keine gute Idee, da kann man es auch ganz sein lassen.

Was die Passwortfrage angeht sollte man auch Bedenken dass "sicher" nur heißt dass das Entschlüsseln zu lange dauert um praktikabel zu sein. Damit dies so bleibt muss jeder Ansatz der das Passwort überprüft mindestens so lange dauern wie das entschlüsseln. Deswegen prüft man am besten nicht das Passwort sondern ob das Entschlüsseln geklappt hat.
Antworten