Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to decrypt OpenSSL AES-encrypted files in Python?

OpenSSL provides a popular (but insecure – see below!) command line interface for AES encryption:

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

Python has support for AES in the shape of the PyCrypto package, but it only provides the tools. How to use Python/PyCrypto to decrypt files that have been encrypted using OpenSSL?

Notice

This question used to also concern encryption in Python using the same scheme. I have since removed that part to discourage anyone from using it. Do NOT encrypt any more data in this way, because it is NOT secure by today's standards. You should ONLY use decryption, for no other reasons than BACKWARD COMPATIBILITY, i.e. when you have no other choice. Want to encrypt? Use NaCl/libsodium if you possibly can.

like image 264
Thijs van Dien Avatar asked May 26 '13 16:05

Thijs van Dien


People also ask

How do I decrypt an encrypted Python file?

Decrypt the encrypted file We have to use the same key to decrypt the file: Initialize the Fernet object and store it in the fernet variable. Read the encrypted file. Decrypt the file and store it into an object.

Can we decrypt AES?

Only those who have the special key can decrypt it. AES uses symmetric key encryption, which involves the use of only one secret key to cipher and decipher information.


Video Answer


2 Answers

Given the popularity of Python, at first I was disappointed that there was no complete answer to this question to be found. It took me a fair amount of reading different answers on this board, as well as other resources, to get it right. I thought I might share the result for future reference and perhaps review; I'm by no means a cryptography expert! However, the code below appears to work seamlessly:

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) 

Usage:

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

If you see a chance to improve on this or extend it to be more flexible (e.g. make it work without salt, or provide Python 3 compatibility), please feel free to do so.

Notice

This answer used to also concern encryption in Python using the same scheme. I have since removed that part to discourage anyone from using it. Do NOT encrypt any more data in this way, because it is NOT secure by today's standards. You should ONLY use decryption, for no other reasons than BACKWARD COMPATIBILITY, i.e. when you have no other choice. Want to encrypt? Use NaCl/libsodium if you possibly can.

like image 77
Thijs van Dien Avatar answered Oct 01 '22 09:10

Thijs van Dien


I am re-posting your code with a couple of corrections (I didn't want to obscure your version). While your code works, it does not detect some errors around padding. In particular, if the decryption key provided is incorrect, your padding logic may do something odd. If you agree with my change, you may update your solution.

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) 
like image 28
Gregor Avatar answered Oct 01 '22 08:10

Gregor