¿Cómo descifrar archivos encriptados AES de OpenSSL en Python?

OpenSSL proporciona una interfaz de línea de comandos popular (pero insegura, ¡vea abajo!) Para el cifrado AES:

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

Python admite AES en la forma del paquete PyCrypto, pero solo proporciona las herramientas. ¿Cómo usar Python / PyCrypto para descifrar archivos que han sido cifrados usando OpenSSL?

darse cuenta

Esta pregunta solía referirse también al cifrado en Python usando el mismo esquema. Desde entonces he eliminado esa parte para desalentar a cualquiera de usarla. NO cifre más datos de esta manera, ya que NO está seguro para los estándares de hoy. SOLO debe usar el descifrado, por ninguna otra razón que no sea COMPATIBILIDAD ATRÁS, es decir, cuando no tenga otra opción. ¿Quieres cifrar? Use NaCl / libsodium si es posible.

Dada la popularidad de Python, al principio me decepcionó que no se encontrara una respuesta completa a esta pregunta. Me tomó bastante leer diferentes respuestas en este foro, así como otros recursos, para hacerlo bien. Pensé que podría compartir el resultado para futuras referencias y tal vez revisión; ¡De ninguna manera soy un experto en criptografía! Sin embargo, el siguiente código parece funcionar perfectamente:

 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 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) 

Uso:

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

Si ve la oportunidad de mejorar esto o ampliarlo para que sea más flexible (por ejemplo, haga que funcione sin sal, o proporcione compatibilidad con Python 3), no dude en hacerlo.

darse cuenta

Esta respuesta solía referirse también al cifrado en Python utilizando el mismo esquema. Desde entonces he eliminado esa parte para desalentar a cualquiera de usarla. NO cifre más datos de esta manera, ya que NO está seguro para los estándares de hoy. SOLO debe usar el descifrado, por ninguna otra razón que no sea COMPATIBILIDAD ATRÁS, es decir, cuando no tenga otra opción. ¿Quieres cifrar? Use NaCl / libsodium si es posible.

Estoy re-publicando su código con un par de correcciones (no quería ocultar su versión). Mientras su código funciona, no detecta algunos errores en el relleno. En particular, si la clave de descifrado proporcionada es incorrecta, su lógica de relleno puede hacer algo extraño. Si está de acuerdo con mi cambio, puede actualizar su solución.

 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] # This encryption mode is no longer secure by today's standards. # See note in original question above. def obsolete_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) 

El código a continuación debe ser compatible con Python 3 con los pequeños cambios documentados en el código. También quería usar os.urandom en lugar de Crypto.Random. ‘Salted__’ se reemplaza por salt_header que se puede personalizar o dejar vacío si es necesario.

 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(...) 

Sé que es un poco tarde, pero aquí hay una solución que publiqué en el blog de 2013 sobre cómo usar el paquete pycrypto de python para cifrar / descifrar de una forma compatible con openssl. Se ha probado en python2.7 y python3.x. El código fuente y un script de prueba se pueden encontrar aquí .

Una de las diferencias clave entre esta solución y las excelentes soluciones presentadas anteriormente es que distingue entre tubería y E / S de archivos, lo que puede causar problemas en algunas aplicaciones.

Las funciones clave de ese blog se muestran a continuación.

 # ================================================================ # 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  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  $ 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</password></ciphertext></password></plaintext></mdi></code> </pre>
</div>
</li><!-- #comment-## -->
<div class="list-group-item list-group-item-action flex-column align-items-start">
		      	<h1>  Nota: este método no es compatible con OpenSSL </h1>
