#!/usr/bin/env python3
"""Criptografía de juguete: Diffie-Hellman, RSA, hash y MAC con números que se ven.

Los números de DH y RSA son diminutos a propósito, para poder seguir las cuentas a mano.
Este código no protege nada: se rompe en menos de un segundo, y la demostración lo hace.
"""
import hashlib
import hmac


def dh_shared(p, g, a, b):
    """Diffie-Hellman: cada lado publica g^x mod p y llega al mismo secreto."""
    big_a = pow(g, a, p)
    big_b = pow(g, b, p)
    return big_a, big_b, pow(big_b, a, p), pow(big_a, b, p)


def dh_break(p, g, public):
    """Logaritmo discreto a la fuerza: con p pequeño, basta probar exponentes."""
    value = 1
    for exponent in range(p):
        if value == public:
            return exponent
        value = value * g % p
    raise ValueError("no hay exponente")


def rsa_keys(p, q, e):
    """Devuelve (n, e, d). d es el inverso de e módulo (p-1)(q-1)."""
    phi = (p - 1) * (q - 1)
    return p * q, e, pow(e, -1, phi)


def rsa_apply(message, exponent, n):
    """La misma operación cifra, descifra, firma y verifica: solo cambia el exponente."""
    if not 0 <= message < n:
        raise ValueError("el mensaje debe estar entre 0 y n-1")
    return pow(message, exponent, n)


def differing_bits(first, second):
    return sum(bin(x ^ y).count("1") for x, y in zip(first, second))


def main():
    print("== Diffie-Hellman con p=2087, g=5")
    p, g, a, b = 2087, 5, 731, 1289
    big_a, big_b, secret_a, secret_b = dh_shared(p, g, a, b)
    print(f"Ana elige a={a} y publica A = g^a mod p = {big_a}")
    print(f"Beto elige b={b} y publica B = g^b mod p = {big_b}")
    print(f"Ana calcula B^a mod p = {secret_a}")
    print(f"Beto calcula A^b mod p = {secret_b}")
    print(f"quien solo vio A y B, probando exponentes, halla a={dh_break(p, g, big_a)}")

    print("\n== RSA con p=61, q=53, e=17")
    n, e, d = rsa_keys(61, 53, 17)
    message = 65
    cipher = rsa_apply(message, e, n)
    print(f"n={n} e={e} d={d}")
    print(f"cifrar con la pública:  {message}^{e} mod {n} = {cipher}")
    print(f"descifrar con la privada: {cipher}^{d} mod {n} = {rsa_apply(cipher, d, n)}")
    signature = rsa_apply(message, d, n)
    print(f"firmar con la privada:  {message}^{d} mod {n} = {signature}")
    print(f"verificar con la pública: {signature}^{e} mod {n} = {rsa_apply(signature, e, n)}")

    print("\n== Hash: un carácter cambia cerca de la mitad de los bits")
    one = hashlib.sha256(b"pagar 100 soles").digest()
    two = hashlib.sha256(b"pagar 900 soles").digest()
    print("sha256('pagar 100 soles') =", one.hex())
    print("sha256('pagar 900 soles') =", two.hex())
    print(f"bits distintos: {differing_bits(one, two)} de {len(one) * 8}")

    print("\n== MAC: el hash solo no autentica, porque cualquiera lo recalcula")
    key = b"clave compartida"
    print("hmac(clave, 'pagar 100 soles') =", hmac.new(key, b"pagar 100 soles", "sha256").hexdigest())
    print("hmac(otra,  'pagar 100 soles') =", hmac.new(b"otra", b"pagar 100 soles", "sha256").hexdigest())


if __name__ == "__main__":
    main()
