I'm using https://github.com/eblocha/django-encrypted-files to encrypt files uploaded to a server through a simple contact form app in Django. django-encrypted-files
uses AES in CTR mode to encrypt an uploaded file via an upload handler while streaming the file to the server.
What I'm trying to do is manually decrypt the encrypted file by downloading the file via FTP and decrypting it locally in the Python shell. I do not want or need to stream decrypt the file from the server or modify django-encrypted-files
; I only want to manually download and then decrypt files locally in the Python shell.
The problem is I can't get local Python decryption to work. The docs at Cryptography show an example of encryption and decryption using a sample text input in the Python shell. But there are no examples of encrypting/decrypting a file.
The code below what I'm trying to use in the Python shell. The original file uploaded via Django is uploaded_file.txt. The encrypted file downloaded from the server is encrypted.txt; the file to save the decrypted text to is decrypted.txt.
But when I try the code below, the decrypted.txt is empty. Is this an issue with writing the file? Or an issue with the iv
?
What is a working example of decrypting a AES-CTR mode file in the local Python shell?
uploaded_file.txt: https://paste.c-net.org/MiltonElliot
encrypted.txt: https://paste.c-net.org/ChasesPrints
Python shell:
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
key = b'\x1a>\xf8\xcd\xe2\x8e_~V\x14\x98\xc2\x1f\xf9\xea\xf8\xd7c\xb3`!d\xd4\xe3+\xf7Q\x83\xb5~\x8f\xdd'
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CTR(iv))
decryptor = cipher.decryptor()
openfile = open("encrypted.txt", 'rb')
savefile = open("decrypted.txt", "wb")
read = openfile.read()
savefile.write(read[16:])
445 // output
exit
But the decrypted.txt file is empty.
This may or may not be relevant; this is the function that encrypts while streaming to the server:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from django.core.files.uploadhandler import FileUploadHandler
class EncryptedFileUploadHandler(FileUploadHandler):
"""Encrypt data as it is uploaded"""
def __init__(self, request=None, key=None):
super().__init__(request=request)
self.key = key or settings.AES_KEY
def new_file(self, *args, **kwargs):
self.nonce = os.urandom(16)
self.encryptor = Cipher(algorithms.AES(self.key),modes.CTR(self.nonce)).encryptor()
self.nonce_passed = False
return super().new_file(*args,**kwargs)
def receive_data_chunk(self, raw_data, start):
if not self.nonce_passed:
self.nonce_passed = True
return self.nonce + self.encryptor.update(raw_data)
else:
return self.encryptor.update(raw_data)
def file_complete(self, file_size):
return
During encryption, IV and ciphertext are concatenated: IV || ciphertext
. During decryption, a random IV is used, which is wrong. Instead, the IV of the encryption must be applied. For this, IV and ciphertext have to be separated.
In addition, the update()
and finalize()
methods must be called, which perform the actual decryption.
The following code essentially matches your code, extended by the missing parts:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
# Read ciphertext
openfile = open("encrypted.txt", 'rb')
read = openfile.read()
openfile.close()
# Separate IV and ciphertext; decrypt
iv = read[:16]
ciphertext = read[16:]
key = b'\x1a>\xf8\xcd\xe2\x8e_~V\x14\x98\xc2\x1f\xf9\xea\xf8\xd7c\xb3`!d\xd4\xe3+\xf7Q\x83\xb5~\x8f\xdd'
cipher = Cipher(algorithms.AES(key), modes.CTR(iv))
decryptor = cipher.decryptor()
decrypted = decryptor.update(ciphertext) + decryptor.finalize()
# Store decrypted data
savefile = open("decrypted.txt", "wb")
savefile.write(decrypted)
savefile.close()
This code successfully decrypts the ciphertext in the linked file (using the posted key).