Criptare file in Python 3 con AES su Windows: la guida al codice

0
criptare-file-python-guida

Vi siete mai trovati davanti alla necessità di nascondere da occhi indiscreti delle informazioni contenute sul vostro PC senza utilizzare software di terzi? Una delle migliori soluzioni è senz’altro criptare file in Python.

Nel contesto della programmazione informatica, infatti, diventa importante riuscire a costruire un codice che implementi tale funzionalità, per poterlo poi riutilizzare in progetti futuri.

Oggi vedremo dunque un piccolo script per criptare file in Python, che permetterà di criptare/decriptare un file mediante l’algoritmo AES, e ne spiegheremo tutti i passaggi.

La scelta del linguaggio Python è per pura convenienza: lo stesso programma è riscrivibile in qualsiasi altro linguaggio di programmazione, come C, Java o Kotlin. È da considerare comunque la vasta gamma di moduli che il Python mette a disposizione per implementare funzioni crittografiche, rendendolo particolarmente adatto allo scopo.

Se stai cercando un posto dove poter scambiare consiglipareriopinioni e molto altro sul mondo della crittografia, ti consigliamo caldamente di dare un’occhiata al nostro gruppo dedicato.

Indice


I moduli per criptare file in Python

Prima di passare al codice, dobbiamo però almeno menzionare i moduli che andremo ad utilizzare.

moduli-criptare-file-python

Per quanto riguarda tkinter, ci servirà per avere un’interfaccia grafica più gradevole e user friendly. I moduli osys sono nativi di Python, e permettono di usufruire di alcune funzionalità del sistema operativo per manipolare file e directory.
Infine, pycrypto (importabile come Crypto) e keysaver saranno i due moduli strettamente legati alle funzioni di crittografia: il primo si occuperà di fornirci gli strumenti per applicare la cifratura AES sul file scelto, mentre il secondo, invece, ci garantirà delle funzionalità per generare e salvare la chiave.

Nel caso non foste pratici sui temi di Crittografia e Sicurezza, vi suggeriamo la lettura dell’articolo che tratta proprio di questo argomento prima di procedere nell’analisi del codice.


Endec.py: analisi delle funzioni

Chiameremo lo script endec.py (encrypt/decrypt): la prima cosa da fare dopo aver importato i moduli sarà scrivere le due funzioni fondamentali, ovvero encryptor decryptor.

Partiamo con la prima, encryptor: questa funzione ci permetterà di criptare un file.

funzione-encryptor-python

Essa riceve in ingresso il filepathovvero il percorso completo del file da criptare (es. “C:\Users\utente\file.txt“), e cipher, che consiste in un oggetto di tipo AES Cipher di cui discuteremo nella sezione principale, la main del codice. Ci restituirà il percorso del file criptato.

Per cominciare, scindiamo il nome del file dal percorso contenuto in filepath e immagazziniamolo in purePath. Le due variabili file e fileCry ci serviranno invece per poter rispettivamente leggere dal file in chiaro e scrivere dati in quello crittografato, che avrà lo stesso nome di quello in chiaro ma con formato .encrypted.

Il ciclo while principale viene eseguito finché ci sono dati da leggere nel file, presi a blocchi di 16 byte alla volta: poiché l’AES richiede di avere blocchi di tale dimensione da cifrare, siamo costretti a leggerne esattamente quella quantità per ogni iterazione del ciclo.

In questo ciclo, leggiamo 16 byte in chiaro dal file, li criptiamo utilizzando l’oggetto AES Cipher, e scriviamo il risultato nel file fileCry.


Il problema del padding

L’unico controllo che faremo durante ogni iterazione del ciclo sarà quello di verificare che il blocco letto abbia una lunghezza uguale a 16. Infatti, nel caso in cui la dimensione del nostro file non sia esattamente un multiplo di 16, quando arriveremo in fondo al file leggeremo necessariamente un blocco di dimensione inferiore.

