Wie AES verschlüsseln / entschlüsseln Dateien mit Python / PyCrypto in einer OpenSSL-kompatiblen Weise?

Es gibt viele Möglichkeiten, AES zu nutzen, was zu häufigen Inkompatibilitäten zwischen verschiedenen Implementierungen führt. OpenSSL bietet eine beliebte (aber unsichere – siehe unten!) Befehlszeilenschnittstelle für AES-Verschlüsselung / Entschlüsselung:

openssl aes-256-cbc -salt -in filename -out filename.enc openssl aes-256-cbc -d -in filename.enc -out filename 

Python hat Unterstützung für AES in Form des PyCrypto-Pakets, aber es gibt nur die Werkzeuge. Wie benutzt man Python / PyCrypto, um Dateien so zu verschlüsseln, dass OpenSSL verwendet werden kann, um sie zu entschlüsseln und Dateien zu entschlüsseln, die mit OpenSSL verschlüsselt wurden?

WARNUNG

DIESES VERSCHIEDENES SCHEMA IST SCHWACH. ES BIETET NICHT EIGENSCHAFTLICHE SICHERHEIT. NICHT VERWENDEN SIE NICHT, WENN SIE NICHT ZUR BACKWARD-KOMPATIBILITÄT SELBST HABEN.

4 Solutions collect form web for “Wie AES verschlüsseln / entschlüsseln Dateien mit Python / PyCrypto in einer OpenSSL-kompatiblen Weise?”

Angesichts der Beliebtheit von Python war ich zunächst enttäuscht, dass es keine vollständige Antwort auf diese Frage gab. Es hat mir eine Menge von verschiedenen Antworten auf diesem Board, sowie andere Ressourcen, um es richtig zu bekommen. Ich dachte, ich könnte das Ergebnis für zukünftige Referenz teilen und vielleicht überprüfen; Ich bin kein Kryptographie-Experte! Der untenstehende Code scheint jedoch nahtlos zu funktionieren:

 from hashlib import md5 from Crypto.Cipher import AES from Crypto import Random def derive_key_and_iv(password, salt, key_length, iv_length): d = d_i = '' while len(d) < key_length + iv_length: d_i = md5(d_i + password + salt).digest() d += d_i return d[:key_length], d[key_length:key_length+iv_length] def encrypt(in_file, out_file, password, key_length=32): bs = AES.block_size salt = Random.new().read(bs - len('Salted__')) key, iv = derive_key_and_iv(password, salt, key_length, bs) cipher = AES.new(key, AES.MODE_CBC, iv) out_file.write('Salted__' + salt) finished = False while not finished: chunk = in_file.read(1024 * bs) if len(chunk) == 0 or len(chunk) % bs != 0: padding_length = (bs - len(chunk) % bs) or bs chunk += padding_length * chr(padding_length) finished = True out_file.write(cipher.encrypt(chunk)) def decrypt(in_file, out_file, password, key_length=32): bs = AES.block_size salt = in_file.read(bs)[len('Salted__'):] key, iv = derive_key_and_iv(password, salt, key_length, bs) cipher = AES.new(key, AES.MODE_CBC, iv) next_chunk = '' finished = False while not finished: chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs)) if len(next_chunk) == 0: padding_length = ord(chunk[-1]) chunk = chunk[:-padding_length] finished = True out_file.write(chunk) 

Verwendung:

 with open(in_filename, 'rb') as in_file, open(out_filename, 'wb') as out_file: encrypt(in_file, out_file, password) with open(in_filename, 'rb') as in_file, open(out_filename, 'wb') as out_file: decrypt(in_file, out_file, password) 

Wenn Sie eine Chance sehen, dies zu verbessern oder zu verlängern, um flexibler zu sein (zB machen Sie es ohne Salz, oder bieten Python 3 Kompatibilität), fühlen Sie sich bitte frei, dies zu tun.

WARNUNG

DIESES VERSCHIEDENES SCHEMA IST SCHWACH. ES BIETET NICHT EIGENSCHAFTLICHE SICHERHEIT. NICHT VERWENDEN SIE NICHT, WENN SIE NICHT ZUR BACKWARD-KOMPATIBILITÄT SELBST HABEN.

