Oggi vedremo come creare una chat IRC in Python crossplatform semplice da usare e crittografata!
IRC (Internet Relay Chat) è un protocollo usato per chat online sviluppato alla fine degli anni '80 e permette agli utenti di comunicare tramite canali e IP. Una chat IRC solitamente si appoggia su un server che gestisce le connessioni degli utenti e distribuisce i messaggi fra loro.
L'idea è scrivere due file python server.py e client.py dove i messaggi scambiati tra client e server sono criptati utilizzando l'algoritmo di crittografia AES.

server.py è lo script che ci permetterà di hostare la chat tramite la nostra macchina e con client.py abbiamo la possibilità di inviare messaggi.
All'inizio importiamo le librerie necessarie come socket per la rete, Crypto.Cipher per la crittografia AES e hashlib per l'hashing delle password.
Legge la configurazione da un file ircehf.conf per ottenere l'host, la porta, la password e altre impostazioni.
All'interno del file ci sono varie funzioni per abbellire l'interfaccia come clear_screen, sigint_handler.
hashercrea un hash MD5 della password per utilizzarlo come chiave AES.
encrypte decrypt criptano e decriptano i messaggi usando AES in modalità ECB.
chat_server crea un socket server, accetta connessioni dai client, riceve e decripta i messaggi, e li ritrasmette criptati a tutti i client connessi.
broadcast invia un messaggio a tutti i client connessi, eccetto il mittente e il server stesso.
Il server viene avviato richiamando chat_server.

Requisiti:

Libreria Pycryptodome installata su ambiente virtuale, segui i comandi sulla foto:

server.py

Andiamo ad importare le librerie necessarie:

  • configparser: per leggere configurazioni da un file.
  • base64: per codificare e decodificare i messaggi criptati.
  • sys, socket, select: moduli standard per interazioni di sistema e comunicazioni di rete.
  • Crypto.Cipher import AES: per la crittografia AES.
  • hashlib: per l'hashing delle password.
  • os: per operazioni di sistema come la pulizia dello schermo.
  • signal: per gestire segnali di interruzione (es Ctrl+C).

In seguito restituisce il banner ASCII.
sigint_handler gestisce l'interruzione dell'utente (Ctrl+C) stampando un messaggio e chiudendo il server.
signal.signal registra sigint_handlercome gestore per SIGINT.
hashercrea un hash MD5 di una stringa (password) utilizzando prima SHA-512.
encrypt cripta i dati usando AES in modalità ECB e li codifica in base64.
decryptdecripta i dati da base64 e li decripta usando AES in modalità ECB.
config.read legge la configurazione dal file ircehf.conf.
Estrae HOST, PORT, PASSWORD, VIEW e crea un hash della password.

chat_server funzione principale del server:

  • Crea un socket.
  • Si mette il listening per connessioni e le aggiunge a SOCKET_LIST.
  • Usa select.select per gestire le letture/scritture sui socket.
  • Accetta nuove connessioni e riceve messaggi dai client.
  • Decripta i messaggi ricevuti, li recripta e li invia a tutti i client connessi.
  • Rimuove i client disconnessi dalla lista.
    broadcast: invia un messaggio a tutti i client connessi tranne al mittente.
import configparser
import base64
import sys, socket, select
from Crypto.Cipher import AES
import hashlib
import os
import signal

# Clear the screen based on the OS
def clear_screen():
    if os.name == 'nt':  # For Windows
        os.system('cls')
    else:  # For Linux and Mac
        os.system('clear')

clear_screen()

print(r"""

 ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄       ▄▄▄▄▄▄▄▄▄▄▄  ▄         ▄  ▄▄▄▄▄▄▄▄▄▄▄ 
▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌     ▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░░░░░░░░░░░▌
 ▀▀▀▀█░█▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀      ▐░█▀▀▀▀▀▀▀▀▀ ▐░▌       ▐░▌▐░█▀▀▀▀▀▀▀▀▀ 
     ▐░▌     ▐░▌       ▐░▌▐░▌               ▐░▌          ▐░▌       ▐░▌▐░▌          
     ▐░▌     ▐░█▄▄▄▄▄▄▄█░▌▐░▌               ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄▄▄ 
     ▐░▌     ▐░░░░░░░░░░░▌▐░▌               ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌
     ▐░▌     ▐░█▀▀▀▀█░█▀▀ ▐░▌               ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀ 
     ▐░▌     ▐░▌     ▐░▌  ▐░▌               ▐░▌          ▐░▌       ▐░▌▐░▌          
 ▄▄▄▄█░█▄▄▄▄ ▐░▌      ▐░▌ ▐░█▄▄▄▄▄▄▄▄▄      ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌       ▐░▌▐░▌          
▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░░░░░░░░░░░▌     ▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░▌          
 ▀▀▀▀▀▀▀▀▀▀▀  ▀         ▀  ▀▀▀▀▀▀▀▀▀▀▀       ▀▀▀▀▀▀▀▀▀▀▀  ▀         ▀  ▀           
                                                                                   
                  IRC chat sicura
                    Samueleex

""")

