Criptografía en Python - Cifrado Híbrido


Esquema de cifrado híbrido
El cifrado híbrido no es más que una combinación con cierta elegencia del ya
conocido
cifrado simétrico
y el amado
cifrado asimétrico. Los dos se complementan para no solo por ser eficiente, sino que también
superar las limitaciones que impone cada uno. Mientras que la limitación del
cifrado simétrico es compartir la clave secreta que no puede ser revelada por
un tercero, la del asimétrico es el bajo rendimiento que ofrece, el increíble
poder de computo que requiere mas el no poder cifrar menos que el tamaño de
clave generado.

Como ya se habló sobre que no es una solución inteligente el aumentar el
tamaño de clave en un algoritmo de cifrado asimétrico, ya que aunque en teoría
sería más seguro, se debe buscar un equilibrio entre eficiencia y la seguridad
según el poder de computo en la actualidad.
Esta no es la primera vez que se hablado sobre este método para compartir
datos de forma segura, ya se hizo con
GnuPG
hablando sobre un tema importante en la sociedad que muchos dan por hecho o
ignoran porque simplemente creen que así se solucionaría todo. La diferencia
sustancial entre ese artículo y el actual, es que el anterior se usa GnuPG
abstrayendo un poco al programador en la práctica. Es cierto, hay que tener
cierta base para comprender y usar eficazmente GnuPG, pero no es tanta como se
pensaría, así que este artículo va más allá de lo que se ha enseñado, también
será complementarío y quizás un repaso a lo que ya hemos observado a lo largo
de esta serie de artículos meramente entretenidos y de índole educativa.
Índice

Creando lo híbrido en lo cifrado

Un título sin sentido, simplón e irregular, pero una vez que experimentemos
cómo crearlo verán que comienza a agradar y a incluir un significado
coherente en nuestra mente.
En una aplicación o un nombre mucho más bonito, un proyecto, se requiere
separar cada pieza para tratarla de diferentes maneras, podría llamarse
modularidad, pero básicamente lo que se trata de hacer es resolver un
problema diferente para cada parte que lo compone. Trataremos de hacer
exactamente lo propuesto anteriormente, separar en tres archivos
(aes_eax.py, rsa.py, hibrid.py) con objetivo de tratarlos de diferente
manera y poder lograr el esquema del susodicho cifrado híbrido.
Comencemos con aes_eax.py:
from Crypto.Cipher import AES

def encrypt(key, data):
    cipher = AES.new(key, AES.MODE_EAX)
    ciphertext, tag = cipher.encrypt_and_digest(data)

    return cipher.nonce + tag + ciphertext

def decrypt(key, data):
    nonce = data[:AES.block_size]
    tag = data[AES.block_size:AES.block_size * 2]
    ciphertext = data[AES.block_size * 2:]

    cipher = AES.new(key, AES.MODE_EAX, nonce)
    
    return cipher.decrypt_and_verify(ciphertext, tag)

El código anterior no es para nada novedoso, ya se ha descrito en el
capítulo dedicado especialmente a AES.
Ahora lo otro que necesitamos es rsa.py:
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)

No hay nada especial con lo mostrado anteriormente, es como repasar cosas
que ya hemos aprendido en el pasado pero que seguiremos usando cuantas veces
sean necesarias.
Muy bien, hemos dado vida a lo principal y posiblemente lo más complejo de
todo, ahora hace falta lo que me motivó a escribir este pequeño escrito,
hibrid.py:
import os

import aes_eax
import rsa

key_size = 32

def encrypt(pub_key, data):
    session_key = os.urandom(key_size)
    enc_key = rsa.encrypt(pub_key, session_key)
    enc_data = aes_eax.encrypt(session_key, data)

    return enc_key + enc_data

def decrypt(priv_key, data):
    key_size = priv_key.size_in_bytes()
    enc_key = data[:key_size]
    enc_data = data[key_size:]

    dec_key = rsa.decrypt(priv_key, enc_key)
    dec_data = aes_eax.decrypt(dec_key, enc_data)

    return dec_data
  

La constante key_size se especifica para describir el tamaño en bytes de la clave que será generada aleatoriamente por os.urandom(...), luego se encriptará la contraseña generada usando RSA rompiendo el problema de compartirla en un canal inseguro, y por último usamos AES para encriptar los datos rápidamente.
Ahora en la función decrypt(...) tendremos que calcular los datos del emisor. Como había dicho en anteriores partes, RSA no cifra más allá del tamaño de la clave, así que usamos la función .size_in_bytes(...) para calcular ese tamaño en bytes y parsear correctamente tanto la clave que está encriptada como los datos que igualmente están encriptados. A posteriori desencriptamos la clave y los datos para retornarlos efectivamente.
Si se observa minuciosamente, el título sí tiene sentido cuando se analiza correctamente el código.

De la teoría a la práctica

Ya sabemos tanto conceptos como el código que hay que usar, pero no lo hemos puesto en práctica. Pues no hace falta más que ejecutar la consola de python, importar un par de módulos y comenzar a jugar. Empecemos:
Ejecución de toda la sopa de código que hemos creado

Lecturas recomendadas

  • https://pycryptodome.readthedocs.io/en/latest/src/examples.html#encrypt-data-with-rsa
  • https://es.wikipedia.org/wiki/Criptograf%C3%ADa_h%C3%ADbrida
  • https://www.gnupg.org/gph/es/manual.html
  • https://www.pythondiario.com/2019/07/tutorial-como-enviar-correos-con-el.html
~ DtxdF

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir
White Monkey