Toccherà dunque a noi ovviare a questo problema, allungando artificialmente la dimensione di quel blocco fino ad arrivare a 16: tale tecnica è detta padding, e vengono usati molti standard diversi.

zero-byte-padding-criptare-file-python

Tuttavia, noi abbiamo optato per un metodo più semplice: nel caso l’ultimo blocco di bytes letto dal file abbia una dimensione diversa da 16 (e quindi necessariamente inferiore), aggiungiamo al blocco n zero-byte, dove n sta ad indicare il numero di byte necessari per colmare la differenza.

Al termine del ciclo, chiudiamo i due file aperti e restituiamo il percorso al file criptato.

La seconda funzione, decryptor, ci consente invece di fare l’inverso, ovvero: dati gli stessi due parametri di encryptor (stavolta col filepath del file da decriptare) e un altro cipher AES, otterremo il rispettivo file decriptato e usufruibile.

decryptor-python-funzione

Il funzionamento è praticamente analogo, ma c’è un errore nel codice!
Un indizio che vi potrebbe far giungere a tale conclusione è la sua brevità rispetto a encryptor: manca la parte in cui togliamo gli zeri che abbiamo aggiunto per creare il padding dal file che abbiamo criptato.

Questo potrebbe causare problemi, ma fortunatamente per noi i formati di file più comunemente utilizzati su Windows come .jpg, .mp3 o .avi sembrano non risentire di questo problema al momento della visualizzazione.

Il motivo di ciò potrebbe essere dovuto all’usanza di vari formati di specificare nei primi byte la lunghezza del file stesso, cosicché i software addetti alla visualizzazione possano sapere quanto ‘mostrare’ nel momento in cui clicchiamo sul file e lo riproduciamo.

Tuttavia, per un uso estensivo del software sarebbe molto meglio ottemperare a tale problema: non si sa mai di che formato strano potrebbe essere il file che andremo a criptare! Lascerò dunque a voi il compito di modificare il codice in modo tale da rendere il programma universalmente compatibile con ogni tipo di formato.


La main del codice

Le due funzioni che abbiamo scritto sopra costituiscono ciò che un altro programmatore potrebbe utilizzare nel caso importasse endec in uno dei suoi script, esattamente come abbiamo fatto noi con tkinter od os per il nostro script endec.py.

Se vogliamo invece far sì che all’esecuzione dello script parta un vero e proprio programma, abbiamo bisogno di scrivere la funzione main, così definita in questo caso:

python-funzione-main-crittografia

Passiamo in ultima battuta ad analizzare il semplice codice del main di endec.

La variabile root sarà il nostro widget di inizializzazione di tkinter: lo nascondiamo immediatamente alla vista con il metodo withdraw().

Ci servirà dunque il percorso esatto del Desktop, poiché sarà la prima schermata che vogliamo far apparire durante la scelta del file da criptare/decriptare: sfruttiamo os.environ per ottenerlo, ed os.join per unire le parti che lo compongono.

A questo punto, usiamo pathname per far scegliere all’utente tramite la GUI di tkinter il file da criptare desiderato:

Adesso dobbiamo leggere da un file .key la chiave da utilizzare per i procedimenti di cifratura e decifratura. Sfrutteremo la funzione read_key_param() del modulo keysaver, che dato un file in ingresso ci restituirà una tupla di tre elementi: il primo consiste in 32 byte che rappresentano la chiave generata tramite l’algoritmo di hashing PBKDF2, mentre il secondo ed il terzo sono rispettivamente il salt PBKDF2 e l’IV, ciascuno di 16 byte.

Non parleremo del modulo keysaver per brevità, ma vi basti sapere che è necessario per generare salvare la chiave AES in un file, che dovrete spostare nella stessa directory di endec.  In fondo all’articolo troverete il codice completo.

Il prossimo passo sarà quello di ottenere i due oggetti AES Cipher, uno dedicato alla cifratura ed uno alla decifratura. Ad entrambi passiamo:

  • res[0] (la chiave)
  • la modalità d’operazione del cipher (che in questo caso sarà CBC)
  • res[2], ovvero l’IV