def sigint_handler(signum, frame):
    print('\n[error] user interrupt')
    print("[info] shutting down EHF-IRC \n\n")
    sys.exit()	

signal.signal(signal.SIGINT, sigint_handler)

def hasher(key):
    hash_object = hashlib.sha512(key.encode('utf-8'))
    hexd = hash_object.hexdigest()
    hash_object = hashlib.md5(hexd.encode('utf-8'))
    hex_dig = hash_object.hexdigest()
    return hex_dig	

def encrypt(secret, data):
    BLOCK_SIZE = 32
    PADDING = '{'
    pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
    EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s).encode('utf-8'))).decode('utf-8')
    cipher = AES.new(secret.encode('utf-8'), AES.MODE_ECB)
    encoded = EncodeAES(cipher, data)
    return encoded

def decrypt(secret, data):
    BLOCK_SIZE = 32
    PADDING = '{'
    DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).decode('utf-8').rstrip(PADDING)
    cipher = AES.new(secret.encode('utf-8'), AES.MODE_ECB)
    decoded = DecodeAES(cipher, data)
    return decoded

config = configparser.RawConfigParser()   
config.read(r'ircehf.conf')

HOST = config.get('config', 'HOST')
PORT = int(config.get('config', 'PORT'))
PASSWORD = config.get('config', 'PASSWORD')
VIEW = str(config.get('config', 'VIEW'))
key = hasher(PASSWORD)
SOCKET_LIST = []
RECV_BUFFER = 4096

def chat_server():

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind((HOST, PORT))
    server_socket.listen(10)

    SOCKET_LIST.append(server_socket)

    print("[Server] Started on port " + str(PORT))

    while 1:

        ready_to_read,ready_to_write,in_error = select.select(SOCKET_LIST,[],[],0)

        for sock in ready_to_read:

            if sock == server_socket:
                sockfd, addr = server_socket.accept()
                SOCKET_LIST.append(sockfd)
                print("[Server] User [(%s, %s)] connected" % addr)
            else:
                try:
                    data = sock.recv(RECV_BUFFER).decode('utf-8')
                    data = decrypt(key, data)
                    if data:
                        broadcast(server_socket, sock, encrypt(key, "\r" + data))
                        if VIEW == '1':
                          print(data)
                    else:

                        if sock in SOCKET_LIST:
                            SOCKET_LIST.remove(sock)

                        broadcast(server_socket, sock, encrypt(key, "[Server] [(%s, %s)] Has left the server.\n" % addr))

                except Exception as e:
                    print(f"Error: {e}")
                    broadcast(server_socket, sock, "[Server] User [(%s, %s)] is offline\n" % addr)
                    continue

    server_socket.close()

def broadcast(server_socket, sock, message):
    for socket in SOCKET_LIST:

        if socket != server_socket and socket != sock :
            try :
                socket.send(message.encode('utf-8'))
            except :

                socket.close()

                if socket in SOCKET_LIST:
                    SOCKET_LIST.remove(socket)

if __name__ == "__main__":

    sys.exit(chat_server())

client.py

Importiamo le librerie necessarie:

  • base64: per codificare e decodificare i messaggi criptati.
  • sys, socket, select: moduli standard per interazioni di sistema e rete.
  • Crypto.Cipher import AES: per la crittografia AES.
  • os: per operazioni di sistema come la pulizia dello schermo.
  • hashlib: per l'hashing delle password.
  • signal: per gestire segnali di interruzione (es Ctrl+C).

chat_client funzione principale del client:

  • Verifica il numero di argomenti immessi tramite input su terminale.
  • Estrae host, port, key e uname dagli argomenti della riga di comando.
  • Connette il socket al server.
  • Gestisce la ricezione e l'invio dei messaggi.
  • Cripta i messaggi prima di inviarli al server e decripta i messaggi ricevuti dal server.
  • Restituisce messaggi locali e ricevuti dal server su terminale.
import base64
import sys, socket, select
from Crypto.Cipher import AES
import os
import hashlib
import signal

