Criptografía en Python - RSA

Índice

¿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 'nn' 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='nn') 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='nn') 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

Deja una respuesta

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

Subir
White Monkey