viernes, 24 de julio de 2020

Criptografía en Python - RSA

¿Qué es RSA?


Adi Shamir, uno de los tres inventores de RSA (los otros dos son Ron Rivest y Leonard Adleman).


En criptografía, RSA (siglás de Rivest, Shamir y Adleman) es un sistema criptográfico de clave pública desarrollado en 1979, siendo el primer y el más utilizado algoritmo de esta índole registrado hasta la fecha. Es capaz incluso de cifrar y firmar digitalmente.

Como en todo sistema de clave pública, el emisor requiere la clave pública del receptor para cifrar los datos a enviar, a posteriori, el receptor deberá usar su clave privada para descifrarlos. Una cosa peculiar es que para el caso de firmar los datos, el emisor usa su propia clave privada para firmarlos y luego el receptor usar la clave pública del emisor para verificarlos.

La Librería pycryptodome

python no es solamente conocido por tener una sintaxis legible por humanos, también se le conoce por la cantidad excesivas (aunque útiles) de librerías, módulos y demás. pycryptodome es una librería que utiliza primitivas criptográficas de bajo-nivel, está escrita mayormente en python, pero para uso crítico (como el rendimiento) se utilizan algunas extensiones escritas en C.

pycryptodome en una bifurcación de la vieja librería pycrypto, que trae con sí, las siguientes mejoras:
  • Modos de cifrado autenticados (GCM, CCM, EAX, SIV, OCB)
  • AES acelerado en plataformas Intel a través de AES-NI
  • Soporte de primera clase para PyPy
  • Criptografía de curvas elípticas (solo curvas NIST P-256, P-384 y P-521)
  • API mejor y más compacta (atributos nonce y iv para cifrados, generación automática de nonces e IVs aleatorios, modo de cifrado CTR simplificado y más)
  • SHA-3 (incluidos los XOF SHAKE), algoritmos hash SHA-512 y BLAKE2 truncados
  • Cifrados de flujo Salsa20 y ChaCha20/XChaCha20
  • Poly1305 MAC
  • Cifrados autenticados ChaCha20-Poly1305 y XChaCha20-Poly1305
  • funciones de derivación scrypt, bcrypt y HKDF
  • Deterministic (EC)DSA
  • Contenedores de claves PKCS-8 protegidos con contraseña
  • Esquema de intercambio secreto de Shamir
  • Los números aleatorios consiguen originados directamente del sistema operativo (y no de un CSPRNG [Generador de Números Criptográficamente Seguros, en español] en el espacio de usuario)
  • Proceso de instalación simplificado, que incluye un mejor soporte para Windows
  • Generación de claves RSA y DSA más limpia (basada en gran medida en FIPS 186-4)
  • Limpieza importante y simplificación de la base de código

Instalación de pycryptodome

Al igual que su uso, su instalación no tarda mucho más que ejecutar un simple comando:
python3 -m pip install pycryptodome 

También es recomendable no tener instalado pycrypto, que como se mencionó pycryptodome, es una bifurcación mucho mejor de él. En caso de tenerlo instalado, igualmente con un simple comando se debería desinstalar:

python3 -m pip uninstall pycrypto

OJO: Todo el tutorial estará hecho para la versión 3 y se debe tener instalado el paquete python-dev y build-essential para su versión de python correspondiente, use su gestor de paquetes en caso de no tenerlo instalado. Como generalmente se usan distribuciones basadas en Debian o Arch Linux, aquí unos simples comandos orientativos:

# Debian
sudo apt-get install build-essential python3-dev
# Arch Linux
sudo pacman -S base-devel

# General
python3 -m pip install pycryptodome

Generando el par de claves

Una vez hemos aprendido los conceptos básicos, además de instalar lo correspondiente, ahora pasemos a la práctica. En este caso primero vamos a generar el par de claves, que como ya saben, serían la clave pública y la clave privada. Empecemos:
import sys
from Crypto.PublicKey import RSA # Importamos el módulo RSA

# El usuario (o sea nosotros) tiene que pasar un número mayor
# o igual 1024 y usando el objeto 'int' convertirmos un string
# a un entero
. bit_size = int(sys.argv[1]) key_format = sys.argv[2]

