Dopo aver visto la crittografia simmetrica in Python ora come promesso vediamo la crittografia asimmetrica.
La crittografia asimmetrica (o a doppia chiave) è un metodo di crittografia in genere molto sicuro dove la chiave pubblica è nota a tutti mentre la chiave privata è conosciuta solo dal mittente.
Quando il mittente invia un messaggio viene criptato con la chiave pubblica del destinatario. Il destinatario poi decodifica il messaggio con la sua chiave privata e così via.

Ora vediamo un esempio in Python dove ad una chat viene implementata una crittografica asimmetrica tramite libreria RSA:

import rsa
(sam_pub, sam_priv) = rsa.newkeys(512)
(mar_pub, mar_priv) = rsa.newkeys(512)
while True:
    sam_msg = input("Samuele: ")
    encrypted_msg = rsa.encrypt(sam_msg.encode(), mar_pub)
    print("Samuele ha inviato il seguente messaggio cifrato:", encrypted_msg)
    decrypted_msg = rsa.decrypt(encrypted_msg, mar_priv).decode()
    print("Marco: Ho ricevuto il messaggio decifrato:", decrypted_msg)
    mar_msg = input("Marco: ")
    encrypted_msg = rsa.encrypt(mar_msg.encode(), sam_pub)
    print("Marco ha inviato il seguente messaggio cifrato:", encrypted_msg)
    decrypted_msg = rsa.decrypt(encrypted_msg, sam_priv).decode()
    print("Samuele: Ho ricevuto il messaggio decifrato:", decrypted_msg)

Output:

Schema di funzionamento:

4 mesi dopo

Proviamo a riscrivere il codice creando la nostra logica di crittografia asimmetrica senza usare librerie esterne come RSA o simili, solo una libreria (random) per la generazione di numeri.
Per farlo bisogna usare una funzione particolare, quella di Eulero che restituisce il numero degli interi positivi minori di n che sono coprimi rispetto a n, cioè che non hanno fattori primi comuni con n.

  • Per la chiave privata per garantire la sicurezza d deve essere l'inverso moltiplicativo di e modulo φ(n). Significa che d * e ≡ 1 (mod φ(n)).
  • Per la chiave pubblica l'esponente e deve essere un numero intero positivo minore di φ(n) e coprimo con φ(n).

Vediamo come funziona il codice, inizialmente la funzione generate_key_pair() genera una coppia di chiavi RSA casuali per Samuele e Marco. In questa funzione si sceglie due numeri primi casuali, si calcola il modulo n e si applica la logica della funzione di Eulero (phi), quindi si genera un esponente pubblico "e" e privato "d". In conclusione restituisce le chiavi pubbliche e private come tuple.
Le funzioni gcd(a, b) e mod_inverse(a, m) vengono usate rispettivamente per calcolare l'mcd tra due numeri e l'inverso modulo indispensabile per la restituzione.
encrypt(message, public_key) prende un messaggio in input e lo cripta usando la chiave pubblica RSA generato in precedenza. Il messaggio viene suddiviso in caratteri e ognuno viene convertito nel suo valore ASCII ed elevato all'esponente (pubblico) e, Infine il risultato è una lista di valori criptati illeggibili.
decrypt(encrypted_msg, private_key) prende un messaggio criptato e lo decripta usando la chiave privata RSA, anche quella generata all'inizio con Eulero. I valori criptati vengono elevati all'esponente privato d, il risultato è una sfilza di valori ASCII che vengono convertiti in caratteri e concatenati per ottenere il messaggio leggibile.
Importante il ciclo while True dove viene eseguito un loop infinito in cui Samuele e Marco possono scambiarsi messaggi.
Riassumendo Samuele invia un messaggio, lo cripta utilizzando la chiave pubblica di Marco, Marco lo decripta utilizzando la sua chiave privata. Poi Marco invia un messaggio lo cripta usando la chiave pubblica di Samuele e Samuele lo decripta utilizzando la sua chiave privata continuando così all'infinito.

import random

def generate_key_pair():
    # Funzione di Eulero
    prime_numbers = [n for n in range(2, 100) if all(n % i != 0 for i in range(2, int(n**0.5) + 1))]
    p = random.choice(prime_numbers)
    q = random.choice(prime_numbers)
    while p == q:
        q = random.choice(prime_numbers)

    n = p * q
    phi_n = (p - 1) * (q - 1)

    e = random.randint(2, phi_n - 1)
    while gcd(e, phi_n) != 1:
        e = random.randint(2, phi_n - 1)

    d = mod_inverse(e, phi_n)

    public_key = (n, e)
    private_key = (n, d)

    return public_key, private_key

def gcd(a, b):
    # mcd di due numeri
    while b != 0:
        a, b = b, a % b
    return a

def mod_inverse(a, m):
    # Calcola l'inverso modulo di a rispetto a m
    if gcd(a, m) != 1:
        return None

    u1, u2, u3 = 1, 0, a
    v1, v2, v3 = 0, 1, m

    while v3 != 0:
        q = u3 // v3
        v1, v2, v3, u1, u2, u3 = (u1 - q * v1), (u2 - q * v2), (u3 - q * v3), v1, v2, v3

    return u1 % m

def encrypt(message, public_key):
    # Cripta con pubblica RSA
    n, e = public_key
    encrypted_msg = [pow(ord(c), e, n) for c in message]
    return encrypted_msg

def decrypt(encrypted_msg, private_key):
    # Decripta privata RSA
    n, d = private_key
    decrypted_msg = [chr(pow(c, d, n)) for c in encrypted_msg]
    return ''.join(decrypted_msg)

# Generazione chiave per utenti...
sam_pub, sam_priv = generate_key_pair()
mar_pub, mar_priv = generate_key_pair()

while True:
    sam_msg = input("Samuele: ")
    encrypted_msg = encrypt(sam_msg, mar_pub)
    print("Samuele ha inviato il seguente messaggio cifrato:", encrypted_msg)
    decrypted_msg = decrypt(encrypted_msg, mar_priv)
    print("Marco: Ho ricevuto il messaggio decifrato:", decrypted_msg)

    mar_msg = input("Marco: ")
    encrypted_msg = encrypt(mar_msg, sam_pub)
    print("Marco ha inviato il seguente messaggio cifrato:", encrypted_msg)
    decrypted_msg = decrypt(encrypted_msg, sam_priv)
    print("Samuele: Ho ricevuto il messaggio decifrato:", decrypted_msg)

Powered by: FreeFlarum.
(remove this footer)