Clase 04 · 44 minGratis
Encriptación simétrica con TDD
Clase 4 del curso gratis de buenas prácticas en Python: escribimos primero las pruebas y después la función, encriptamos con Fernet y aprendemos a probar funciones que no son deterministas.
Toca cualquier marca ▸ 0:00 de los apuntes y el video salta a ese momento. Abrir en YouTube
Simétrica y asimétrica
En la encriptación simétrica, la misma llave sirve para encriptar y para desencriptar. En la asimétrica hay dos: una pública para encriptar y una privada para desencriptar. Aquí implementamos la simétrica.
Pistas de tipo y docstrings
Desde Python 3.5 puedes anotar el tipo de cada argumento y del retorno. Python no las hace cumplir (es de tipado dinámico), pero documentan la función y el editor las muestra al usarla:
def encriptando_texto(mensaje: str, llave: str) -> str:
...Sobre el idioma : el estándar de industria es escribir código y documentación en inglés, porque en un equipo internacional es el idioma común. En el curso usamos español con fines educativos.
Primero la prueba
TDD invierte el orden: dejas solo el esqueleto de la función y escribes la prueba. La prueba falla (en rojo) y tu trabajo es hacerla pasar.
Hay una trampa que conviene ver una vez : con un solo ejemplo, basta con que la función devuelva siempre ese resultado para que la prueba pase. Por eso una prueba con un solo caso no prueba casi nada .
Fernet y las funciones puras
Para encriptar usamos cryptography, una librería probada; el curso no trata
de inventar un algoritmo.
pip install cryptography"""
Este modulo trabajará una encriptacion simetrica!
"""
from cryptography.fernet import Fernet
def encriptando_texto(mensaje: str, llave: str) -> str:
"""
Esta funcion hace un encriptado simetrico
Parametros:
- mensaje (str): es el mensaje a ser encriptado
- llave (str): es la llave de encriptación
Retorna:
- (str): Mensaje encriptado
"""
entorno_cifrado = Fernet(llave)
mensaje_encriptado = entorno_cifrado.encrypt(mensaje)
return mensaje_encriptadoFernet en realidad trabaja con bytes (
b"hola"), no constr. Las pistas de tipo quedan comostrhasta la clase 8, cuando la API obliga a notar la diferencia y se corrigen.
La llave no se genera dentro de la función. Una función pura devuelve siempre la misma salida para la misma entrada , y eso es lo que se puede probar con confianza. Una llave aleatoria adentro la volvería impredecible.
¿Cómo se prueba algo que cambia cada vez?
Sorpresa: las pruebas fallan aunque la función esté bien. Fernet agrega una marca de tiempo y un valor aleatorio, así que el mismo mensaje encriptado dos veces da dos resultados distintos. Comparar contra un texto fijo no sirve.
La salida es probar la propiedad que sí importa: lo que encripto, lo puedo recuperar .
"""
Aqui se prueba la funcion de encriptado simetrico
"""
import pytest
from cryptography.fernet import Fernet
from app.funciones.encriptado import encriptando_texto
LLAVE = b"UyJ4iWnYDPOcZ3sCmwNZtKbIudZRYl5n7owVTiQ4r3o="
@pytest.mark.parametrize(
"mensaje,llave",
[
(b"a", LLAVE),
(b"b", LLAVE),
(b"Este es mi curso de python", LLAVE),
],
)
def test_encriptando_texto(mensaje, llave):
"""Encripta con nuestra función y desencripta con Fernet."""
salida = encriptando_texto(mensaje, llave)
assert Fernet(llave).decrypt(salida) == mensajeLa función inversa
def desencriptando_texto(mensaje_encriptado: str, llave: str) -> str:
"""funcion para desencriptar mensaje con una llave"""
entorno_cifrado = Fernet(llave)
return entorno_cifrado.decrypt(mensaje_encriptado)Su prueba es el espejo de la anterior: encripta con Fernet, desencripta con
nuestra función y compara. Aquí sí se puede generar la llave al azar en cada
caso, porque lo que se compara es el mensaje original. Y una regla de estilo
que Pylint recuerda : los import van arriba del
archivo, no dentro de las funciones. (En el proyecto de esta clase todavía
quedan dentro de las funciones de encriptado.py; se suben al pagar la deuda
técnica de la clase 5.)
El reto de esta clase
Escribe tests/test_desencriptando_texto.py con al menos cinco mensajes:
mayúsculas y minúsculas mezcladas, una arroba, números y un mensaje largo.
Ver una solución posibleOcultar la solución
"""
Aqui se prueba el modulo de desencriptar un texto
"""
import pytest
from cryptography.fernet import Fernet
from app.funciones.encriptado import desencriptando_texto
@pytest.mark.parametrize(
"mensaje",
[b"a", b"A", b"mensaje", b"MeNsAjE", b"@", b"este es un mens@je", b"m3ns@j3 d3 pru3b4!!"],
)
def test_desencriptando_texto(mensaje):
"""Encripta con Fernet y desencripta con la función a probar."""
llave = Fernet.generate_key()
mensaje_encriptado = Fernet(llave).encrypt(mensaje)
assert desencriptando_texto(mensaje_encriptado, llave) == mensaje