Ich melde den Code mit ein paar Korrekturen (ich wollte deine Version nicht verdecken). Während dein Code funktioniert, erkennt man keine Fehler beim Auffüllen. Insbesondere wenn der Entschlüsselungsschlüssel falsch ist, kann Ihre Polsterlogik etwas Ungewöhnliches tun. Wenn Sie mit meiner Änderung einverstanden sind, können Sie Ihre Lösung aktualisieren.

 from hashlib import md5 from Crypto.Cipher import AES from Crypto import Random def derive_key_and_iv(password, salt, key_length, iv_length): d = d_i = '' while len(d) < key_length + iv_length: d_i = md5(d_i + password + salt).digest() d += d_i return d[:key_length], d[key_length:key_length+iv_length] def encrypt(in_file, out_file, password, key_length=32): bs = AES.block_size salt = Random.new().read(bs - len('Salted__')) key, iv = derive_key_and_iv(password, salt, key_length, bs) cipher = AES.new(key, AES.MODE_CBC, iv) out_file.write('Salted__' + salt) finished = False while not finished: chunk = in_file.read(1024 * bs) if len(chunk) == 0 or len(chunk) % bs != 0: padding_length = bs - (len(chunk) % bs) chunk += padding_length * chr(padding_length) finished = True out_file.write(cipher.encrypt(chunk)) def decrypt(in_file, out_file, password, key_length=32): bs = AES.block_size salt = in_file.read(bs)[len('Salted__'):] key, iv = derive_key_and_iv(password, salt, key_length, bs) cipher = AES.new(key, AES.MODE_CBC, iv) next_chunk = '' finished = False while not finished: chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs)) if len(next_chunk) == 0: padding_length = ord(chunk[-1]) if padding_length < 1 or padding_length > bs: raise ValueError("bad decrypt pad (%d)" % padding_length) # all the pad-bytes must be the same if chunk[-padding_length:] != (padding_length * chr(padding_length)): # this is similar to the bad decrypt:evp_enc.c from openssl program raise ValueError("bad decrypt") chunk = chunk[:-padding_length] finished = True out_file.write(chunk) 

Der unten stehende Code sollte Python 3 sein, der mit den im Code dokumentierten kleinen Änderungen kompatibel ist. Wollte auch os.urandom anstelle von Crypto.Random verwenden. 'Salted__' wird ersetzt durch salt_header, die maßgeschneidert oder leer gelassen werden kann, wenn nötig.

 from os import urandom from hashlib import md5 from Crypto.Cipher import AES def derive_key_and_iv(password, salt, key_length, iv_length): d = d_i = b'' # changed '' to b'' while len(d) < key_length + iv_length: # changed password to str.encode(password) d_i = md5(d_i + str.encode(password) + salt).digest() d += d_i return d[:key_length], d[key_length:key_length+iv_length] def encrypt(in_file, out_file, password, salt_header='', key_length=32): # added salt_header='' bs = AES.block_size # replaced Crypt.Random with os.urandom salt = urandom(bs - len(salt_header)) key, iv = derive_key_and_iv(password, salt, key_length, bs) cipher = AES.new(key, AES.MODE_CBC, iv) # changed 'Salted__' to str.encode(salt_header) out_file.write(str.encode(salt_header) + salt) finished = False while not finished: chunk = in_file.read(1024 * bs) if len(chunk) == 0 or len(chunk) % bs != 0: padding_length = (bs - len(chunk) % bs) or bs # changed right side to str.encode(...) chunk += str.encode( padding_length * chr(padding_length)) finished = True out_file.write(cipher.encrypt(chunk)) def decrypt(in_file, out_file, password, salt_header='', key_length=32): # added salt_header='' bs = AES.block_size # changed 'Salted__' to salt_header salt = in_file.read(bs)[len(salt_header):] key, iv = derive_key_and_iv(password, salt, key_length, bs) cipher = AES.new(key, AES.MODE_CBC, iv) next_chunk = '' finished = False while not finished: chunk, next_chunk = next_chunk, cipher.decrypt( in_file.read(1024 * bs)) if len(next_chunk) == 0: padding_length = chunk[-1] # removed ord(...) as unnecessary chunk = chunk[:-padding_length] finished = True out_file.write(bytes(x for x in chunk)) # changed chunk to bytes(...) 

Ich weiß, das ist ein bisschen spät, aber hier ist eine Lösung, die ich im Jahr 2013 darüber nachgedacht habe, wie man das Python pycrypto Paket benutzt, um in einer openssl kompatiblen Weise zu verschlüsseln / entschlüsseln zu können. Es wurde auf python2.7 und python3.x getestet. Der Quellcode und ein Testskript finden Sie hier .

Einer der wichtigsten Unterschiede zwischen dieser Lösung und den hervorragenden Lösungen, die oben dargestellt wurden, ist, dass sie zwischen Pipe und File I / O unterscheidet, die bei einigen Anwendungen Probleme verursachen können.

