Ir al contenido principal

Criptografía (CCLXXXVIII): Descifrado y verificación firma digital ElGamal en python

En un post anterior puse un script en python para  implementar la firma digital y el cifrado con el esquema de firma digital y el esquema de cifrado ElGamal, respectivamente, y en éste incluyo otro script en python para  implementar el descifrado y la verificación de la firma digital con esos mismos esquemas, es decir, para las operaciones que se realizan en la comunicación en el lado del receptor.

Antes  de ver el script, creo que conviene recordar cómo se realizan dichas operaciones, para lo que recomiendo leer este post en el que expliqué este asunto referido al caso de RSA, pero que es lo mismo para el caso de aplicar los esquemas de firma digital y de cifrado ElGamal.

El script que pongo a continuación implementa las operaciones de descifrado de la clave de sesión mediante el esquema de cifrado ElGamal y el descifrado del texto en claro mediante el algoritmo AES de criptografía simétrica en modo de operación CBC, mientras que la verificación de la firma digital se realiza utilizando el esquema de firma ElGamal

El par de claves ElGamal, publica y privada, para los dos usuarios participantes en la comunicación, emisor y receptor, son las mismas que las que generé mediante el script que puse en esta entrada y utilicé en el post citado al principioclave pública emisorclave privada emisorclave pública receptorclave privada receptor.

Para simular la recepción del mensaje cifrado utilizo un archivo que se ha creado tras la ejecución del script que puse en el post citado al principio, comunicación.txt, y que contiene, codificada en base64, la siguiente información: 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.

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 el descifrado y verificación de la firma digital ElGamal:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# DESCIFRADO Y VERIFICACIÓN DE FIRMA ELGAMAL:
#
# Descifrar y verificar firma digital utilizando el criptosistema ElGamal.
#
# http://mikelgarcialarragan.blogspot.com/

import hashlib
from Crypto.Cipher import AES
import pyasn1.type.univ
from pyasn1.codec.der import decoder
from base64 import b64decode
from exponenciacion_modular_rapida import exp_modular_rapida

# CIFRADO AES (CRIPTOGRAFÍA SIMÉTRICA).
def descifrar_aes(criptograma,clave):
    iv = criptograma[:AES.block_size]
    descifrar = AES.new(clave, AES.MODE_CBC, iv)
    texto_claro = descifrar.decrypt(criptograma[AES.block_size:]).decode()
    return texto_claro[:-ord(texto_claro[len(texto_claro) - 1:])]

# 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. Descifrar y verificar firma digital.")
        print ("2. Salir.")
        print ("")
        opcion = input("Por favor, seleccione una opción: ")
        if opcion == "1":
            print ("")
            print ("--- DESCIFRAR Y VERIFICAR FIRMA DIGITAL:")
            # Descifrar y verificar firma digital: se reciben 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.
            f_comunicacion = open("comunicacion.txt")
            datos_comunicacion = f_comunicacion.readlines()
            print ("[+] Clave de sesión AES cifrada (base64): (", datos_comunicacion[0].rstrip(), ",", datos_comunicacion[1].rstrip(),")")
            print ("[+] Criptograma (base64):", datos_comunicacion[2].rstrip())
            print ("[+] Firma digital (base64): (", datos_comunicacion[3], ",", datos_comunicacion[4].rstrip(),")")
            # Se lee la clave privada del receptor para descifrar la clave de sesión.
            datos_clave_privada = clave_privada_elgamal("private_receptor.pem")
            print ("[+] Clave privada del receptor:")
            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 descifra la clave de sesión AES con la que se cifró el texto en claro con la clave
            # privada del receptor (criptografía asimétrica).
            clave_sesion = exp_modular_rapida(int(b64decode(datos_comunicacion[0])), datos_clave_privada[0][0]-1-datos_clave_privada[0][3],int(datos_clave_privada[0][0]))
            clave_sesion = (clave_sesion * int(b64decode(datos_comunicacion[1])))%datos_clave_privada[0][0]
            print ("[+] Clave de sesión AES descifrada (hexadecimal):", hex(clave_sesion)[2:])
            # Se descifra el criptograma con la clave de sesión AES (criptografía simétrica).
            texto_claro = descifrar_aes(b64decode(datos_comunicacion[2]),bytes.fromhex(hex(clave_sesion)[2:]))
            print ("[+] Texto claro descifrado con la clave de sesión AES:", texto_claro)
            # Se calcula el hash SHA-256 (256 bits) del texto en claro descifrado.
            h_M = (hashlib.sha256(texto_claro.encode()).digest())
            print ("[+] Hash calculado para el texto en claro descifrado (hexadecimal):", h_M.hex())
            # Se lee la clave pública del emisor para verificar la firma digital.
            datos_clave_publica = clave_publica_elgamal("public_emisor.pem")
            print ("[+] Clave pública del emisor:")
            print ("    p:", datos_clave_publica[0][0])
            print ("    g:", datos_clave_publica[0][1])
            print ("    k:", datos_clave_publica[0][2])
            # Se realizan los cálculos utilizando la clave pública del emisor (criptografía asimétrica)
            # para verificar la firma recibida.
            g_elevado_h_M = exp_modular_rapida(datos_clave_publica[0][1], int(h_M.hex(),16), int(datos_clave_publica[0][0]))
            print ("[+] g ** h'(M) =", g_elevado_h_M)
            k_elevado_r = exp_modular_rapida(datos_clave_publica[0][2], int(b64decode(datos_comunicacion[3])), int(datos_clave_publica[0][0]))
            r_elevado_s = exp_modular_rapida(int(b64decode(datos_comunicacion[3])), int(b64decode(datos_comunicacion[4])), int(datos_clave_publica[0][0]))
            print ("[+] (k ** r) * (r ** s) =", (k_elevado_r *  r_elevado_s)%datos_clave_publica[0][0])
            if g_elevado_h_M == (k_elevado_r *  r_elevado_s)%datos_clave_publica[0][0]:
                print ("[+] Se ha verificado la firma digital del contenido de la comunicación con el resultado de FIRMA DIGITAL VÁLIDA.")
            else:
                print ("[+] Se ha verificado la firma digital del contenido de la comunicación con el resultado de FIRMA DIGITAL NO VÁLIDA.")
        elif opcion == "2":
            print ("*** FIN ******************************************")
            salir = True
        else:
            print ("*** ERROR: Opción no válida.")

