🚀 Descubre cómo asegurar tus datos con Fernet y FastAPI 🍹
Si eres un entusiasta de la programación web y te gusta disfrutar de un buen Fernet en tu tiempo libre, este artículo es para ti. En este post, exploraremos cómo puedes combinar dos elementos aparentemente dispares: Fernet, un sistema de cifrado de datos, y FastAPI, un marco web rápido y moderno para construir aplicaciones web API de alto rendimiento.
¿Qué es Fernet?
El Fernet, un destilado de hierbas con raíces en Italia, es conocido por su sabor único y su capacidad para unir a la gente en torno a una copa. En el mundo de la programación, Fernet toma un papel diferente: se convierte en una herramienta esencial para proteger tus datos. Fernet utiliza el cifrado AES para encriptar o desencriptar los datos dentro de una base de datos.
Vamos a ver una forma muy rápida de implementarlo en FAST API.
Primero tenemos que instalar la dependencia, bien en el fichero de requirements o bien como dependencia de pip:
pip install cryptography
Una vez instalada la dependencia, tendremos este archivo con los métodos más utilizados:
cryptografia.py:
from cryptography.fernet import Fernet import os from dotenv import load_dotenv load_dotenv() FERNET_KEY = bytes(os.getenv('FERNET_KEY'), 'utf-8') fernet = Fernet(FERNET_KEY) def generate_key(): key = Fernet.generate_key() return key.decode('utf-8') #print (generate_key()) def encrypt_data(data): return fernet.encrypt(str(data).encode('utf-8')).decode('utf-8') def decrypt_data(data): try: datosDecrypt = fernet.decrypt(data.encode()).decode('utf-8') except: datosDecrypt = data return datosDecrypt
Primero intentamos cargar la clave de fernet, esta clave será importantísima ya que nos servirá para cifrar y descifrar los datos. Sin ella, perdemos los datos. Por ello almacénala muy bien en tu llavero.
Con esta función obtenemos una clave de Fernet Aleatoria:
def generate_key(): key = Fernet.generate_key() return key.decode('utf-8') #print (generate_key())
En la primera ejecución, debemos ejecutar esa función (descomentando el print) y comentando la línea de : fernet = Fernet(FERNET_KEY)
Esto nos devolverá una clave aleatoria, podemos ejecutarlo tantas veces como queramos, hasta tener una clave que nos guste.
Una vez tengamos la clave, tenemos que crear un .env (en la raíz del proyecto y añadir la clave que hemos generado).
FERNET_KEY="Yt6dus7fasdgertmpoefv9i94059032ikjo-fmkfgmlkpe"
*Esta clave es ficticia, tendrás que añadir la tuya.
Ahora voy a explicar los otros dos métodos:
def encrypt_data(data): return fernet.encrypt(str(data).encode('utf-8')).decode('utf-8')
Este método (encrypt_data) cifra los datos, podemos usarlo antes de almacenar los datos en la base de datos (BBDD).
def decrypt_data(data): try: datosDecrypt = fernet.decrypt(data.encode()).decode('utf-8') except: datosDecrypt = data return datosDecrypt
El método decrypt_data descifra los datos, esto se usará para los get de la base de datos. En este método he incluido un try catch, ya que cuando obtiene un dato que no esté cifrado, saltaría error, en ese caso lo devuelve en crudo (esto es por si vuestros datos no estaban previamente cifrados).
Tenemos que tener cuidado con qué datos vamos a cifrar, ya que una vez cifrados no podremos filtrar por ellos (no de forma eficiente). Entonces intentaremos cifrar aquellos datos esenciales y que no tengamos que realizar búsquedas sobre ellos en el futuro (en la interfaz de APP, siempre podríamos descifrar la base de datos entera y realizar las búsquedas en crudo).
Y ya tenemos todo, ahora viene la magia.
Para implementar de forma rápida estos métodos, lo implementaremos directamente en los objetos de schema de nuestros datos antes de entrar en la BBDD.
Por ejemplo:
from util.cryptografia import decrypt_data, encrypt_data class PostUsuario (BaseModel): id: int dni: str datos: str class Config: orm_mode = True, schema_extra = { "example": { "id": "1", "dni": "12345678A", "datos": "Mis datos secretos" } } def __init__(self, **data): # cifra los datos antes de crear el objeto data['dni'] = encrypt_data(data['dni']) data['datos'] = encrypt_data(data['datos']) super().__init__(**data)
Tal como muestro en el ejemplo importo dentro de la carpeta /util/cryptografia.py los métodos decrypt_data, encrypt_data y luego hago la magia.
Solo con incluir en el constructor de inicio __init__ del objeto Python en cuestión, la función encrypt_data aplicada al dato que queremos cifrar, se encargará de cifrar automáticamente los datos.
Esto debemos aplicarlo a nuestro método POST/UPDATE (previo a introducir datos en la base de datos).
Si tenemos un GET para obtener datos, tendremos que construir el objeto de la siguiente forma.
from util.cryptografia import decrypt_data, encrypt_data class GetUsuario (BaseModel): id: int dni: str datos: str class Config: orm_mode = True, schema_extra = { "example": { "id": "1", "dni": "12345678A", "datos": "Mis datos secretos", } } def __init__(self, **data): # cifra los datos antes de crear el objeto data['dni'] = decrypt_data(data['dni']) data['datos'] = decrypt_data(data['datos']) super().__init__(**data)
En este ejemplo como antes, importo dentro de la carpeta /util/cryptografia.py los métodos decrypt_data, encrypt_data.
Y después añado un __init__ que se encarga de descifrar los datos que están cifrados previamente.
De esta manera no tendremos que modificar nuestras llamadas a la base de datos.
*Para que funcione correctamente, al devolver datos necesitamos devolver un objeto GetUsuario (construido a partir del schema que hemos modificado)
return GetUsuario(datosDevueltosCRUD.id, datosDevueltosCRUD.dni, datosDevueltosCRUD.datos)
Espero que te sirva.
Ingeniero en Informática, me encanta crear cosas o arreglarlas y darles una nueva vida. Escritor y poeta. Más de 20 APPs publicadas y un libro en Amazon.