Gli ultimi passaggi sono abbastanza banali: chiediamo con dei popup tkinter il file da criptare e quello da decriptare, per utilizzare i rispettivi path in encryptor e decryptor, assieme ai due cipher creati. Potremo trovare dunque il file in chiaro e quello .encrypted nella directory in cui avete endec.py.


Codice sorgente per criptare file in Python

Per chi volesse provare di persona il programma, eccovi rilasciato il codice sorgente di endec.py e keysaver.py

#endec.py

import tkinter
import tkinter.simpledialog
import tkinter.messagebox
from tkinter.filedialog import askopenfilename
import os
import os.path
import sys
from Crypto.Cipher import AES
import keysaver



def encryptor(filepath, cipher):
    purePath = os.path.basename(filepath)
    file = open(filepath,"rb")
    fileCry = open(purePath+".encrypted","wb")
    buf = file.read(16)
    while(buf):
     if(len(buf)%16!=0):
      barray = bytearray(buf)
      missing = 16-(len(buf)%16)
      while(missing!=0):
       barray.append(0x00)
       missing-=1
      fileCry.write(cipher.encrypt(barray))
     else:
      fileCry.write(cipher.encrypt(buf))
     buf = file.read(16)
    file.close()
    fileCry.close()
    return fileCry





def decryptor(filepath,cipher):
    purePath = os.path.basename(filepath)
    file = open(os.path.splitext(purePath)[0],"wb")
    fileCry = open(filepath,"rb")
    buf = fileCry.read(16)
    while(buf):
     file.write(cipher.decrypt(buf))
     buf = fileCry.read(16)
    file.close()
    fileCry.close()
    return file




if __name__ == "__main__":
    root = tkinter.Tk()
    root.withdraw()
    desktop = os.path.join(os.path.join(os.environ['USERPROFILE']), 'Desktop')
    pathname = askopenfilename(initialdir=desktop,
                           filetypes =(("All Files","*.*"),),
                           title = "Scegli un file da criptare.",
                           parent = root          )
    res = keysaver.read_key_param("key.key")
    cCipher = AES.new(res[0],AES.MODE_CBC,res[2])
    dCipher = AES.new(res[0],AES.MODE_CBC,res[2])
    fCriptato = encryptor(pathname,cCipher)
    print("File criptato con successo!")
    pathname = askopenfilename(initialdir=desktop,
                           filetypes =(("All Files","*.*"),),
                           title = "Scegli un file da DECRIPTARE.",
                           parent = root           )
    fDecriptato = decryptor(pathname,dCipher)
    print("File DECRIPTATO con successo!")
    input()
#keysaver.py

import tkinter
import tkinter.simpledialog
import tkinter.messagebox
from tkinter.filedialog import askopenfilename
import os
import os.path
import sys
from Crypto.Cipher import AES
from pbkdf2 import PBKDF2



def key_generator(pwd):
    salt = os.urandom(16)
    key = PBKDF2(pwd,salt,400000).read(64)
    iv = os.urandom(32)
    storage = open('key.key','wb')
    storage.write(key+salt+iv)
    storage.close()
    return storage

def read_key_param(file):
	retriever = open(file,"rb")
	retKey = retriever.read(32)
	retSalt = retriever.read(16)
	retIv = retriever.read(16)
	return (retKey,retSalt,retIv)


if __name__ == '__main__':
    app_window = tkinter.Tk()
    app_window.withdraw()
    inputPass = tkinter.simpledialog.askstring("Password", "Inserisci una password su cui generare un hash (è suggerito utilizzare una password forte): ", parent = app_window)

    if(inputPass!=None):
     key_generator(inputPass)
     print("Chiave a 256 bit generata con successo.")

    else:
     print("ERRORE")
     exit()

    values = read_key_param('key.key')
    chiave = values[0]
    sale = values[1]
    ivector = values[2]
    print("Key:"+str(chiave)+"\n"+"Salt:"+str(sale)+"\n"+"IV:"+str(ivector))
    input()