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
.
hasher
crea un hash MD5 della password per utilizzarlo come chiave AES.
encrypt
e 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_handler
come gestore per SIGINT.
hasher
crea 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.
decrypt
decripta 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