Criptografía en Python - Aumentando la seguridad, trucos y aplicativos en la vida real
Ya hemos presenciado tanto el cifrado simétrico como el asimétrico, inclusive, una combinación de éstos, como lo es el cifrado híbrido, pero hoy vamos a aprender cómo mantener una infraestructura sencilla y hablar un poco sobre cosas relevantes en cuanto a la seguridad, asimismo, realizaremos un pequeño programa que haga uso de todo lo que hemos comprendido a los largo de estas partes.
La entropía
La entropía es la aleatoriedad que obtiene a través de un sistema operativo o una aplicación para su uso en los algoritmos o cálculos que requieran datos aleatorios.
Haciendo mención al cifrado híbrido, como ya sabemos, en el cifrado híbrido se utiliza el cifrado simétrico para cifrar los datos y se utiliza el cifrado asimétrico para cifrar la clave (también llamada clave de sesión), clave que debería ser aleatoria y es la base para proteger los datos.
PRNG y CSPRNG
Los generadores de números pseudo-aleatorios (pseudo-random number generators, o simplemente PRNG, en inglés) y los generadores de números
pseudo-aleatorios criptográficamente seguros (cryptography secure random number generators, o simplemente CSPRNG, en inglés) son los que permiten incluir cierto grado de aletoriedad a los algoritmos (o en el caso mencionado, del cifradohíbrido).
La diferencia relevante entre cada uno, es que los PRNG son inseguros cuando se les aplica a la criptografía, mientras que los CSPRNG son por definición PRNG con propiedades que lo hacen adecuados para la criptografía.
Imaginen que están creando una página web, al usuario se le proporciona un identificador que aparentemente es único, luego de que unos miles de
usuarios se han registrado, llega la pesadilla, un usuario tiene el
identificador de otro usuario e imagínense que esa página web sea una
aplicación que maneje dinero...
random vs. secrets
En la parte anterior se usó os.urandom(...) para generar una clave aleatoria, pero a diferencia de secrets no proporciona cómodamente una interfaz para la creación de aplicaciones (como de escritorio, web y demás).

Aunque no lo parezca, es muy trivial encontrar una URL en nuestra bandeja
de correo electrónico al registrarnos en algún servicio que requiera la
confirmación explícita del mismísmo usuario. Como ya mencioné
anteriormente, a diferencia de os.urandom(...) y secrets, es que éste proporciona una interfaz más cómoda para este tipo de aplicativos. Siguiendo con las comparaciones, hay que aclarar que no se debe ni pensar ni usar la librería random para la criptografía, sino más bien para el modelado de datos y la simulación.
Creando una infraestructura
Ya hemos aprendido acómo usar RSAen python con la librería pycryptodome, aunque hay algo faltante que hasta puede ser una limitante en la usabilidad referente al usuario, la importación de claves.
No hace falta mencionar los formatos admitidos que se ha hablado, pero lo que debo mencionar es lo fácil que es importar así como su contraparte, la exportación.
Para este ejemplo usaremos el código de la parte anterior y le agregaremos un método más, llamado '.import_key(...)' con el objetivo de importar las claves en los formatos OpenSSH,PEM o DER.
from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA def generate(bit_size): keys = RSA.generate(bit_size) return (keys, keys.publickey()) def encrypt(pub_key, data): cipher = PKCS1_OAEP.new(pub_key) return cipher.encrypt(data) def decrypt(priv_key, data): cipher = PKCS1_OAEP.new(priv_key) return cipher.decrypt(data) def import_key(filename, passphrase=None): with open(filename, 'rb') as fd: return RSA.import_key(fd.read(), passphrase)
claves RSA. Aquí está la observación empírica:



Lo que acabamos de ver es más que una simple utilidad, es algo que no
puede faltar en una aplicación de esta índole, de ser así el usuario
tendría que estar generando un par de claves sin sentido o sólo se
pudieran usar mientras estén en memoria, pero nada más. Además que así
se puede compartir fácilmente la clave pública a nuestro destinatario.
Exportando e importando con una contraseña
Tal vez exportar e importar una clave y guardarla en el disco no
suene muy seguro (especialmente para la clave privada), quizá lo que
el usuario desee es cifrar el par de claves o necesitamos hacerlo.
pycryptodome nos ofrece algunos parámetros que son fácilmente
configurables.
En el caso de la clave pública no es muy necesario y es poco
frecuente ya que ésta no pone en peligro la infraestructura ni mucho
menos, de hecho es una de las ventajas de la encriptación de clave
pública, ya que dos partes se pueden comunicar entre sí.