if __name__ == '__main__':
    main()

Lo ejecuto:

Tras leerse del fichero que simula la comunicación, comunicacion.txtel par de valores correspondientes al cifrado de la clave de sesión AES, el criptograma y el par de valores correspondientes a la firma digitallo primero que hace el script es descifrar la clave de sesión AES con la clave privada del receptor (criptografía asimétricay descifrar el texto en claro cifrado con ella por el emisor (criptografía simétrica).

Lo siguiente que hace el script es calcular el hash SHA-256 del texto en claro descifrado.

Y, finalmente, el script lee la clave pública del emisor para validar mediante el esquema de firma digital ElGamal (criptografía asimétrica) la firma digital que se ha recibido.

Tal y como se puede observar en la figura anterior, en este caso el proceso de verificación de la firma digital termina con éxito y, por tanto, quedarían acreditadas tanto la autenticidad como la integridad del mensaje recibido.

Quizás también te interese:

Comentarios

Entradas populares de este blog

Criptografía (I): cifrado Vigenère y criptoanálisis Kasiski

Hace unos días mi amigo Iñaki Regidor ( @Inaki_Regidor ), a quien dedico esta entrada :), compartió en las redes sociales un post titulado "Criptografía: el arte de esconder mensajes"  publicado en uno de los blogs de EiTB . En ese post se explican ciertos métodos clásicos para cifrar mensajes , entre ellos el cifrado de Vigenère , y , al final del mismo, se propone un reto consistente en descifrar un mensaje , lo que me ha animado a escribir este post sobre el método Kasiski  para atacar un cifrado polialfabético ( conociendo la clave descifrar el mensaje es muy fácil, pero lo que contaré en este post es la forma de hacerlo sin saberla ). El mensaje a descifrar es el siguiente: LNUDVMUYRMUDVLLPXAFZUEFAIOVWVMUOVMUEVMUEZCUDVSYWCIVCFGUCUNYCGALLGRCYTIJTRNNPJQOPJEMZITYLIAYYKRYEFDUDCAMAVRMZEAMBLEXPJCCQIEHPJTYXVNMLAEZTIMUOFRUFC Como ya he dicho el método de Vigenère es un sistema de sustitución polialfabético , lo que significa que, al contrario que en un sistema de

Criptografía (XXIII): cifrado de Hill (I)

En este post me propongo explicar de forma comprensible lo que he entendido sobre el cifrado de Hill , propuesto por el matemático Lester S. Hill , en 1929, y que se basa en emplear una matriz como clave  para cifrar un texto en claro y su inversa para descifrar el criptograma correspondiente . Hay tres cosas que me gustan de la criptografía clásica, además de que considero que ésta es muy didáctica a la hora de comprender los sistemas criptográficos modernos: la primera de ellas es que me "obliga" a repasar conceptos de matemáticas aprendidos hace mucho tiempo y, desgraciadamente, olvidados también hace demasiado tiempo, y, por consiguiente, que, como dice  Dani , amigo y coautor de este blog, me "obliga" a hacer "gimnasia mental"; la segunda es que, en la mayoría de las ocasiones, pueden cifrarse y descifrase los mensajes, e incluso realizarse el criptoanálisis de los criptogramas, sin más que un simple lápiz y papel, es decir, para mi es como un pasat

¿Qué significa el emblema de la profesión informática? (I)

Todas o muchas profesiones tienen un emblema que las representa simbólicamente y en el caso de la  informática: " es el establecido en la resolución de 11 de noviembre de 1977  para las titulaciones universitarias superiores de informática, y  está constituido por una figura representando en su parte central  un  núcleo toroidal de ferrita , atravesado por  hilos de lectura,  escritura e inhibición . El núcleo está rodeado por  dos ramas : una  de  laurel , como símbolo de recompensa, y la otra, de  olivo , como  símbolo de sabiduría. La  corona  será la  de la casa real  española,  y bajo el escudo se inscribirá el acrónimo de la organización. ". Veamos los diferentes elementos tomando como ejemplo el emblema del COIIE/EIIEO (Colegio Oficial de Ingenieros en Informática del País Vasco/ Euskadiko Informatikako Ingeniarien Elkargo Ofiziala ) . Pero no sólo el COIIE/EIIEO adopta el emblema establecido en dicha resolución, sino que éste se adopta también como im