En este post incluyo un script en python para implementar la firma digital y el cifrado con el esquema de firma digital y el esquema de cifrado ElGamal.
Antes de poner el script, recordar que en este post referido a RSA, decía que los esquemas modernos de cifrado utilizan conjuntamente los conceptos de clave pública y clave privada, es decir, el mensaje o archivo completo se cifra con un algoritmo de clave privada o simétrica (por ejemplo, AES) y luego la clave empleada en ese cifrado (clave de sesión), a su vez, se cifra con un algoritmo de clave pública o asimétrica (por ejemplo, RSA):
Por tanto, como se ve en la figura anterior, el texto en claro se cifra mediante una clave de sesión, un número correspondiente a cada mensaje particular, y la clave de sesión se cifra utilizando la clave pública del receptor, de tal manera que sólo el receptor puede descifrar la clave de sesión mediante su clave privada (criptografía asimétrica) y posteriormente descifrar el criptograma o texto en claro cifrado mediante la clave de sesión (criptografía simétrica).
De esta forma, sólo se podrá obtener la clave de sesión (clave simétrica) si se está en posesión de la clave privada, y, por consiguiente, si no se tiene esta última no se puede descifrar el criptograma o texto en claro cifrado.
Este esquema de cifrado se puede utilizar para mantener la confidencialidad de los mensajes y los archivos, sin tardar demasiado ni consumir demasiados recursos informáticos, y el esquema de firma digital se puede utilizar para firmar digitalmente los mensajes y los archivos, es decir, para garantizar al receptor que el emisor es realmente quien los ha enviado (autenticidad: el emisor es quien dice ser) y para comprobar que no han sido interceptados y alterados por terceros (integridad); con la característica adicional de no repudio, tanto en origen (el emisor no puede negar que creó y envió esos textos en claro) como en destino (el receptor no puede negar que los recibió y recibió exactamente esos textos en claro).
El script que pongo a continuación implementa las operaciones de firma digital del texto en claro y de cifrado de la clave de sesión mediante ElGamal, y el cifrado del texto en claro mediante el algoritmo AES de criptografía simétrica en modo de operación CBC, es decir, las operaciones que se realizan en la comunicación desde el lado del emisor.
Previamente he generado, mediante el script que puse en esta entrada, el par de claves ElGamal, publica y privada, para los dos usuarios participantes en la comunicación, emisor y receptor: clave pública emisor, clave privada emisor, clave pública receptor, clave privada receptor.
Asimismo, para que el script funcione se necesita importar el siguiente módulo en el programa principal, que se encargará de hacer de forma eficiente las operaciones de exponenciación modular.
#!/usr/bin/env python # -*- coding: utf-8 -*- # ALGORITMO DE EXPONENCIACIÓN MODULAR RÁPIDA: # # Algoritmo utilizado en operaciones de exponenciación modular # con números muy grandes con objeto de hacerlas de forma # más eficiente. # # http://mikelgarcialarragan.blogspot.com/ def exp_modular_rapida(b, e, m): c = 1 e = bin(e)[2:] for i in range(0, len(e)): c = pow(c, 2, m) if e[i]=="1": c = c*b%m return c
El script es el siguiente:
- Script python para la firma digital y cifrado ElGamal:
#!/usr/bin/env python # -*- coding: utf-8 -*- # FIRMA Y CIFRADO ELGAMAL: # # Firma digitalmente y cifra utilizando el esquema de firma y de cifrado ElGamal. # # http://mikelgarcialarragan.blogspot.com/ import hashlib import random import math from Crypto import Random from Crypto.Util.Padding import pad from Crypto.Cipher import AES import pyasn1.type.univ from pyasn1.codec.der import decoder from base64 import b64encode, b64decode from exponenciacion_modular_rapida import exp_modular_rapida # CIFRADO AES (CRIPTOGRAFÍA SIMÉTRICA). def cifrar_aes(texto_claro,clave): iv = Random.new().read(AES.block_size) print("[+] IV (hexadecimal):", iv.hex()) cifrar = AES.new(clave, AES.MODE_CBC, iv) criptograma = iv + cifrar.encrypt(pad(texto_claro.encode(), AES.block_size)) return criptograma # OBTENER DATOS DE LA CLAVE PÚBLICA ELGAMAL def clave_publica_elgamal(fichero_pem): f_clave_publica_elgamal = open(fichero_pem, "r") clave_publica = f_clave_publica_elgamal.read() clave_publica = clave_publica.replace("-----BEGIN ELGAMAL PUBLIC KEY-----","") clave_publica = clave_publica.replace("-----END ELGAMAL PUBLIC KEY-----","") datos_clave_publica_elgamal = pyasn1.codec.der.decoder.decode(b64decode(clave_publica.encode('ascii'))) f_clave_publica_elgamal.close() return datos_clave_publica_elgamal # OBTENER DATOS DE LA CLAVE PRIVADA ELGAMAL def clave_privada_elgamal(fichero_pem): f_clave_privada_elgamal = open(fichero_pem, "r") clave_privada =f_clave_privada_elgamal.read() clave_privada = clave_privada.replace("-----BEGIN ELGAMAL PRIVATE KEY-----","") clave_privada = clave_privada.replace("-----END ELGAMAL PRIVATE KEY-----","") datos_clave_privada_elgamal = pyasn1.codec.der.decoder.decode(b64decode(clave_privada.encode('ascii'))) f_clave_privada_elgamal.close() return datos_clave_privada_elgamal def main(): # MENÚ: # Se presenta el menú para que se seleccione una opción. salir = False while not salir: print ("") print ("*** MENÚ *****************************************") print ("1. Firmar digitalmente y cifrar clave de sesión AES.") print ("2. Salir.") print ("") opcion = input("Por favor, seleccione una opción: ") if opcion == "1": print ("") print ("--- FIRMAR DIGITALMENTE Y CIFRAR CLAVE DE SESIÓN:") # Firmar digitalmente y cifrar: Se introducen el texto en claro y la clave. texto_claro = clave = "" while texto_claro == "": texto_claro = input('Texto en claro a cifrar: ') if texto_claro != "": while clave == "": clave = input('Clave: ') if clave != "": # Se genera la clave de sesión AES con la que se va a cifrar el # texto en claro y que, a su vez, va a ser cifrada con la clave # pública del destinatario, que será descifrada por éste con su # clave privada (criptografía asimétrica) para, a su vez, descifrar # el criptograma (criptografía simétrica). # La clave se genera a partir de la clave introducida por el usuario, # que puede tener cualquier longitud, y de la cual se calcula su # hash SHA-256 (256 bits). # Ese hash será la clave de sesión (32 bytes). clave_sesion = (hashlib.sha256(clave.encode()).digest()) print ("[+] Clave de sesión AES generada (hexadecimal):", clave_sesion.hex()) criptograma = cifrar_aes(texto_claro,clave_sesion) print ("[+] Texto claro cifrado con la clave de sesión AES (hexadecimal):", criptograma.hex()) # Se lee la clave privada del emisor para firmar digitalmente el texto en claro. datos_clave_privada = clave_privada_elgamal("private_emisor.pem") print ("[+] Clave privada del emisor:") print (" p:", datos_clave_privada[0][0]) print (" g:", datos_clave_privada[0][1]) print(" k:", datos_clave_privada[0][2]) print (" a:", datos_clave_privada[0][3]) #Se genera aleatoriamente un número primo ('h'), mayor o igual que 1 y menor o igual que p - 1, #coprimo o primo relativo con phi(p) while True: h = random.randint(1, datos_clave_privada[0][0]-1) if math.gcd(h, datos_clave_privada[0][0]-1) == 1: break print ("[+] Número primo 'h' coprimo con phi(p) generado aleatoriamente:", h) # Se calcula la rúbrica del emisor ('r'), primera parte de la firma digital. r = exp_modular_rapida(datos_clave_privada[0][1], h, int(datos_clave_privada[0][0])) # Se calcula 's', segunda parte de la firma digital. h_M = (hashlib.sha256(texto_claro.encode()).digest()) s = ((int(h_M.hex(),16) - datos_clave_privada[0][3] * r) * pow(h,-1,int(datos_clave_privada[0][0])-1))%(datos_clave_privada[0][0]-1) print ("[+] Firma digital: (", r, ",", s, ")") # Se lee la clave pública del receptor para cifrar la clave de sesión AES. datos_clave_publica = clave_publica_elgamal("public_receptor.pem") print ("[+] Clave pública del receptor:") print (" p:", datos_clave_publica[0][0]) print (" g:", datos_clave_publica[0][1]) print(" k:", datos_clave_publica[0][2]) b = random.randint(1, (datos_clave_publica[0][0])-1) c1 = exp_modular_rapida(datos_clave_publica[0][1], b,int(datos_clave_publica[0][0])) c2 = (int(clave_sesion.hex(),16) * exp_modular_rapida(datos_clave_publica[0][2], b, int(datos_clave_publica[0][0])))%datos_clave_publica[0][0] print ("[+] Clave de sesión cifrada con la clave pública Elgamal del receptor (hexadecimal): (", hex(c1)[2:], ",", hex(c2)[2:], ")") f_comunicacion = open("comunicacion.txt", "w") f_comunicacion.write((b64encode(str(c1).encode())).decode() + "\n") f_comunicacion.write((b64encode(str(c2).encode())).decode() + "\n") f_comunicacion.write(b64encode(criptograma).decode() + "\n") f_comunicacion.write((b64encode(str(r).encode()).decode()) + "\n") f_comunicacion.write((b64encode(str(s).encode()).decode())) f_comunicacion.close() else: print ("*** ERROR: Por favor, introduzca la clave.") else: print ("*** ERROR: Por favor, introduzca el texto en claro a cifrar.") elif opcion == "2": print ("*** FIN ******************************************") salir = True else: print ("*** ERROR: Opción no válida.") if __name__ == '__main__': main()
Lo ejecuto:
Tras introducirse el mensaje o texto en claro a cifrar, lo primero que hace el script es generar la clave AES, calculando el hash SHA-256 de la clave introducida por el usuario, con la que se cifra el texto en claro y se obtiene el criptograma o texto en claro cifrado (criptografía simétrica).
Lo siguiente que hace el script es leer la clave privada del emisor, almacenada en formato PEM en un fichero, y generar aleatoriamente un número primo ('h'), mayor o igual que 1 y menor o igual que p - 1, coprimo o primo relativo con ϕ(p), para firmar digitalmente.
Posteriormente, el script lee la clave pública del receptor, almacenada en formato PEM en otro fichero, para cifrar mediante el algoritmo ElGamal (criptografía asimétrica) la clave de sesión AES con la que se ha cifrado el mensaje o texto en claro (criptografía simétrica).
Y, finalmente, para simular el envió de la comunicación, se genera un fichero, comunicación.txt, en el que se almacenan, codificados en base64, lo siguientes datos: el par de valores correspondientes al cifrado de la clave de sesión AES, el criptograma y el par de valores correspondientes a la firma digital.
De esta forma, cuando el receptor reciba la comunicación, lo primero que se hará será descifrar la clave de sesión AES con su clave privada (criptografía asimétrica) y con ésta descifrar el criptograma (criptografía simétrica), para, posteriormente, verificar la firma digital con la clave pública del emisor (criptografía asimétrica).
En un post posterior pondré el script en python para implementar esto último.
Quizás también te interese:
Comentarios
Publicar un comentario