martes, 28 de julio de 2020

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.

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
  • http://www.pythondiario.com/2019/07/tutorial-como-enviar-correos-con-el.html

~ DtxdF

No hay comentarios :
Write comentarios

Tu comentario es importante y nos motiva a seguir escribiendo...

Powered by Blogger .