<p>  Pero es adecuado si todo lo que quieres hacer es cifrar y descifrar archivos. </p>
<p>  Una auto-respuesta que copié de aquí .  Creo que esta es, quizás, una opción más simple y segura.  Aunque estaría interesado en una opinión experta sobre qué tan seguro es. </p>
<p>  Utilicé Python 3.6 y SimpleCrypt para cifrar el archivo y luego lo subí. </p>
<p>  Creo <em>que</em> este es el código que utilicé para cifrar el archivo: </p>
<pre> <code>from simplecrypt import encrypt, decrypt f = open('file.csv','r').read() ciphertext = encrypt('USERPASSWORD',f.encode('utf8')) # I am not certain of whether I used the .encode('utf8') e = open('file.enc','wb') # file.enc doesn't need to exist, python will create it e.write(ciphertext) e.close</code> </pre>
<p>  Este es el código que uso para descifrar en tiempo de ejecución, ejecuto <code>getpass("password: ")</code> como argumento para no tener que almacenar una variable de <code>password</code> en la memoria </p>
<pre> <code>from simplecrypt import encrypt, decrypt from getpass import getpass # opens the file f = open('file.enc','rb').read() print('Please enter the password and press the enter key \n Decryption may take some time') # Decrypts the data, requires a user-input password plaintext = decrypt(getpass("password: "), f).decode('utf8') print('Data have been Decrypted')</code> </pre>
<p>  Tenga en cuenta que el comportamiento de la encoding UTF-8 es diferente en Python 2.7, por lo que el código será ligeramente diferente. </p>
</div>
</li><!-- #comment-## -->

 	</div>
		
        </div>
<ul>
<li><a href="https://www.pythond.com/14010/no-se-puede-instalar-con-easy_install-o-pip-en-mac.html" rel="bookmark" title="No se puede instalar con easy_install o pip en mac">No se puede instalar con easy_install o pip en mac</a></li><li><a href="https://www.pythond.com/20400/cifrar-y-descifrar-usando-pycrypto-aes-256.html" rel="bookmark" title="Cifrar y descifrar usando PyCrypto AES 256">Cifrar y descifrar usando PyCrypto AES 256</a></li><li><a href="https://www.pythond.com/52858/acolchado-de-descifrado-aes-con-pkcs5-python.html" rel="bookmark" title="Acolchado de descifrado AES con PKCS5 Python">Acolchado de descifrado AES con PKCS5 Python</a></li><li><a href="https://www.pythond.com/28470/cifrado-y-descifrado-rsa-en-python.html" rel="bookmark" title="Cifrado y descifrado RSA en Python">Cifrado y descifrado RSA en Python</a></li><li><a href="https://www.pythond.com/63694/cifrado-de-un-archivo-jpg-utilizando-aes-de-pycrypro-que-falla.html" rel="bookmark" title="Cifrado de un archivo JPG utilizando AES de pycrypro que falla">Cifrado de un archivo JPG utilizando AES de pycrypro que falla</a></li><li><a href="https://www.pythond.com/63210/descifrando-datos-en-python-que-fueron-cifrados-en-3des-por-java.html" rel="bookmark" title="Descifrando datos en Python que fueron cifrados en 3DES por Java">Descifrando datos en Python que fueron cifrados en 3DES por Java</a></li><li><a href="https://www.pythond.com/59248/creacion-de-pycrypto-con-fastmath-gmp-o-mpir-a-traves-de-pip-en-windows.html" rel="bookmark" title="Creación de PyCrypto con fastmath (gmp o mpir) a través de pip en Windows">Creación de PyCrypto con fastmath (gmp o mpir) a través de pip en Windows</a></li><li><a href="https://www.pythond.com/23632/como-uso-un-certificado-x509-con-pycrypto.html" rel="bookmark" title="¿Cómo uso un certificado X509 con PyCrypto?">¿Cómo uso un certificado X509 con PyCrypto?</a></li></ul>    
    </div>
    
</div>

   <div class="clearfix mt-5"></div>
    <hr />
<footer>
        <ul class="list-inline text-center">
        <li class="list-inline-item">© 2017 Desarrollo de Python</li>
        <li class="list-inline-item"><a href="/topics">Topics</a></li>
        <li class="list-inline-item"><a href="#">Terms</a></li>
        <li class="list-inline-item"><a href="#">Privacy Policy</a></li>
        </ul>
</footer>

</div>     
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
  </body>
<!-- Default Statcounter code for Pythond.com
http://www.pythond.com -->
<script type="text/javascript">
var sc_project=11834239; 
var sc_invisible=1; 
var sc_security="e0c6971b"; 
</script>
<script type="text/javascript"
src="https://www.statcounter.com/counter/counter.js"
async></script>
<noscript><div class="statcounter"><a title="Web Analytics
Made Easy - StatCounter" href="https://statcounter.com/"
target="_blank"><img class="statcounter"
src="https://c.statcounter.com/11834239/0/e0c6971b/1/"
alt="Web Analytics Made Easy -
StatCounter"></a></div></noscript>
<!-- End of Statcounter Code -->
</html>