def clear_screen():
    if os.name == 'nt':  # For Windows
        os.system('cls')
    else:  # For Linux and Mac
        os.system('clear')

clear_screen()

print(r"""

 ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄       ▄▄▄▄▄▄▄▄▄▄▄  ▄         ▄  ▄▄▄▄▄▄▄▄▄▄▄ 
▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌     ▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░░░░░░░░░░░▌
 ▀▀▀▀█░█▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀      ▐░█▀▀▀▀▀▀▀▀▀ ▐░▌       ▐░▌▐░█▀▀▀▀▀▀▀▀▀ 
     ▐░▌     ▐░▌       ▐░▌▐░▌               ▐░▌          ▐░▌       ▐░▌▐░▌          
     ▐░▌     ▐░█▄▄▄▄▄▄▄█░▌▐░▌               ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄▄▄ 
     ▐░▌     ▐░░░░░░░░░░░▌▐░▌               ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌
     ▐░▌     ▐░█▀▀▀▀█░█▀▀ ▐░▌               ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀ 
     ▐░▌     ▐░▌     ▐░▌  ▐░▌               ▐░▌          ▐░▌       ▐░▌▐░▌          
 ▄▄▄▄█░█▄▄▄▄ ▐░▌      ▐░▌ ▐░█▄▄▄▄▄▄▄▄▄      ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌       ▐░▌▐░▌          
▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░░░░░░░░░░░▌     ▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░▌          
 ▀▀▀▀▀▀▀▀▀▀▀  ▀         ▀  ▀▀▀▀▀▀▀▀▀▀▀       ▀▀▀▀▀▀▀▀▀▀▀  ▀         ▀  ▀           
                                                                                   
                  IRC chat sicura
                    Samueleex

""")

def sigint_handler(signum, frame):
    print('\n[error] user interrupt')
    print("[info] shutting down EHF-IRC \n\n")
    sys.exit()

signal.signal(signal.SIGINT, sigint_handler)

def hasher(key):
    hash_object = hashlib.sha512(key.encode('utf-8'))
    hexd = hash_object.hexdigest()
    hash_object = hashlib.md5(hexd.encode('utf-8'))
    hex_dig = hash_object.hexdigest()
    return hex_dig

def encrypt(secret, data):
    BLOCK_SIZE = 32
    PADDING = '{'
    pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
    EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s).encode('utf-8'))).decode('utf-8')
    cipher = AES.new(secret.encode('utf-8'), AES.MODE_ECB)
    encoded = EncodeAES(cipher, data)
    return encoded

def decrypt(secret, data):
    BLOCK_SIZE = 32
    PADDING = '{'
    DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).decode('utf-8').rstrip(PADDING)
    cipher = AES.new(secret.encode('utf-8'), AES.MODE_ECB)
    decoded = DecodeAES(cipher, data)
    return decoded

def chat_client():
    if len(sys.argv) < 5:
        print('Usage: python dream-irc.py [hostname] [port] [password] [username]')
        sys.exit()

    host = sys.argv[1]
    port = int(sys.argv[2])
    key = sys.argv[3]
    key = hasher(key)
    uname = sys.argv[4]

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(2)

    try:
        s.connect((host, port))
    except:
        print("\033[95m" + 'Unable to connect' + "\033[0m")
        sys.exit()

    print("Connected to remote host. Your messages will be securely transmitted.")
    sys.stdout.write("\033[34m" + '\n[local] #  ' + "\033[0m")
    sys.stdout.flush()

    while True:
        socket_list = [sys.stdin, s]
        read_sockets, write_sockets, error_sockets = select.select(socket_list, [], [])

        for sock in read_sockets:
            if sock == s:
                data = sock.recv(4096).decode('utf-8')

                if not data:
                    print("\033[95m" + "\nDisconnected from server" + "\033[0m")
                    sys.exit()
                else:
                    data = decrypt(key, data)
                    sys.stdout.write(data)
                    sys.stdout.write("\033[34m" + '\n[local] #  ' + "\033[0m")
                    sys.stdout.flush()
            else:
                msg = sys.stdin.readline()
                msg = '[ ' + uname + ': ] ' + msg
                msg = encrypt(key, msg)
                s.send(msg.encode('utf-8'))
                sys.stdout.write("\033[34m" + '\n[local] #  ' + "\033[0m")
                sys.stdout.flush()

if __name__ == "__main__":
    sys.exit(chat_client())

ircehf.conf:

[config]

HOST = 127.0.0.1

PORT = 43434

PASSWORD = password

VIEW = 0

Powered by: FreeFlarum.
(remove this footer)