Die wichtigsten Funktionen aus diesem Blog sind unten gezeigt.

 # ================================================================ # get_key_and_iv # ================================================================ def get_key_and_iv(password, salt, klen=32, ilen=16, msgdgst='md5'): ''' Derive the key and the IV from the given password and salt. This is a niftier implementation than my direct transliteration of the C++ code although I modified to support different digests. CITATION: http://stackoverflow.com/questions/13907841/implement-openssl-aes-encryption-in-python @param password The password to use as the seed. @param salt The salt. @param klen The key length. @param ilen The initialization vector length. @param msgdgst The message digest algorithm to use. ''' # equivalent to: # from hashlib import <mdi> as mdf # from hashlib import md5 as mdf # from hashlib import sha512 as mdf mdf = getattr(__import__('hashlib', fromlist=[msgdgst]), msgdgst) password = password.encode('ascii', 'ignore') # convert to ASCII try: maxlen = klen + ilen keyiv = mdf(password + salt).digest() tmp = [keyiv] while len(tmp) < maxlen: tmp.append( mdf(tmp[-1] + password + salt).digest() ) keyiv += tmp[-1] # append the last byte key = keyiv[:klen] iv = keyiv[klen:klen+ilen] return key, iv except UnicodeDecodeError: return None, None # ================================================================ # encrypt # ================================================================ def encrypt(password, plaintext, chunkit=True, msgdgst='md5'): ''' Encrypt the plaintext using the password using an openssl compatible encryption algorithm. It is the same as creating a file with plaintext contents and running openssl like this: $ cat plaintext <plaintext> $ openssl enc -e -aes-256-cbc -base64 -salt \\ -pass pass:<password> -n plaintext @param password The password. @param plaintext The plaintext to encrypt. @param chunkit Flag that tells encrypt to split the ciphertext into 64 character (MIME encoded) lines. This does not affect the decrypt operation. @param msgdgst The message digest algorithm. ''' salt = os.urandom(8) key, iv = get_key_and_iv(password, salt, msgdgst=msgdgst) if key is None: return None # PKCS#7 padding padding_len = 16 - (len(plaintext) % 16) if isinstance(plaintext, str): padded_plaintext = plaintext + (chr(padding_len) * padding_len) else: # assume bytes padded_plaintext = plaintext + (bytearray([padding_len] * padding_len)) # Encrypt cipher = AES.new(key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(padded_plaintext) # Make openssl compatible. # I first discovered this when I wrote the C++ Cipher class. # CITATION: http://projects.joelinoff.com/cipher-1.1/doxydocs/html/ openssl_ciphertext = b'Salted__' + salt + ciphertext b64 = base64.b64encode(openssl_ciphertext) if not chunkit: return b64 LINELEN = 64 chunk = lambda s: b'\n'.join(s[i:min(i+LINELEN, len(s))] for i in range(0, len(s), LINELEN)) return chunk(b64) # ================================================================ # decrypt # ================================================================ def decrypt(password, ciphertext, msgdgst='md5'): ''' Decrypt the ciphertext using the password using an openssl compatible decryption algorithm. It is the same as creating a file with ciphertext contents and running openssl like this: $ cat ciphertext # ENCRYPTED <ciphertext> $ egrep -v '^#|^$' | \\ openssl enc -d -aes-256-cbc -base64 -salt -pass pass:<password> -in ciphertext @param password The password. @param ciphertext The ciphertext to decrypt. @param msgdgst The message digest algorithm. @returns the decrypted data. ''' # unfilter -- ignore blank lines and comments if isinstance(ciphertext, str): filtered = '' nl = '\n' re1 = r'^\s*$' re2 = r'^\s*#' else: filtered = b'' nl = b'\n' re1 = b'^\\s*$' re2 = b'^\\s*#' for line in ciphertext.split(nl): line = line.strip() if re.search(re1,line) or re.search(re2, line): continue filtered += line + nl # Base64 decode raw = base64.b64decode(filtered) assert(raw[:8] == b'Salted__' ) salt = raw[8:16] # get the salt # Now create the key and iv. key, iv = get_key_and_iv(password, salt, msgdgst=msgdgst) if key is None: return None # The original ciphertext ciphertext = raw[16:] # Decrypt cipher = AES.new(key, AES.MODE_CBC, iv) padded_plaintext = cipher.decrypt(ciphertext) if isinstance(padded_plaintext, str): padding_len = ord(padded_plaintext[-1]) else: padding_len = padded_plaintext[-1] plaintext = padded_plaintext[:-padding_len] return plaintext 
  • Verwenden Sie RSA, um in JavaScript zu verschlüsseln und in Python3 zu entschlüsseln
  • Entschlüsseln Sie SHA1 mit (Passwort) in Python
  • Ist es möglich, einen 100% sicheren virtuellen privaten Server zu haben?
  • Sicher verschlüsseln Sie ganzzahlige (bis zu 2 ^ 48) in den kürzesten URL-sicheren String
  • Obfuscate Strings in Python
  • XOR Python Text Verschlüsselung / Entschlüsselung
  • Alte Python Hashing von links nach rechts - warum ist es schlecht?
  • Fügen Sie Nonce und Block zählen in PyCrypto AES MODE_CTR
  • AES-Verschlüsselung in Node.js, um die erwartete Entschlüsselung in Python zu erfüllen
  • Python - GPG entschlüsselt funktioniert aber GNUPG nicht
  • Wie würde ich ein einfaches Verschlüsselungs- / Entschlüsselungsprogramm machen? [abgeschlossen]
  • Python ist die beste Programmiersprache der Welt.