AES128-CBC con la contraseña '123'
El parámetro pkcs es necesario especificarlo en 8 cuando se necesite especificar una protección aparte de la que tiene por defecto cuando no se le pasa ningún parámetro, además que también se requiere cuando se ingresa una contraseña (en el parámetro passphrase). Si es una clave pública (o es una clave privada que solo se define el parámetro passphrase), se ignoran los parámetros y se deriva la contraseña con MD5 y Triple DES para el cifrado (todo se considera obsoleto, por lo que es conveniente especificarlos).

Al tratar de importar sin especificar la contraseña, genera un error, pero cuando se es explícito con ella se descifra y se importa correctamente.
Cifrando archivos
La ventaja del disco de almacenamiento es el tamaño, pero en su contra es muy lenta tanto la lectura como la escritura, se puede resolver con la memoria RAM que es muy rápida pero carece de una limitación en cuanto al tamaño. Una solución no es comprarse una que tenga más capacidad, lo que funcionaría sin problemas es un algoritmo que cifre los datos en partes, por lo se tendría un equilibrio sin considerar el tamaño del mismo.
El truco aquí es crear una versión mejorada de aes_eax.py que tenga implementada lo que se acaba de mencionar (lo de cifrar partes y no todo el archivo por completo), son palabras bonitas pero seguro se verían mejor con código:
import os import struct from Crypto.Cipher import AES # El tamaño de la división del archivo chunk_size = 1024*64 # La extensión del archivo a la hora # de encriptarlo. extension = 'enc' def encrypt_file(key, filename): # Concatenamos el nombre del archivo # con el nombre de la extensión. output = filename + '.' + extension # Obtenemos el tamaño del archivo filesize = os.path.getsize(filename) with open(filename, 'rb') as fd_in: with open(output, 'wb') as fd_out: # Escribimos el tamaño del archivo. # # <: Little Indian # Q: unsigned long long fd_out.write( struct.pack('<Q', filesize) ) while (True): # Leemos según el tamaño de división # proporcionado por el usuario. chunk = fd_in.read(chunk_size) # Termino de leer, sale if (len(chunk) == 0): break cipher = AES.new(key, AES.MODE_EAX) ciphertext, tag = cipher.encrypt_and_digest(chunk) # Escribimos las partes relevantes fd_out.write(cipher.nonce) fd_out.write(tag) fd_out.write(ciphertext) # Vaciar el buffer de la memoria y pasarlo al disco fd_out.flush() # Forzar la escritura de cualquier buffer # (ya sea del sistema operativo o del mismo programa) # al disco. os.fsync(fd_out) def decrypt_file(key, filename): # Obtenemos el verdadero nombre, que ahora será el de salida (output, _) = os.path.splitext(filename) with open(filename, 'rb') as fd_in: with open(output, 'wb') as fd_out: # Obtenemos el tamaño del archivo original filesize = struct.unpack('<Q', fd_in.read(struct.calcsize('<Q')))[0] while (True): # Leemos el tamaño de la división mas el tamaño del bloque, # pero se le multiplica por 2, porque se necesita calcular # el nonce, tag y el mismísimo texto. chunk = fd_in.read(chunk_size + AES.block_size * 2) if (len(chunk) == 0): break nonce = chunk[:AES.block_size] tag = chunk[AES.block_size:AES.block_size * 2] ciphertext = chunk[AES.block_size * 2:] cipher = AES.new(key, AES.MODE_EAX, nonce) text_plain = cipher.decrypt(ciphertext) fd_out.write(text_plain) fd_out.truncate(filesize) fd_out.flush() os.fsync(fd_out)
salem.jpg.enc

La diferencia entre la primera y la segunda imagen es que el la
primera se cifra la imagen original, mientras que en la segunda se
elimina y se descifra para luego mostrarla.
Lecturas recomendadas Esta es la última parte de esta serie de artículos meramente educativos y entretenidos. No hay necesidad de estar cabizbajo por ello, como les vengo mencionando deben leer mucho más allá de lo que se escribe aquí porque estos mundos son muy amplios, por eso recomiendo leer lo siguiente y mucho más:
- https://docs.python.org/3/library/struct.html
- https://docs.python.org/3/library/os.html
- eli.thegreenplace.net/2010/06/25/aes-encryption-of-files-in-python-with-pycrypto
- https://es.wikipedia.org/wiki/Generador_de_n%C3%BAmeros_pseudoaleatorios_criptogr%C3%A1ficamente_seguro
- https://pycryptodome.readthedocs.io/en/latest/src/examples.html#generate-an-rsa-key
- https://es.wikipedia.org/wiki/Entrop%C3%ADa_(computaci%C3%B3n)

Deja una respuesta