I protocolli di Internet sono stati progettati per performance e affidabilità.
Protezione:
- autenticità: autenticità dell'origine, assicurarsi che l'origine sia quella che dice di essere
- confidenzialità: proteggere il contenuto delle informazioni
#Nota si parla di rilevazione dell'autenticità, non della prevenzione. Se il canale è inaffidabile l'unica cosa che si può fare è rilevare, ad esempio, la modifica dei messaggi.
- A: mittente -> Alice
- B: destinatario -> Bob
- Eve: cattivo -> Eavesdropping = origliare
Dati at rest: dati statici memorizzati su qualsivoglia supporto Dati in motion: dati in movimento su un canale di comunicazione
Il secondo scenario è quello per cui sono stati sviluppati gli algoritmi di crittografia. Il primo scenario, in cui già l'accesso fisico è improbabile, la crittografia diventa un caso di defence in depth. In altri casi, come le trasmissioni wireless, la crittografia è il primo livello di sicurezza.
Garanzie di sicurezza:
- confidenzialità
- integrità (e/o autenticità)
- availability (= disponibilità): DOS, Cryptolocker, etc.
- accountability: progettare il sistema in modo tale da poter risalire alla causa (persona o macchina) del problema
#Nota tipicamente non si parla di availability nel contesto delle comunicazioni sicure, in quanto l'accesso fisico al canale, può comportare incondizionatamente (dalle scelte informatiche) l'indisponibilità dei dati
Il termine setting si riferisce al modello usato per rappresentare lo scenario per il quale lo schema crittografico è progettato.
Si divide in:
- setting simmetrico:
$encryption Key = decryption Key$ - setting asimmetrico:
$encryption Key \neq decryption Key$
In un protocollo di comunicazione packet oriented and half-duplex, il setting più semplice che si può usare è quello delle lettere sicure: secure envelopes
ciphertext = encrypt(msg) --------------> msg = decrypt(ciphertext)
La crittografia è il campo che studia come trasformare un pezzo di informazione in dati non intelligibili da un attaccante razionale. Nel caso più semplice si parla di cifratura e decifrazione.
Questo garantisce:
- confidenzialità, in quanto Eve non potrebbe leggere il contenuto del messaggio
- integrità, che implica l'autenticità dell'origine, in quanto la compromissione può avvenire solamente rispetto alla sorgente dei dati.
#Nota la best-practice attuale prevede di proteggere tutti i dati della comunicazione
Ulteriore problema: sia in caso di crittografia simmetrica che in caso di crittografia asimmetrica A e B devono condividere/conoscere la chiave. Come scambiarla sul canale insicuro? il problema si ramifica sulla scelta di un protocollo che permetta lo scambio in modo sicuro, ad esempio Diffie-Hellman
Siamo sempre in presenza di 3 elementi:
- keygen
- encrypt che ritorna un messaggio cifrato probabilisticamente, ovvero diverso ad ogni generazione. Un algo che ritorna la stessa cifratura due volte consecutive è insicuro. L'attaccante può capire che lo stesso messaggio è stato mandato più volte.
- decrypt
from cryptography import fernet
k = fernet.Fernet.generate_key() # di default ritorna una chiave sufficientemente sicura
cipher = Fernet(k)
ct = cipher.encrypt(b'hello')
msg = cipher.decrypt(ct)
#Attenzione le funzioni di crittografia non proteggono rispetto alla dimensione del messaggio cifrato. In casi di messaggi "enumerati", con dimensioni differenti per ogni elemento, come un sistema che risponde con YES o NO. A livello applicativo bisogna prevedere funzionalità di padding se il protocollo non lo gestisce. In generale: tieni conto dell'entropia del sistema.
#Vedi: AES-CBC, AES-CTR, Chacha20, 3DES-CBC, rc4. Questi standard crittografici proteggono la confidenzialità ma non l'autenticità.
La funzione di cifratura dovrebbe rilevare la compromissione dei dati (garantendo integrità e autenticità):
ct = cipher.encrypt(b'hello')
ct2 = cy[:-1]
cipher.decrypt(ct2) # Deve segnalare evento di manipolazione/compromissione
#Nota non trascurare eccezioni o valori di ritorni di funzioni di decifrazione.
In generale, una funzione di hash prende in ingresso un valore e ritorna un digest di dimensione fissa.
Le funzioni di hash vengono usate nel campo di DS per costruire DS associative con accesso costante (a meno di aggiornamenti). Per disambiguare si parla quindi di funzioni hash crittografiche resistenti alle collisioni. Si parla di resistenza perché ottenere un valore biunivoco per ogni input è impossibile, dato che il dominio è più grande del codominio della funzione. Si tratta quindi di un'improbabilità delle collisioni.
Gli attori in gioco sono:
- content provider: sorgente originale dei dati alla quale l'user arriva
- mirror website (infrastructure): sorgente alternativa dei dati, a cui user viene reindirizzato
- user: desidera i dati
Questo modello viene usato per le distribuzioni Linux.
Protocollo che consente di garantire l'autenticità e l'integrità dei dati (rilevare manipolazioni):
- user arriva a content provider, che gli fornisce H = hash(data)
- user viene reindirizzato al mirror
- user scarica data da mirror
- user ricalcola hash(data) e lo confronta con H
#Nota il compito di verificare l'autenticità della sorgente è out-of-band e non riguarda questo algoritmo. Infatti un attaccante razionale potrebbe ricalcolare l'hash per il messaggio da lui modificato. È per questo che CRC e simili suppongono eventi stocastici non razionali.
Questa classe di problemi si può inscatolare nell'autenticità dell'outsourcing
Molte funzioni hash "storiche" sono deprecate, in quanto sono state trovate delle collisioni note:
- md5 - deprecata -
md5sum
- sha1 (o sha160) - Standard Hash Algorithm - deprecata -
shasum
- sha2 (224, 256, 384, 512) - ok, rafforza sha1
- sha3 (224, 256, 384, 512) - ok, creata su primitive diverse da sha2
Il suffisso delle funzioni (sha256, sha3_224) indica il numero di bit (dimensione fissa, per definizione) la funzione ritorna. Ricorda che i tool da riga di comando, che tipicamente ritornano dei valori hex, raddoppiano il numero di byte richiesto per la rappresentazione: la funzione ritorna N bit, ovvero N/8 bytes, che vengono mostrati con 2*N/8 caratteri (quindi byte).
Message Authentication Code. Hanno firma:
digest = MAC(key, message)
. In contesti informali il digest viene anche chiamato tag.
Sono simili alle funzioni hash, ma autenticate.
tag := MAC(key, msg) -----------------> tag ?= MAC(key, msg)
#Nota un'alternativa potrebbe essere concatenare chiave e messaggio e passarla ad una funzione hash:
from hashlib import sha256
h = sha256()
k = gen_key()
h.update(k)
h.update(hello)
Questo approccio è vulnerabile: length extension attack. Si riescono a calcolare messaggi validi in questo modo: k || 'hello!' || msg
Quindi... usa le funzioni MAC:
from hmac import HMAC # Hash-based MAC
# ...
Reply attack: l'attaccante può rinviare lo stesso messaggio più volte. Se il sistema è progettato male il destinatario potrebbe accettare i messaggi replicati.
Mitigazioni: aggiungere un contatore progressivo ad ogni messaggio (chiamato ID) + autenticazione dei messaggi.
Alcune misure di sicurezza non sono necessarie, in quanto lo stack di rete potrebbe implementare giù alcune di queste funzionalità. Eg: su TCP non è necessario un ID, in quanto esiste già il sequence number. In UDP invece è necessario inserirlo per renderlo robusto rispetto a replay attacks.
#Vedi DTLS -> proto in cui la difesa contro replay attack è opzionale.
Reflection attack: in canali full-duplex
Mitigazioni:
- utilizzare due chiavi differenti, una per cifrare e una per decifrare per ogni soggetto. Il canale bidirezionale si spezza in due flussi unidirezionali.
- inserire un bit che identifica la direzione del messaggio
Il primo metodo diventa dispendioso per comunicazioni "di gruppo", si preferisce la seconda soluzione.
Mischiando le tecniche sopra viste possiamo ottenere:
- cifratura autenticata: autenticazione, integrità e confidenzialità
- AEAD - Auth Encryption with Associated Data
Framework tipico per AEAD:
keygen([size]) -> key
encrypt(key, n, associatedData, message) -> ciphertext
decrypt(key, n, associatedData, ciphertext) -> message
Dove associatedData
consiste nei dati di cui non vogliamo garantire confidenzialità e autenticità, ma solo autenticità. Il flusso dati viene diviso; la parte di dati associati non viene "nascosta".
#Vedi AES GCM
Caso reale: layer TLS sopra al TCP che prende il SEQ di TCP come dato associato, che non può essere nascosto per garantire il funzionamento dello stack.
Il principale problema della crittografia simmetrica è la distribuzione della chiave, un problema non banale. Si sviluppa quindi la crittografia asimmetrica: un tipo di crittografia in cui ogni partecipante ha una coppia di chiavi (key pair), una pubblica e una privata.
La chiave pubblica serve per cifrare (operazioni che possono fare tutti), mentre quella privata per decifrare. La chiave pubblica può essere conosciuta da tutti senza intaccare le garanzie di sicurezza.
Schema:
ciphertext = encrypt(pub_key_dst, msg)
msg = decrypt(sec_key, ciphertext)
Problema: non posso garantire l'autenticità, dato che tutti sono a conoscenza della public key. Per questo si utilizzano algoritmi di firma - signature - secondo il seguente modello:
signature = sign(sec_key, msg)
verify(pub_key, msg, signature)
Una firma è inforgiabile da parte di un attaccante, dato che sarebbe necessario possedere la SK. Simile a Message Authentication Code, ma verificabile da chiunque. Inoltre, la firma, garantisce la non repudiability del messaggio
In una comunicazione tra Bob e Alice le chiavi coinvolte sono 5: PkA, SkA, PkB, SkB Quando Alice vuole inviare a Bob un messaggio:
ALICE
ciphertext = encrypt(msg, PkB)
signature = sign(ciphertext, SkA)
|
|
v
BOB
verify(ciphertext, PkA)
msg = decrypt(ciphertext, SkB)
Resta il problema della distribuzione delle chiavi pubbliche, di cui si vuole garantire autenticità. PKI - Public Key Infrastructure
In caso contrario un mittente cifrerebbe il messaggio con una chiave pubblica dall'origine sconosciuta, il che implicherebbe un destinatario in grado il interpretare il messaggio sconosciuto, quindi un potenziale attaccante.
Trusted Third Party:
- A e B non si conoscono. Condividono solo la Pk della Certification Authority
- B manda a A la sua Pk (PkB), A come fa a fidarsi del messaggio ricevuto? B manda insieme alla Pk anche una firma, generata dalla Certification Authority. A può verificarla
I software come i browser vengono distribuiti con certificati inseriti dai mantainer. La generazione dei certificati presso la CA è una procedura offline.
Un certificato nello standard x509 è composto da: PubKey, Signature, Identity e metatdati come il periodo di validità, nazione e nome della CA.
Se esistesse un'unica CA si andrebbe in contro a problemi di natura politica ed economica, conflittuale, mancanza di ridondanza. Un approccio gerarchico è un buon tradeoff.
Una chain of trust si concretizza con una catena di certificati in cui la trustness è definita in modo ricorsivo:
- CA root trusts
- CA intermedia 1 trusts
- CA intermedia 2 trusts
- ...
- Final user (web server)
Ha come effetto che CA root si fida dell'utente finale. Chiaramente il web server dovrà inviare, insieme alla sua chiave pubblica, anche tutta la catena di PK + signatures per poter verificare la sua attendibilità. L'ultima voce della catena conterrà la
Visualizzazione chain of trust di un sito web: openssl s_client -host google.com -port 443
Lettura di un certificato: openssl x509 -noout -text -in <cert>
Certificati autofirmati:
openssl req -newkey rsa:2048 -nodes -x509 -days 365 -keyout <sk-file> -out <crt-file>
Server di debug:
openssl s_server -cert <crt-file> -key <sk-file> -port 4433
Lo schema KEM - Key Encapsulation Method - è un'alternativa allo scambio di chiavi simmetriche mediante crittografia asimmetrica; un'operazione fatta tipicamente per evitare l'overhead della crittografia asimmetrica con moli importanti di dati.
Risolve il problema dello scambio di una chiave corta, che rende necessario l'aggiunta di padding, il quale potrebbe rendere lo schema insicuro.
Il KEM permette di ottenere schemi ibridi, in cui la crittografia asimmetrica è usata nello scambio della chiave di crittografia simmetrica, per passare poi alla crittografia simmetrica.
Definito nello standard 802.11AE ambisce a rendere sicura la comunicazione host-to-host contro attacchi sulla stessa LAN, come lo spoofing.
È un protocollo di livello 3 sicuro by design, pensato per estendere/sostituire IP. Rispetto a TLS e affini cifra anche informazioni come le porte sorgente e destinazione.
Implementazione open-source: https://www.strongswan.org/
In alcuni contesti, sopra alla connessione TCP, viene aperto una connessione sicura mediante un protocollo chiamato TLS - Transport Layer Security. L'equivalente in UDP si chiama DTLS - Datagram TLS.
Infatti HTTPS = HTTP + TLS
L'apertura della connessione TLS 1.2 avviene mediante un handshake:
- [c->s] hello
- [s->c] hello + certificato TLS/SSL
- [c] verifica certificato (autenticazione server)
- [c->s] premaster secret cifrato con la PK del server
- [s] decrypt premaster secret
- [s, c] generano la chiave di sessione dal segreto premaster
- [c->s] ready cifrato con la chiave simmetrica
- [s->c] ready cifrato con la chiave simmetrica
- Connessione instaurata
Perché scambiarsi il premaster secret e non la chiave simmetrica direttamente? Il motivo è la forward secrecy, ovvero la promessa e garanzia che le chiavi di sessione passate non vengano compromesse in futuro anche se la chiave privata del server venisse compromessa.
Questa garanzia si realizza con l'Ephemeral Diffie-Hellman ed è stata introdotta in TLS 1.3; nella versione 1.2 (descritta sopra) venivano infatti generate chiavi di sessione a partire dal premaster secret: considerando un attaccante che ascolti passivamente il traffico di rete pregresso, esso avrebbe tutte le informazioni necessarie (SK del server per decifrare il premaster secret, quindi il premaster secret, da cui la chiave simmetrica può essere calcolata) per decifrare il traffico di sessione.
Nella versione TLS 1.3 il client invia un parametro DH, da cui viene ricavato il premaster secret, usato per calcolare la chiave simmetrica.
Il protocollo TLS è usato in: HTTPS, DNS over HTTPS, FTPS
Le possibili tecniche sono 2:
- protocollo insicuro usato su layer sicuro
- protocollo sicuro by design: SSH, MOSH, SRTP, Wireguard