# Generamos el par de claves. Dependiendo del tamaño y el
# procesamiento de nuestro computador es lo que podrá tardar
.
keys = RSA.generate(bit_size) print("Clave Pública:") # Exportamos la clave pública y la imprimimos. Colocamos como
# argumento '\n\n' en el parámetro 'end' de la función 'print'
# para imprimir dos saltos de líneas y se vea más legible.
#
# 'key_format' se explicará en unos instantes
#
# Usamos el método '.decode(...)' porque al exportarlos estarán
# en 'bytes' y es mejor (para volverlo legible) tenerlo en 'utf-8'.
print(keys.publickey().export_key(key_format).decode(), end='\n\n') print("Clave Privada:") # Hacemos prácticamente lo mismo que lo anterior, pero a diferencia
# de la exportación de la clave pública, no se necesita explicitar
# ningún método.
print(keys.export_key(key_format).decode())

El código está comentado para que vayan comprendiendo cada cosa. Ahora pasemos a ejecutar el script para verlo detenidamente.


No solo es recomendable, es un deber el usar claves mayor o igual que 2048, 1024 ya se considera inseguro, tampoco hay que abusar del tamaño, yo recomiendo 2048 o 3072 bits, aunque la siguiente tabla enumera el tamaño de algunos algoritmos para mantener una seguridad mayor a 2030:


Sobre los formatos, existen tres formatos para usar con pycryptodome: PEM (el predeterminado) que es la codificación basada en texto (útil si se quiere compartir por algún servicio como correo electrónico); OpenSSH, también es una codificación textual que sigue la especificación OpenSSH; por último, DER, codificación binaria.


Usando el formato OpenSSH con un tamaño 2048 bits como el tamaño del par de claves

¡Oh no!, ha ocurrido un error inesperado

En la última imagen se puede apreciar claramente un error, un carácter inválido para 'utf-8', pero simplemente quitando el método '.decode(...)' de la línea 13 y 17, permitiría mostrar los datos:


Cifrado con RSA

No tiene mucho sentido simplemente generar el par de claves, pycryptodome nos permite cifrar y descifrar datos, como se podrá ver a continuación:


Cifrando y descifrando el texto "hola"

Aquí el script:
import sys
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

bit_size = int(sys.argv[1])
key_format = sys.argv[2]
# El usuario (o sea nosotros) pasaremos un texto
# arbitrario para después cifrarlo y descifrarlo
. text2cipher = sys.argv[3] keys = RSA.generate(bit_size) # Importamos la clave pública para cifrar los datos cipher_rsa = PKCS1_OAEP.new(keys.publickey()) # Importamos la clave privada para descifrar los datos decipher_rsa = PKCS1_OAEP.new(keys) # Ciframos los datos. # # Se deben codificar los datos a 'utf-8', por eso está
# presente el método '.encode(...)'
enc_data = cipher_rsa.encrypt(text2cipher.encode()) # Desciframos los datos dec_data = decipher_rsa.decrypt(enc_data) print("Encriptado:") print(enc_data, end='\n\n') print("Desencriptado:") print(dec_data)
Básicamente es el mismo que el primero, y se comentó solo lo necesario para que se tenga una mejor compresión. Por cierto, el script no es muy realista en cuanto a lo que se hace en la vida real, ya que cada vez que lo ejecutemos se va a generar el par de claves y luego se va a cifrar y descifrar el dato. En las próximas entregas se mostrarán algunos trucos para realizar un script realista a lo que se hace en la vida real (como almacenar claves, importarlas, etcétera).

También tengo que aclarar que no se puede cifrar o descifrar datos que fueron codificados con una clave de tamaño superior a la de su generación, pero no ha de haber preocupaciones, en las próximas entregas se enseñará cómo quitar esa limitación.

Lecturas recomendadas

Los siguientes enlaces les servirán si quieren aprender mucho más y prepararse fluidamente para lo que vendrán en las siguientes partes:
  • https://pycryptodome.readthedocs.io
  • https://es.wikipedia.org/wiki/RSA
  • https://es.qwe.wiki/wiki/Key_size
Me despido, espero hayan aprendido tanto como yo lo hice.

~ DtxdF

No hay comentarios :
Write comentarios

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

Powered by Blogger .