https://github.com/trykatchup/sicurezza-informazione-m
Appunti di sicurezza dell'informazione (Ingegneria Informatica M - Alma Mater Studiorum, Università di Bologna)
https://github.com/trykatchup/sicurezza-informazione-m
Last synced: 7 months ago
JSON representation
Appunti di sicurezza dell'informazione (Ingegneria Informatica M - Alma Mater Studiorum, Università di Bologna)
- Host: GitHub
- URL: https://github.com/trykatchup/sicurezza-informazione-m
- Owner: TryKatChup
- License: cc0-1.0
- Created: 2021-09-24T10:56:51.000Z (almost 5 years ago)
- Default Branch: main
- Last Pushed: 2022-02-12T08:50:07.000Z (over 4 years ago)
- Last Synced: 2025-01-22T23:26:38.556Z (over 1 year ago)
- Size: 40.9 MB
- Stars: 13
- Watchers: 1
- Forks: 2
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Sicurezza dell'informazione 2021-2022
[](https://github.com/dariodenardi)
[](http://creativecommons.org/publicdomain/zero/1.0/)
- [Introduzione](#introduzione)
- [Scopo della sicurezza informatica](#scopo-della-sicurezza-informatica)
- [I tre capisaldi della sicurezza informatica (CIA Trade Triangle)](#i-tre-capisaldi-della-sicurezza-informatica-cia-trade-triangle)
- [Terminologia](#terminologia)
- [Calcolatore sicuro](#calcolatore-sicuro)
- [Valutazione, Certificazione, Enti](#valutazione-certificazione-enti)
- [Modello a canale insicuro](#modello-a-canale-insicuro)
- [Classificazione attacchi](#classificazione-attacchi)
- [Contromisure](#contromisure)
- [Possibili contromisure per attacchi passivi](#possibili-contromisure-per-attacchi-passivi)
- [Possibili contromisure per attacchi attivi](#possibili-contromisure-per-attacchi-attivi)
- [Dati Sicuri](#dati-sicuri)
- [Algoritmi e protocolli](#algoritmi-e-protocolli)
- [Come rendere sicuri i dati](#come-rendere-sicuri-i-dati)
- [Crittografia e Crittoanalisi](#crittografia-e-crittoanalisi)
- [I principi della difesa](#i-principi-della-difesa)
- [Proteggere la proprietà di confidenzialità (o riservatezza)](#proteggere-la-proprietà-di-confidenzialità-o-riservatezza)
- [Proteggere la proprietà di integrità](#proteggere-la-proprietà-di-integrità)
- [Esempio: garantire riservatezza e integrità](#esempio-garantire-riservatezza-e-integrità)
- [Esempio: garantire solo integrità](#esempio-garantire-solo-integrità)
- [Esempio: garantire solo riservatezza](#esempio-garantire-solo-riservatezza)
- [Proteggere la proprietà di autenticità](#proteggere-la-proprietà-di-autenticità)
- [Firma digitale](#firma-digitale)
- [Hash del messaggio e di un segreto](#hash-del-messaggio-e-di-un-segreto)
- [Firma digitale vs Hash del messaggio e di un segreto](#firma-digitale-vs-hash-del-messaggio-e-di-un-segreto)
- [Esempio: SSL](#esempio-ssl)
- [Esempio: SSH](#esempio-ssh)
- [Esempio: IPsec](#esempio-ipsec)
- [Anonimato/Identificazione](#anonimatoidentificazione)
- [Protocollo di identificazione](#protocollo-di-identificazione)
- [Funzioni one-way](#funzioni-one-way)
- [Trasformazioni segrete](#trasformazioni-segrete)
- [Algoritmo forza bruta](#algoritmo-forza-bruta)
- [Relazioni fra le chiavi](#relazioni-fra-le-chiavi)
- [Proprietà delle chiavi simmetriche](#proprietà-delle-chiavi-simmetriche)
- [Proprietà delle chiavi asimmetriche](#proprietà-delle-chiavi-asimmetriche)
- [Crittoanalisi](#crittoanalisi)
- [Indovinare la chiave](#indovinare-la-chiave)
- [Intercettare la chiave](#intercettare-la-chiave)
- [Esempio: generazione, memorizzazione e uso di una chiave segreta](#esempio-generazione-memorizzazione-e-uso-di-una-chiave-segreta)
- [Dedurre la chiave](#dedurre-la-chiave)
- [Teoria della complessità](#teoria-della-complessità)
- [Definizioni](#definizioni)
- [Classificazione degli algoritmi](#classificazione-degli-algoritmi)
- [Classificazione dei problemi](#classificazione-dei-problemi)
- [Complessità e Sicurezza](#complessità-e-sicurezza)
- [Meccanismi di base](#meccanismi-di-base)
- [Generatori di numeri casuali (RNG)](#generatori-di-numeri-casuali-rng)
- [True Random Number Generator (TRNG)](#true-random-number-generator-trng)
- [Pseudo Random Number Generator (PRNG)](#pseudo-random-number-generator-prng)
- [Cryptographically Secure PseudoRandom Bit Generator (CSPRBG)](#cryptographically-secure-pseudorandom-bit-generator-csprbg)
- [Esempio](#esempio)
- [Algoritmi di hash](#algoritmi-di-hash)
- [Efficienza](#efficienza)
- [Compressione iterata (Schema di Merkle-Damgard)](#compressione-iterata-schema-di-merkle-damgard)
- [Attacco con estensione del messaggio](#attacco-con-estensione-del-messaggio)
- [Attacco al segreto con una collisione](#attacco-al-segreto-con-una-collisione)
- [Robustezza alle collisioni](#robustezza-alle-collisioni)
- [Unidirezionalità](#unidirezionalità)
- [Complessità del calcolo di una collisione](#complessità-del-calcolo-di-una-collisione)
- [Meccanismi Simmetrici](#meccanismi-simmetrici)
- [Cifrario simmetrico](#cifrario-simmetrico)
- [Cifrario a flusso](#cifrario-a-flusso)
- [Esempio](#esempio-1)
- [Attacchi attivi](#attacchi-attivi)
- [Possibili vulnerabilità](#possibili-vulnerabilità)
- [Uso della chiave due volte](#uso-della-chiave-due-volte)
- [Esempio](#esempio-2)
- [Malleabilità](#malleabilità)
- [Cifrari a blocchi](#cifrari-a-blocchi)
- [Modalità di cifratura](#modalità-di-cifratura)
- [Electronic Code Book (ECB)](#electronic-code-book-ecb)
- [Cipher Block Chaining (CBC)](#cipher-block-chaining-cbc)
- [Chipher Feedback Block (CFB)](#chipher-feedback-block-cfb)
- [Output Feedback (OFB)](#output-feedback-ofb)
- [Counter (CTR)](#counter-ctr)
- [Esempio](#esempio-3)
- [Dimensione del blocco](#dimensione-del-blocco)
- [Gestione della chiave](#gestione-della-chiave)
- [Con precedente KA (key agremeent)](#con-precedente-ka-key-agremeent)
- [Key Distribution Center (KDC)](#key-distribution-center-kdc)
- [Esempio](#esempio-4)
- [Implementazione Key Distribution Center (KDC)](#implementazione-key-distribution-center-kdc)
- [Key Distribution Center (KDC) - Alternativo](#key-distribution-center-kdc---alternativo)
- [Implementazione Key Distribution Center (KDC) - Alternativo](#implementazione-key-distribution-center-kdc---alternativo)
- [Key Distribution Center (KDC) vs Key Distribution Center (KDC) - Alternativo](#key-distribution-center-kdc-vs-key-distribution-center-kdc---alternativo)
- [Senza precedente KA (key agremeent)](#senza-precedente-ka-key-agremeent)
- [Integrità e confidenzialità](#integrità-e-confidenzialità)
- [Autenticazione con cifrario simmetrico](#autenticazione-con-cifrario-simmetrico)
- [Meccanismi per l'autenticazione](#meccanismi-per-lautenticazione)
- [Message Authentication Code (MAC)](#message-authentication-code-mac)
- [Esempio](#esempio-5)
- [Message Authentication Code (MAC) + padding](#message-authentication-code-mac--padding)
- [CMAC](#cmac)
- [Authenticated encryption (AE)](#authenticated-encryption-ae)
- [Esempio](#esempio-6)
- [Esempio 2](#esempio-2)
- [Integrità, Autenticità e Non ripudio](#integrità-autenticità-e-non-ripudio)
- [Integrità, Autenticità e Non ripudio](#integrità-autenticità-e-non-ripudio-1)
- [Firma digitale con cifrario simmetrico](#firma-digitale-con-cifrario-simmetrico)
- [Registro Atti Privati (RAP)](#registro-atti-privati-rap)
- [Meccanismi asimmetrici](#meccanismi-asimmetrici)
- [Autenticità della chiave pubblica](#autenticità-della-chiave-pubblica)
- [Esempio: attacco dell'uomo in mezzo](#esempio-attacco-delluomo-in-mezzo)
- [Ente certificatore](#ente-certificatore)
- [Certificato](#certificato)
- [Certificato ISO X.509](#certificato-iso-x509)
- [PKI (Public Key Infrastructure)](#pki-public-key-infrastructure)
- [Directory](#directory)
- [Richiesta di un certificato](#richiesta-di-un-certificato)
- [Generazione delle chiavi](#generazione-delle-chiavi)
- [Schema centralizzato](#schema-centralizzato)
- [Schema a tre parti](#schema-a-tre-parti)
- [Prova di possesso (POP)](#prova-di-possesso-pop)
- [Esempio](#esempio-7)
- [Revoca di un certificato](#revoca-di-un-certificato)
- [Modelli di notifica della revoca](#modelli-di-notifica-della-revoca)
- [Certificate Revocation List (CRL)](#certificate-revocation-list-crl)
- [Online Certificate Status Protocol (OCSP)](#online-certificate-status-protocol-ocsp)
- [Performance Evaluation Criteria](#performance-evaluation-criteria)
- [Modelli di Fiducia](#modelli-di-fiducia)
- [Modello centralizzato](#modello-centralizzato)
- [Modello distribuito](#modello-distribuito)
- [Politiche di gestione](#politiche-di-gestione)
- [Proprietà di PKI](#proprietà-di-pki)
- [Accordo sul segreto: Anonymous Diffie-Hellman](#accordo-sul-segreto-anonymous-diffie-hellman)
- [Cifrari asimmetrici](#cifrari-asimmetrici)
- [Caratteristiche della crittografia a chiave pubblica](#caratteristiche-della-crittografia-a-chiave-pubblica)
- [Numeri primi](#numeri-primi)
- [Aspetti caratteristici](#aspetti-caratteristici)
- [Algoritmo RSA](#algoritmo-rsa)
- [Algoritmo E e D](#algoritmo-e-e-d)
- [Algoritmo G](#algoritmo-g)
- [Sicurezza di RSA](#sicurezza-di-rsa)
- [Il cifrario Ibrido](#il-cifrario-ibrido)
- [Firma Digitale](#firma-digitale-1)
- [Algoritmi di firma con recupero](#algoritmi-di-firma-con-recupero)
- [Proprietà di reversibilità](#proprietà-di-reversibilità)
- [Servizi di Sicurezza a livello applicativo](#servizi-di-sicurezza-a-livello-applicativo)
- [Firma digitale con marca temporale](#firma-digitale-con-marca-temporale)
- [Implementazione TSS](#implementazione-tss)
- [Pretty Good Privacy (PGP)](#pretty-good-privacy-pgp)
- [Autenticazione](#autenticazione)
- [Riservatezza](#riservatezza)
- [Formato dei messaggi PGP](#formato-dei-messaggi-pgp)
- [Servizi d'identificazione](#servizi-didentificazione)
- [Identificazione passiva](#identificazione-passiva)
- [Identificazione attiva](#identificazione-attiva)
- [One-time password](#one-time-password)
- [Protocollo sfida/risposta](#protocollo-sfidarisposta)
- [Protocollo sfida/risposta (hash)](#protocollo-sfidarisposta-hash)
- [Protocollo sfida/risposta (cifratura)](#protocollo-sfidarisposta-cifratura)
- [Protocollo sfida/risposta (firma digitale)](#protocollo-sfidarisposta-firma-digitale)
- [Esempio](#esempio-8)
- [Esempio](#esempio-9)
- [Kerberos](#kerberos)
- [Esempio: semplice dialogo di autenticazione](#esempio-semplice-dialogo-di-autenticazione)
- [Esempio: dialogo di autenticazione più sicuro](#esempio-dialogo-di-autenticazione-più-sicuro)
- [Kerberos V4](#kerberos-v4)
- [Request for Service in Another Realm](#request-for-service-in-another-realm)
- [Modello di Controllo dell'Accesso basato sui ruoli (RBAC)](#modello-di-controllo-dellaccesso-basato-sui-ruoli-rbac)
- [Introduzione alla Blockchain](#introduzione-alla-blockchain)
- [Blockchain: struttura](#blockchain-struttura)
- [Merkle Trees](#merkle-trees)
- [Bitcoin](#bitcoin)
## Introduzione
### Scopo della sicurezza informatica
Il tema della sicurezza informatica è molto importante, in un mondo come quello di oggi, dove l'informatica domina buona parte delle relazioni sociali, lavorative, economiche e politiche. Questo tema è ancora più sentito con l'arrivo del COVID in cui il mondo si presta a digitalizzarsi.
Lo scopo della sicurezza informatica è di proteggere le risorse da accessi indesiderati, garantire la riservatezza delle informazioni, assicurare il funzionamento e la disponibilità dei servizi a fronte di eventi imprevedibili.
Per essere più precisi, è l'insieme dei prodotti, dei servizi, delle regole organizzative e dei comportamenti individuali che proteggono i sistemi informatici di un'azienda.
[Torna all'indice](#sicurezza-dellinformazione-2021-2022)
### I tre capisaldi della sicurezza informatica (CIA Trade Triangle)
L'acronimo CIA viene usato per rappresentare le tre proprietà fondamentali della sicurezza informatica:
- **confidenzialità** (o **riservatezza**): solo chi è autorizzato può accedere (in sola lettura) a risorse o sapere almeno che esse esistano;
- **integrità**: solo chi è autorizzato può modificare, eliminare e creare risorse;
- **disponibilità**: solo chi è autorizzato può accedere alle risorse senza interferenze ed ostacoli.


A queste proprietà se ne possono aggiungere altre come:
- **autenticità**: occorre effettivamente dimostrare chi è stato a creare il dato o sapere da chi proviene;
- **non ridupio**: quando una qualsiasi operazione sul dato è conclusa, si può dimostrare con certezza a una terza parte la paternità di quell'operazione.
### Terminologia
- **Vulnerabilità**: punto debole del sistema che può rendere realizzabile una minaccia.
- **Minaccia**: atto ostile intenzionale o meno che ha un qualsiasi effetto negativo sulle risorse o sugli utenti del sistema.
- **Attacco**: qualsiasi azione che sfrutta una vulnerabilità per concretizzare una minaccia.
- **Contromisura**: azione, dispositivo, procedura o tecnica che consente di rimuovere o ridurre una vulnerabilità.
Esempio:
- **Vulnerabilità**: un ponte ha una crepa.
- **Minaccia**: rischia di crollare.
- **Attacco**: un peso totale eccessivo sul ponte.
- **Contromisura**: cercare di controllare il numero di veicoli sul ponte.
### Calcolatore sicuro
La sicurezza di un calcolatore deve essere garantita a livello hardware, firmware e software. Ovviamente questa ipotesi non è sempre verificata, ma in questo corso lo si darà per scontato.
### Valutazione, Certificazione, Enti
Gli enti di certificazione definiscono delle metodologie che consentono di verificare che un progetto sia effettivamente sicuro. Applicando questi standard, gli enti garantiscono la sicurezza del prodotto che si installa.
Esempi di standard internazionali per valutazione e certificazione della sicurezza: Orange book del NCSC, ISO 17799, CINI, CERT, ecc.
### Modello a canale insicuro
D'ora in poi per analizzare e studiare i meccanismi di sicurezza si farà riferimento ad un modello molto specifico, chiamato modello a canale insicuro.
Questo modello prevede che ci sia una sorgente dei dati, una destinazione a cui sono rivolti e che ci sia un canale che mette in comunicazione sorgente con destinazione.
Si assume che:
- La sorgente sia in un ambiente sicuro (hardware e sistema operativo sicuri).
- La destinazione sia in un ambiente sicuro (hardware e sistema operativo sicuri).
- Il canale sia insicuro, ovvero la possibilità di presenza di intrusori che possano fare degli attacchi su tale canale.
Definito un modello di questo genere, l'obiettivo finale sarà di garantire che la destinazione possa consumare ed interpretare correttamente i dati inviati dalla sorgente.
### Classificazione attacchi
Gli attacchi si classificano in due tipologie:
- **Passivo**: l'intrusore si inserisce sul canale e osserva solo i dati trasmessi. Ad esempio, se il canale è un cavo di rete, si usa uno sniffer. Viene minato il requisito di confidenzialità.
- **Attivo**: l'intrusore si inserisce sul canale ed altera il normale flusso dei dati. Può:
- **Modificare** il flusso intenzionalmente per cambiare il contenuto dei dati. Viene minato il requisito di integrità.
- **Aggiungere** nuove informazioni facendo credere alla destinazione che siano state inviate dalla sorgente legittima. Viene minato il requisito di autenticità.
- **Interrompere** il normale flusso impedendo che i dati arrivino alla destinazione. Viene minato il requisito di disponibilità.
### Contromisure
Esistono 3 tipologie di contromisure per gli attacchi:
- **prevenzione**: si previene la possibilità di un attacco;
- **rilevazione**: si rileva un attacco in corso;
- **reazione**: si reagisce dopo che un attacco è avvenuto.
Il tipo di contromisura da adottare viene scelta anche in base al sistema e a cosa si vuole proteggere (nel corso il focus è incentrato sui dati).
La contromisura ha:
- Un costo in termini economici, di impegno delle risorse informatiche del sistema, di impatto sugli utenti.
- Una sua efficacia (a fronte di una certa minaccia) e dei suoi effetti collaterali (la creazione di nuove vulnerabilità).
Una certa contromisura, quindi, deve essere applicata attentamente, valutando sia la probabilità che si verifichi una certa minaccia in grado di sfruttare una certa vulnerabilità, sia il danno che ne discende. Questa attività è chiamata analisi del rischio.
Come i [veri ingegneri dimostrano](https://youtu.be/EOR8Uz27s3Y?t=420), l'obiettivo è capire a fronte di più possibilità progettuali quale risulti la migliore scelta per garantire la [sicurezza](https://twitter.com/AndreaDraghetti/status/1455576154425933828) del proprio [sistema](https://raidforums.com/Thread-Italy-s-Ministery-of-Healthcare-hacked-and-Blackmails-WhiteHatsicurezza) e dei propri dati.
### Possibili contromisure per attacchi passivi
L'unica contromisura da utilizzare è la prevenzione. La rilevazione e la reazione risultano inutili dopo che l'attacco è avvenuto poiché l'intrusore ha intercettato i messaggi.
Si può:
- **Impedire** l'accesso al canale. Ciò viene implementato tramite l'utilizzo di canali dedicati tra sorgente e destinazione. Questa soluzione non è tuttavia né economicamente sostenibile né scalabile.
- **Cifrare** i dati da inviare, ovvero rendere incomprensibili i dati trasmessi, tranne al destinatario legittimo.
### Possibili contromisure per attacchi attivi
Le contromisure da adottare, a differenza degli attacchi passivi, includono anche la rilevazione e la reazione. L'unico modo per prevenire un attacco attivo è quello di controllare l'accesso al canale. Tuttavia, è quasi impossibile per i motivi specificati in precedenza. Questa contromisura, quindi, non viene usata.
Si può:
- **Aggiungere** un attestato di integrità e/o di autenticità. In questo modo la destinazione è in grado di comprendere se il flusso dei messaggi è integro e/o autentico oppure se è stato manomesso (integrità e autenticità).
- **Impedire** l'interruzione del flusso di dati. La destinazione si assicura che abbia ricevuto il numero corretto di messaggi (disponibilità).
## Dati Sicuri
### Algoritmi e protocolli
Per proteggere i dati a fronte di eventuali attacchi occorre utilizzare delle trasformazioni.


L'algoritmo è una sequenza di istruzioni, e rappresenta una singola trasformazione (blocco `Ts` o `Td`).


Nei casi complessi, è necessario che si eseguano più trasformazioni e la sequenza da eseguire deve essere ben precisa. In questo caso, si parla di protocollo.
### Come rendere sicuri i dati
La sicurezza dei dati può essere garantita tramite l'impiego di una codifica ridondante dei dati, che consiste nell'aggiunta di bit in più rispetto alla lunghezza del messaggio originario. In tal casi si ha ridondanza in termini di:
- **Spazio**: necessario per la memorizzazione.
- **Tempo**: maggiore tempo di trasferimento.
### Crittografia e Crittoanalisi
La disciplina che studia gli algoritmi ed i protocolli da applicare dal lato sorgente e lato destinatario di un canale insicuro è detta crittologia. A sua volta è formata da due distinte e correlate discipline:
- **Crittografia**: è la disciplina che studia gli algoritmi che si possono adottare per proteggere i dati in in termini di riservatezza, autenticità e integrità;
- **Crittoanalisi**: è la disciplina che studia il modo in cui è possibile rompere le trasformazioni che proteggono i dati in termini di riservatezza, autenticità e integrità.
Nel corso si studierà solo crittografia.
### I principi della difesa
Ci sono tre principi che guidano la progettazione delle trasformazioni:
- Deve essere impossibile sapere la trasformata/i calcoli da parte dell'intrusore;
- Deve essere impossibile dedurre la trasformata/i calcoli da parte dell'intrusore;
- Deve essere impossibile indovinare la trasformata/i calcoli da parte dell'intrusore.
### Proteggere la proprietà di confidenzialità (o riservatezza)
Per proteggere i dati si ha bisogno di una trasformata che renda incomprensibile il contenuto. In questo modo, l'intrusore non sarà in grado di capire i messaggi.
La riservatezza si ottiene con una trasformazione di tipo preventivo: l'intrusore che accede ai dati non sarà in grado di comprenderli.


Lo scenario applicativo è il seguente:
- La sorgente `A` non può inserire i dati `m` sul canale insicuro, ma deve prima trasformarli tramite un'encryption `E`;
- Questa trasformazione è conosciuta solo dalla sorgente `A` ed è l'unica in grado di eseguirla;
- I dati `m` vengono trasformati in dati incomprensibili `c`;
- La destinazione `B` riceve `c` e tramite la trasformazione di decryption `D`, risale al contenuto dei dati `m`.
Devono essere rispettate le seguenti proprietà:
- **Segretezza**: la trasformazione `E` e `D` sono conosciute solo rispettivamente dalla sorgente e dal destinatario, non è possibile risalire al messaggio in chiaro;
- **Calcoli difficili**: dato il messaggio `m` deve essere facile calcolare il messaggio cifrato. L'operazione inversa, se non si conosce la trasformata di `D`, deve essere computazionalmente difficile.
Altre considerazioni:
- **Il flusso è bidirezionale**: lo schema della figura può essere applicato sia da `A` verso `B` che da `B` verso `A`. Ovviamente `B` potrà eseguire l'operazione di encryption, mentre `A` di decryption;
- **A e B non è detto che siano entrambi online**: possono essere online contemporaneamente oppure A online e B offline. I messaggi possono essere decifrati in momenti diversi;
- **B = A**: la sorgente potrebbe coincidere con la destinazione. Ad esempio, vogliamo cifrare dei dati che abbiamo sul nostro hard disk. Quando effettuiamo il logout dal sistema essi vengono cifrati mentre quando effettuiamo il login li decifriamo.
### Proteggere la proprietà di integrità
Proteggere l'integrità vuol dire costruire delle trasformazioni in grado di rilevare modifiche al contenuto dei dati trasmessi. In questo caso, la contromisura da adottare è quella della rilevazione perché le modifiche ai messaggi sul canale non possono essere evitate a priori.
Se si vuole costruire una trasformazione che protegga l'integrità, bisogna far si che la sorgente utilizzi ridondanza al messaggio e affianchi al dato iniziale un'informazione aggiuntiva che deve essere costruita opportunamente. Il risultato finale `H(m)` viene detto riassunto o impronta. La sua dimensione deve essere inferiore perché la si deve poi trasmettere sul canale.
Il riassunto lo si calcola tramite una funzione hash.


In generale, una funzione hash `H` è una funzione che prende un dato `m` di lunghezza arbitraria e restituisce in uscita un'impronta `H(m)`.
Una funzione hash ha la seguente proprietà:
- **Calcoli difficili**: dato il messaggio `m` deve essere facile calcolare la sua impronta. L'operazione inversa invece deve essere computazionalmente difficile;
Oltre alla proprietà appena scritta, risulta importante che essa sia crittograficamente sicura e abbia anche le seguenti proprietà:
- **Comportamento da "oracolo casuale"**: se si decide che l'impronta sia costituita da `n` bit, le possibili uscite della funzione hash sono 2n.
Considerato il messaggio `m`, bisogna fare in modo che la probabilità che esca un'uscita rispetto ad un'altra sia la stessa. Inoltre, se il messaggio viene ripetuto, la risposta sarà la medesima, dato che è stata assegnata precedentemente dall'oracolo.
- **Resistente alle collisioni**: per un intrusore deve essere molto difficile individuare due messaggi che abbiano la stessa impronta.
È inevitabile che due messaggi diversi possano avere in uscita la stessa impronta perché lo spazio di input è molto più grande dello spazio di output (`m > n`). Per un intrusore deve essere computazionalmente difficile trovare un messaggio `m2` con impronta `H(m2)` uguale a quella di `m1`, cioè `H(m1)`.
Ad esempio, se i messaggi sono `10` e il numero di bit è pari a `3`, le possibili uscite della funzione hash sono `8` e alcuni messaggi avranno sicuramente la stessa impronta.
Le funzioni hash possono essere classificate in due categorie:
- **Funzioni hash semplici**: l'individuazione di due messaggi con la stessa impronta è un calcolo facile. In presenza di certi disturbi, è più probabile che i bit che variano siano sempre gli stessi, per cui è possibile calcolare la probabilità;
- **Funzioni hash sicure**: se le funzioni hash sono crittograficamente sicure (vedi proprietà scritte in alto).


Uno scenario applicativo in cui viene garantita solamente l'integrità è il seguente:
- La sorgente `A` manda sul canale insicuro un messaggio `m`.
- L'impronta `H(m)` deve viaggiare su un canale sicuro per evitare che un intrusore possa sostituire `m` con un altro messaggio `m'` e di conseguenza sostituire anche `H(m)` con `H(m')` (la funzione `H` non è segreta).
- `B` applica la funzione hash al messaggio ricevuto, e verifica che l'hash ricevuto coincida con quello ottenuto.
- Se si equivalgono, il messaggio è stato trasmesso correttamente. In questo modo la destinazione è in grado di rilevare se il messaggio ha subito modifiche.
### Esempio: garantire riservatezza e integrità
Le trasformazioni possono essere combinate fra di loro. Ad esempio, per garantire la _riservatezza_ e l'_integrità_ dei dati si possono usare in questo modo:
`p = m || H(m)` (Concatenazione del messaggio originale con un hash sicuro)
`c = E(p)` (Encryption su p)
`p* = D(c*) = m* || H*(m)` (Decryption sul messaggio inviato sul canale)
`H*(m) =? H(m*)` (Verifica dell'integrità del hash)
Il protocollo che assicura riservatezza e integrità dei dati è costituito dai questi passi:
- La sorgente esegue due trasformazioni:
- **Passo 1**: viene applicata la funzione hash crittograficamente sicura e la si concatena con il messaggio;
- **Passo 2**: viene applicata la funzione di encryption `E`. Dato che il messaggio `c` è cifrato, lo si può inviare direttamente sul canale insicuro.
- La destinazione esegue due trasformazioni:
- **Passo 3**: la destinazione riceve `c` che può essere, o quello inviato dalla sorgente o un altro che è stato modificato dall'intrusore. Per questo motivo, dato che non si ha la certezza, si indica il messaggio `c` con `c*`. La destinazione applica la funzione `D` su `c*` ottenendo di nuovo `m* || H*(m)`;
- **Passo 4**: per vedere se il messaggio sia integro, dobbiamo verificare che la funzione crittograficamente sicura `H(m*)` coincida con `H*(m)`. Se non coincidono il messaggio viene scartato.
L'intrusore può modificare solo casualmente i bit del cifrato perché calcolarsi l'operazione inversa senza conoscere la trasformata `D` è computazionalmente difficile. Inoltre, le trasformate `D` e `E` sono segrete.
Riassumendo, vengono garantite:
- **Riservatezza**: grazie all'operazione di Encryption (`E`) e Decryption (`D`) sul hash concatenato al messaggio, conosciute soltanto rispettivamente dalla sorgente e dalla destinazione. `D` deve essere anche computazionalmente difficile da ricavare.
- **Integrità**: grazie alla funzione di _hash sicuro_ su `m` che possiede le seguenti proprietà
- resistenza alle collisioni;
- difficoltà nel risalire al messaggio originale, a partire dall'impronta;
- comportamento da oracolo casuale.
### Esempio: garantire solo integrità
In questo caso, l'integrità è rispettata mentre la riservatezza no:
`p = m`
`c = E(p) || H(m)` (Cifro il messaggio e lo concateno al suo hash)
`p* = D(c*) = E*(p) || H*(m)` (Decryption sul messaggio inviato sul canale)
`m* = D(E*(p))` (Decryption sul messaggio cifrato)
`H*(m) =? H(m*)` (Controllo se l'hash presente sul canale insicuro è lo stesso del messaggio decifrato)
L'**integrità** è rispettata perché se si modificano i bit di:
- **E(p)**: se si cambiano dei bit di `E(p)` casualmente, è difficile che corrisponda la stessa impronta per la proprietà alla resistenza alle collisioni;
- **H(m)**: H(m) corrisponde a un altro messaggio per la resistenza alle collisioni;
- **E(p) e H(m)**: la probabilità che il messaggio modificato corrisponda proprio a quell'impronta che è anch'essa modificata risulta essere molto bassa.
La **riservatezza**, invece, non è rispettata perché:
- L'intrusore ha a disposizione molte informazioni di contesto.
L'operazione di verifica del hash mandato in chiaro è inutile, dato che tramite attacchi di tipo _Man in The Middle_ si può rubare l'impronta e violare il controllo di riservatezza. In questo caso `H(m)` è totalmente inutile.
### Esempio: garantire solo riservatezza
In questo caso, la proprietà di integrità non viene rispettata mentre quella di riservatezza sì:
`p = m`
`c = E(p) || H(E(p))` (Cifro il messaggio e lo concateno al hash del messaggio cifrato)
`p* = D(c*) = E*(p) || H*(E(p))` (Decifro il contenuto del canale)
`m* = D(E*(p))` (Decifro il messaggio cifrato)
`H*(E(p)) =? H(E(m*))` (Controllo la funzione di hash del canale insicuro con la funzione di hash del messaggio decifrato)
La **riservatezza** è verificata perché non si riuscirebbe a confrontare l'impronta `H(m')` scelta dall'intrusore con quella `H(E(p))`, poiché è assente la parte di cifratura del messaggio; in più `H(m)` non è invertibile, quindi non si riesce a risalire a `m`.
L'**integrità** non è garantita perché l'intrusore può modificare `E(p)` e ottenere `E*(p)` e sostituire `H(E(p))` con `H(E*(p))`. Se `m` è un messaggio senza un particolare significato (ad esempio un numero), `D(E(p))` restituisce `m*` e la destinazione potrebbe non accorgersi che `m*` non sia corretto. Se invece `m` è un messaggio dotato di significato, la destinazione potrebbe accorgersi che `m*` è scorretto, e quindi può essere scartato.
Riassumendo, non si ha modo di controllare se l'integrità è stata violata o meno, dato che la funzione di hash del canale insicuro può essere stata modificata e, di conseguenza, anche il messaggio decifrato potrebbe non essere quello mandato dalla sorgente.
### Proteggere la proprietà di autenticità
Chi riceve un messaggio è importante che sappia chi è l'autore o chi lo ha inviato. L'intrusore può creare ad hoc un messaggio, inserirlo nel normale flusso dei dati e fingere di provenire dalla sorgente originale. Questo attacco lo si può solo rilevare.


Per garantire l'autenticità di una sorgente, si deve costruire una trasformazione `S` che, dato un messaggio `m`, deve produrre in uscita un attestato di autenticità `c` che rappresenta in maniera non imitabile il messaggio `m` originale.
La destinazione riceve l'attestato di autenticità `c` ed effettua una trasformazione `V` sull'attestato di autenticità, producendo in uscita una risposta che afferma l'autenticità o meno del messaggio. In caso affermativo, viene restituito il messaggio `m`.
Devono essere rispettate le seguenti proprietà:
- **Calcoli difficili**: dato il messaggio `m` deve essere facile calcolare l'attestato di integrità `c`. L'operazione inversa invece non è fattibile.
- **Segretezza**: la trasformazione `S` deve essere segreta e conosciuta soltanto dalla sorgente, altrimenti eventuali intrusori potrebbero effettuare la trasformazione. Invece, `V` può essere noto, dato che qualsiasi destinazione deve essere in grado di dire se l'attestato è autentico o no.
Alcune considerazioni:
- **A e B non è detto che siano entrambi online**: `B` può verificare l'autenticità in un secondo momento.
- **B = A**: se nel file system vogliamo garantire la loro autenticità, durante la fase di logout e login, oltre a decifrarli, occorre verificare che siano anche autentici.
Esistono 2 schemi alternativi per realizzare sign-verify: la _firma digitale_ e _hash_.
### Firma digitale
Il primo schema alternativo per realizzare sign-verify è la _firma digitale_.


Lo scenario applicativo è il seguente:
- La sorgente `A` prende il messaggio `m` e lo sottopone a una trasformazione `H`, costruendo l'impronta `H(m)`, che garantisce l'integrità.
- La funzione `S` di sign viene eseguita su `H(m)`, e sul canale di comunicazione viene trasmesso `m` concatenato con `c`.
- La destinazione `B` verifica tramite `V` che `c` proviene dalla sorgente legittima. In questo modo viene verificata l'autenticità del messaggio. Dato che `c` contiene `H(m)`, possiamo verificare anche la proprietà di integrità.
Questo schema ha due vantaggi rispetto allo schema normale di sign:
- **Efficienza**: la funzione di sign `S` è una trasformazione costosa. Anzichè applicare `m` direttamente a sign, viene applicata a `H(m)`, che ha dimensione inferiore, rispetto a `m`. Si può applicare anche all'impronta, dato che è univoca per la proprietà di resistenza alle collisioni.
- **Avere subito la disponibilità del dato**: si può prendere direttamente `m` e si verifica l'autenticità in un secondo momento, come previsto dallo schema a blocchi.
Questo schema assicura anche la proprietà di:
- **Non ripudio**: dato che la sorgente `A` è l'unica che esegue `S`, non può disconoscere in un secondo momento l'attestato di autenticità.
- **Integrità**: per la funzione hash `H(m)`.
### Hash del messaggio e di un segreto
Il secondo schema alternativo per realizzare sign-verify è tramite l'utilizzo di un _hash_.


- Due entità `A` e `B` (mittente e destinatario) condividono un segreto `s`.
- `A` calcola `H(m || s)` a partire da `m`, cioè il messaggio che si vuole trasferire, e invia alla destinazione `m || H(m || s)`.
- Dato che il messaggio viaggia sul canale insicuro, l'intrusore può aver modificato il messaggio. Per questo motivo `m` si indica con `m*`.
- La destinazione riceverà il messaggio `m*` e andrà a calcolare `H(m* || s)`.
- Se `H(m* || s) = H(m || s)`, le due proprietà sono state garantite (integrità e autenticità).
Rispetto allo schema di firma digitale, **non** viene garantita la proprietà di non ripudio, poiché la sorgente `A` potrebbe sospettare che la destinazione `B` si sia costruita `H(m || s)`, e che la sorgente `A` non abbia inviato nessun messaggio.
Questo è dovuto al fatto che `A` e `B` condividono entrambi lo stesso segreto, quindi non si è in grado di risalire a chi è effettivamente l'autore del messaggio inviato.
### Firma digitale vs Hash del messaggio e di un segreto
L'utilizzo di _hash_ risulta essere più efficiente rispetto alla _firma digitale_, ma potrà essere impiegato solamente quando si è sicuri del corretto comportamento di `A` e `B`. Può essere utilizzato, ad esempio, con sistemi IoT che richiedono consumi ridotti di batteria e alta efficienza.
Viceversa, la firma digitale è meno efficiente, poiché ha la funzione di sign `S`, ma garantisce il non ripudio.
### Esempio: SSL
In questo caso, si invia un messaggio che rispetta le proprietà di **riservatezza**, **autenticità** e **integrità**:
`p = m || H(m || s)`
`c = E(p)`
`p* = D(c*) = m* || H*(m || s)`
`H*(m || s) =? H(m* || s)`
Il messaggio `m` viene concatenato al certificato e cifrato dal client.
Questo schema è usato dal protocollo SSL, il quale adotta le funzioni hash crittograficamente sicure con un segreto per costruire il certificato di autenticità.
Da un punto di vista di efficienza, le trasformazioni in fase di ricezione sono due:
- decodifica del messaggio cifrato;
- autenticazione tramite funzione hash crittograficamente sicura.
### Esempio: SSH
In questo caso, si invia un messaggio che rispetta le proprietà di **riservatezza**, **integrità** e **autenticità**:
`p = m`
`c = E(p) || H(m || s)`
Il messaggio `m` viene cifrato e viene inviato il cifrato concatenato con l'attestato di autenticità costruito sul messaggio.
Questo schema viene usato dal protocollo SSH. Permette di aprire shell remote sicure.
Da un punto di vista di efficienza, le trasformazioni in fase di ricezione sono due:
- Decodifica del messaggio cifrato;
- Autenticazione tramite funzione hash crittograficamente sicura.
L'integrità, la riservatezza e l'autenticità vengono garantite anche in questo caso, grazie alle proprietà delle funzioni hash.
### Esempio: IPsec
In questo caso, si invia un messaggio che rispetta le proprietà di **riservatezza**, **integrità** e **autenticità**:
`p = m`
`c = E(p) || H((E(p) || s)`
In fase di invio, il messaggio viene cifrato e autenticato, mentre in fase di ricezione viene controllato l'attestato di autenticità e decifrato il messaggio.
Questo schema viene usato dal protocollo IPsec. È un protocollo di sicurezza a livello di rete.
La ricezione è efficiente: viene risparmiata una trasformazione. Se il testo cifrato ha subito delle modifiche, chi riceve verifica il certificato e, se qualche operazione illegale è avvenuta, si evita l'operazione di decifratura.
Come nell'esempio [_garantire solo riservatezza_](#esempio-garantire-solo-riservatezza), si ha il messaggio cifrato `E(p)`concatenato con l'hash del messaggio cifrato `H((E(p) || s)`. Stavolta, nella funzione di hash, `E(p)` è concatenato con il segreto `s`: ciò garantisce l'integrità del messaggio (mancante nell'esempio [_garantire solo riservatezza_](#esempio-garantire-solo-riservatezza)) e l'autenticità.
### Anonimato/Identificazione
Per identificazione si intende un insieme di azioni che richiedono di identificare chi sta partecipando a un'interazione. Ad esempio, risulta indispensabile quando si effettua un pagamento o quando si vuole accedere a certe risorse. L'opposto dell'identificazione è l'anonimato.
Il processo di identificazione ha le seguenti caratteristiche:
- **Real-time**: l'identificazione deve avvenire entro un breve intervallo di tempo prestabilito e non oltre.
- **Efficienza**: l'identificazione di una entità deve avvenire in maniera efficiente, dato che il processo deve essere _real-time_.
- **Sicurezza**: possono essere presenti:
- **Falsi positivi**: una determinata persona possiede diritti di accesso, ma non riesce ad accedere. Ciò causa inefficienza. Bisogna minimizzare questo numero.
- **Falsi negativi**: l'accesso viene effettuato da persone non autorizzate. Occorre prevenire la presenza di falsi negativi.
Un sistema di identificazione si può basare su:
- **Conoscenza**: sistemi che si basano sulla conoscenza di un'informazione (password, pin, chiavi di sicurezza).
- **Possesso**: sistemi che si basano sul possesso di un oggetto che solo quella persona può avere (carte magnetiche, token, smart card).
- **Conformità**: sistemi che si basano su una caratteristica di un'entità (dati biometrici come impronte o analisi della retina, dati comportamentali come il numero di login durante il giorno, quante volte un impiegato entra ed esce dall'ufficio etc).
### Protocollo di identificazione
Un processo d'identificazione avviene tramite l'uso di protocolli.


Qualunque protocollo di identificazione prevede due fasi:
- **Registrazione**: durante la registrazione, l'identificando ed il verificatore concordano e memorizzano rispettivamente il segreto `S` con cui l'identificando si farà riconoscere ed il termine di paragone `T = H(S)`, che consentirà al verificatore di accertare che l'identificando conosce `S`. Il verificatore non può memorizzare direttamente il segreto `S` perché se è malintenziato potrebbe usarlo a sua volta.
- **Identificazione**: l'identificando e il verificatore **devono essere entrambi online** (univocità del tempo).
Questo processo può scomporsi in:
- **Dichiarazione**: l'identificando dichiara la sua identità.
- **Interrogazione**: il verificatore interroga l'identificando, che deve dimostrare la sua identità.
- **Dimostrazione**: l'identificando deve fornire la stessa prova che aveva fornito in fase di registrazione. La dimostrazione deve essere semplice per chi è il legittimo identificando, mentre complesso per l'intrusore.
In generale, un intrusore può:
- Dedurre o indovinare la prova (il segreto).
- Rubare il dispositivo (smartcard).
- Replicare una prova che ha viaggiato sul canale in una legittima transizione di identificazione e riutilizzarla.
### Funzioni one-way
Una funzione `f` è detta unidirezionale se:
- È invertibile (biunivoca).
- È semplice da calcolare: dato lo spazio di input `x` è facile calcolare l'uscita `f(x)`.
- È difficile, dato `f(x)`, risalire a `x` che ha originato l'output.
Ad esempio, sono funzioni unidirezionali la:
- Funzione hash crittograficamente sicura `H`.
- Funzione di cifratura `E` e decifratura `D`.
- Funzione di sign `S`.
Nella teoria della complessità computazionale, non esistono funzioni che siano unidirezionali. In crittografia, invece, sono state individuate diverse funzioni che sono candidate ad avere un comportamento di unidirezionalità (come ad esempio le funzioni di compressione, di cifratura e di firma). Vengono chiamate _pseudo-unidirezionali_ le funzioni che appaiono unidirezionali se non si possiede una particolare informazione sulla loro costruzione.
### Trasformazioni segrete
Possiamo avere tre approcci:
- **Macchine a funzionamento segreto**: non conoscere la struttura della macchina. Ad esempio, la macchina enigma.
- **Algoritmo**: le operazioni sono segrete. Ad esempio, gli algoritmi delle prime SIM, algoritmi USA durante la guerra fredda.
- **Parametro**: la macchina e l'algoritmo sono noti, ma un parametro di ingresso dell'algoritmo è segreto (chiave crittografica).
I primi due approcci non sono molto funzionali:
- **No manutenibilità**: _security through obscurity is always a bad idea_.
- **No scalabilità**: vedi sopra.
- **No Certificazione**: chi garantisce che quello che si sta usando è davvero sicuro, se nessuno conosce come è stato costruito?
L'approccio usato al giorno d'oggi, quindi, risulta essere la trasfrormazione segreta tramite parametro.


- Con `T` si indica la trasformazione nota (quindi la conosce anche l'intrusore).
- Con `k` indichiamo la chiave, ovvero il parametro non noto, in ingresso.
- Con spazio delle chiavi si intende l'insieme delle  possibili configurazioni dove `n` è il numero di bit della chiave.
- La chiave è costituita da una delle  configurazioni. Più è grande `n`, più è complicato per un intrusore indovinare la chiave.
### Algoritmo forza bruta
Un intrusore può sempre disporre di un algoritmo di ricerca esauriente, noto anche come algoritmo di forza bruta.


Se l'intrusore non conosce la chiave, può esplorare tutto lo spazio delle chiavi. Se `n` è il numero di bit della chiave e  è il numero totale di configurazioni, effettua diversi tentativi fino a quando non indovina la chiave.
Occorre conoscere la trasformata `T`. Se il cifrato che ottiene è uguale a quello presente sul canale, allora ha avuto successo nel trovare la configurazione corretta.
### Relazioni fra le chiavi
Si possono individuare due famiglie di cifrari:
- **Cifrari a chiavi simmetriche**: le chiavi `ks` e `kd` o sono _identiche_ oppure _facilmente ricavabili_ l'una dall'altra, quindi devono essere tenute segrete.
- **Cifrari a chiavi asimmetriche**: le chiavi `ks` e `kd` sono diverse e una delle due è difficilmente calcolabile dall'altra. Ogni entità dispone di una coppia di chiavi `ks` e `kd`, di cui `ks` è privata, mentre `kd` pubblica.
### Proprietà delle chiavi simmetriche
Le chiavi simmetriche devono avere le seguenti proprietà:
- **Robustezza**: un intrusore non deve essere in grado di individuare facilmente la chiave.
- **Riservatezza**: il sistema deve garantire la riservatezza della chiave poiché deve essere segreta.
- **Integrità**: si deve essere sicuri che la chiave non sia stata alterata, altrimenti non è possibile utilizzarla.
- **Autenticità**: la chiave è conosciuta solo dalla sorgente e dalla destinazione autentica.
### Proprietà delle chiavi asimmetriche
Le chiavi asimmetriche devono avere le seguenti proprietà:
- **Robustezza**: un intrusore non deve essere in grado di individuare facilmente la chiave privata `ks`.
- **Riservatezza**: la riservatezza è legata alla chiave `ks` privata.
- **Integrità**: è importante che le chiavi `ks` e `kd` siano quelle corrette e non modificate.
- **Autenticità**: si deve essere certi che la **chiave pubblica** `kd` appartenga a un certo utente.
### Crittoanalisi
Come riportato precedentemente, la crittoanalisi si occupa di progettare tutte quelle trasformazioni che minano le proprietà di sicurezza.
Bisogna evitare che un intrusore possa:
- Indovinare la chiave.
- Intercettare la chiave.
- Dedurre la chiave.
### Indovinare la chiave
È sempre possibile risalire alla chiave tramite attacco con forza bruta. Per ridurre le probabilità di successo di questo attacco, occorrono alcuni accorgimenti:
- **Lo spazio delle chiavi deve essere molto grande**: se `n` è il numero di bit che rappresenta la chiave,   è l'insieme delle possibili configurazioni. Più è grande `n`, più è difficile per un intrusore indovinare la chiave.
- **I bit della chiave devono essere casuali**.
- **Limitare il numero di prove che l'intrusore ha a disposizione**, come ad esempio il pin del bancomat. Dopo tre tentativi si disabilita l'accesso.
- **Cambiare frequentemente la chiave**: se i dati devono essere mantenuti a lungo termine, occorre cambiare frequentemente la chiave. In questo modo, si riducono le probabilità di individuare quest'ultima da parte dell'intrusore.
### Intercettare la chiave
Ogni volta che si deve eseguire l'algoritmo, occorre caricarlo in RAM, pertanto, dato che i calcolatori usano l'architettura di Von Neumann:
- La chiave deve essere memorizzata in maniera sicura (cifrata) e solo il proprietario deve avere accesso alla cella in cui è memorizzata.
- Si deve evitare l'intercettazione della chiave sui canali di comunicazione.
- Ci deve essere un supporto alla sicurezza che protegga la RAM.
- L' unità di elaborazione, dopo aver eseguito l'algoritmo, deve cancellare accuratamente il dato dalla memoria.
In questo corso si darà per scontato che l'hardware sia sicuro, ma in uno scenario reale non è detto che lo sia.
Esistono diverse celle di memoria dove può risiedere la chiave:
- **Hard Disk**: poco sicuro, dato che lo stesso calcolatore può essere usato da più utenti.
- **Memory Card**: la si inserisce nel sistema solo quando vi è necessità del dato segreto.
- **Smart Card**: molto sicura, consiste nell'avere un calcolatore portatile con memoria propria contenente la chiave `s` cifrata; in questo modo `s` non esce mai dal suo ambiente protetto.
### Esempio: generazione, memorizzazione e uso di una chiave segreta
Nei file system, una chiave `s` viene memorizzata in forma cifrata, in una regione della memoria chiamata _portachiavi_.

In questo schema, per generare e memorizare una chiave:
- La chiave `s` viene generata una volta (o più, se si decide di cambiarla nel tempo) tramite un **RNG (Random Number Generator)**.
- È necessaria una passphrase (password tra 20 e 30 caratteri) dettata dall'utente, facile per lui da ricordare.
- Su di essa viene eseguita una funzione hash `H(passphrase)` per ricavare l'impronta `u` (la chiave che cifra chiavi).
- La chiave viene subito cifrata con la funzione `E` accoppiata a `u` (`u` è una chiave che cifra altre chiavi).
- Successivamente, `Eu(s)` viene trasferita e memorizzata in modo cifrato sulla memoria `M`.
Per l'esecuzione di accessi tramite una chiave segreta:
- Una volta creata `u`, essa deve essere impiegata per decifrare `s` (la chiave generata in partenza con RNG).
- Una volta decifrata la chiave `s`, essa viene utilizzata per tradurre il testo `y` e quindi calcolare `y = T(s,x)`.
- Infine, una primitiva cancella `s` dalla memoria del processore (grazie al wiper sicuro).
In caso di smarrimento o danneggiamento della passphrase è necessario un sistema di recovery.
## Dedurre la chiave
Esistono diverse tipologie di attacco:
- **Attacco con solo testo cifrato**: l'intrusore dispone esclusivamente di testo cifrato che preleva dal canale insicuro. L'intrusore può sfruttare:
- conoscenze
- ipotesi sul linguaggio di origine che è stato usato per scrivere il messaggio cifrato
- tecniche e statistiche
per effettuare determinati attacchi. Avendo il testo cifrato, l'attaccante può dedurre delle informazioni sul testo originario o sulla chiave stessa.
- **Attacco con testo in chiaro noto**: l'intrusore possiede sia il testo in chiaro che il cifrato corrispondente. In questo modo, si può studiare il codice usato per cifrare il testo.
- **Attacco con testo in chiaro scelto**: l'intrusore sceglie un testo in chiaro e ha la possibilità di cifrare il messaggio, ingannando la vittima.
- **Attacco con testo cifrato scelto**: l'intrusore sceglie un un campione di testo cifrato per ottenere il testo in chiaro corrispondente dalla sorgente.
La contromisura da adottare è quella preventiva: il legame tra il testo in chiaro ed il testo cifrato deve essere aleatorio.
## Teoria della complessità
La complessità computazionale può essere determinata con una serie di indicatori:
- **Tempo di esecuzione**: ovviamente non è un tempo ˝ _vero_ ˝. Ogni tecnologia ha un concetto di tempo diverso. Per misurare il tempo di esecuzione si fa riferimento al numero di operazioni eseguite dall'algoritmo per terminare.
- **Memoria occupata dal programma**.
- **Memoria occupata dai dati**.
Negli algoritmi di crittografia gli ultimi due parametri non sono presi in considerazione.
### Definizioni
- **Tempo di esecuzione di un algoritmo**: si intende il **numero di operazioni** `N` che occorre eseguire per terminare l'algoritmo quando il dato d'ingresso è rappresentato da una stringa di `n` bit `n = log [valore del dato]`).\
\
Il numero `n` (dimensione input) incide sul numero di operazioni richieste e, in alcuni casi, anche il valore stesso può incidere sul numero di passi da eseguire. Dunque, a parità di `n`, si possono avere diversi valori di `N`.
- **Tempo di esecuzione nel caso peggiore**: si intende il numero massimo di operazioni N_{max} che occorre eseguire per qualsiasi dato d'ingresso di `n` bit.\
\
Tramite la notazione del _O grande_ è possibile evidenziare come incrementa il tempo di esecuzione dell'algoritmo al crescere senza limiti della dimensione dell'input.
### Classificazione degli algoritmi
Gli algoritmi possono essere classificati in due categorie:
- **Tempo polinomiale**: algoritmo in grado di completare l'elaborazione di una dimensione `n` di dati in ingresso in un tempo di esecuzione pari a }#gh-light-mode-only) }#gh-dark-mode-only) dove `k` è un numero intero positivo.
- **Tempo esponenziale**: algoritmo in grado di completare l'elaborazione di una dimensione `n` di dati in ingresso in un tempo di esecuzione pari a }#gh-light-mode-only) }#gh-dark-mode-only) dove `n` è la dimensione dei dati in ingresso.
### Classificazione dei problemi
Un problema si può classificare in:
- **Facile**: se esiste un algoritmo polinomiale in grado di risolverlo su una macchina di Turing deterministica.
- **Difficile**: se **non** sono stati fino ad ora individuati (e probabilmente non saranno mai individuati) algoritmi che lo risolvono in tempo polinomiale.
### Complessità e Sicurezza
Per ottenere sicurezza è necessario:
- **Trovare il valore n al di sopra del quale l'andamento diventa esponenziale**: se l'andamento è polinomiale l'intrusore è capace di entrare nel sistema. Dunque, la chiave dovrà avere un numero di bit in modo tale che la ricerca nello spazio delle chiavi diventi esponenziale.
- **Vedere qual è il caso migliore**: l'intrusore non si deve trovare di fronte a istanze del problema di facile soluzione.
Le unità di misura che si possono adottare sono:
- **Anno MIPS**: il tempo di esecuzione di un attacco è espresso in anni MIPS. Questa unità di misura fa riferimento a quante istruzioni può elaborare un calcolatore e con il passare degli anni il numero di riferimento aumenta. Attualmente, un calcolatore è in grado di eseguire un milione di istruzioni al secondo. Questo parametro dipende dalla tecnologia.
- **Livello di sicurezza**: assume come riferimento il tempo
d'esecuzione dell'algoritmo di ricerca esauriente. L'algoritmo di ricerca esauriente è in grado di risolvere tutti i problemi e quindi si deve individuare qual è il numero di bit della chiave tale per cui l'andamento diventa esponenziale. Il parametro è indipendente dalla tecnologia.
Naturalmente è possibile passare dalla misura in anni MIPS a quella in livello di sicurezza e viceversa.

Una chiave deve avere un numero minimo di bit:
- **In una chiave simmetrica**: se si usa una chiave a 128 bit, l'intrusore è difficile che riesca a trovarla perché l'andamento dell'algoritmo diventa esponenziale.
- **In una chiave asimmetrica**: in questo caso i bit non possono essere solo 128, poiché non esiste solo la modalità attacco di forza bruta. Nelle chiavi asimmetriche esistono algoritmi di fattorizzazione, che consente di risalire dalla chiave pubblica alla chiave privata, il cui andamento è sub-esponenziale. In questo caso, per evitare attacchi, il numero di bit delle chiavi asimmetriche, deve essere almeno di 2000.
## Meccanismi di base
### Generatori di numeri casuali (RNG)
I componenti per generare numeri casuali, vengono chiamati **RNG**.
In generale, per generare una chiave crittografica è importante che un componente RNG abbia due determinate caratteristiche:
- Ogni valore deve essere casuale.
- Ogni valore deve essere indipendente dal punto di vista statistico dal precedente e successivo, poiché bisogna evitare che un intrusore possa riuscire a dedurre i bit adiacenti.
### True Random Number Generator (TRNG)
Questi componenti non si possono usare per generare grandi quantità di chiavi crittografiche per due motivi:
- **Bassa frequenza di generazione**: se bisogna generare un numero elevato di chiavi in un tempo brevissimo non sono adatti questi componenti, perché usano ad esempio fenomeni fisici per generarli (decadimento radioattivo, rumore termico etc);
- **Non riproducibilità**: mittente e destinatario devono disporre dello stesso segreto per applicare una determinata trasformazione. Quando il mittente genera una chiave deve condividerla con il destinatario, altrimenti, se il destinatario provasse a generare una chiave, non otterrebbe mai la stessa.
### Pseudo Random Number Generator (PRNG)
Per superare i problemi del TRNG, si usano questi tipi di generatori. Sono componenti che consentono di generare lunghe sequenze di numeri casuali in modo deterministico, a partire da un dato iniziale detto _seme_. Per questo motivo si chiamano _pseudo_, perché se il _seme_ iniziale è uguale sia nella sorgente che nella destinazione, viene riprodotta la stessa sequenza di bit.

Questi componenti non garantiscono la proprietà di _imprevedibilità_: un intrusore che è riuscito a intercettare l'uscita o ad individuare, in tutto o in parte, lo stato del generatore, riesce a dedurre da quale seme si è partiti, o quali saranno i prossimi valori generati.
### Cryptographically Secure PseudoRandom Bit Generator (CSPRBG)
Nella sicurezza informatica, si ha bisogno che si rispetti assolutamente la proprietà dell'_imprevedibilità_: non deve essere in grado di dedurro da quale seme sono i partiti i calcoli o quali saranno i prossimi valori generati.

Questo generatore prevede che il seme, imprevedibile e segreto, sia generato da un _True Number Generator_ una volta sola. A questo punto, il seme viene dato in ingresso a un PRNG il cui modello è un automa a stati finiti, dove la funzione di stato futuro o di uscita deve essere unidirezionale.
In questo modo si rende impossibile a un avversario che ha individuato uno stato di risalire agli stati precedenti e al seme.
Occorre che sia verificato anche il _next-bit test_: dati L bit del seme, non deve esistere alcun algoritmo in grado di prevedere il L+1-esimo bit del seme con probabilità maggiore di 0,5.
La funzione unidirezionale impiegata può sfruttare algoritmi di crittografia per produrre in uscita bit casuali (e.g. crittografia simmetrica e asimmetrica).
### Esempio
Un esempio di PRNG è la classe [`SecureRandom`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/security/SecureRandom.html) di Java.
### Algoritmi di hash
Gli algoritmi che realizzano la funzione hash devono presentare le seguenti proprietà:
- **Efficienza**: deve essere facile calcolare l'impronta anche se il messaggio in ingresso è molto lungo;
- **Robustezza alle collisioni**: si possono individuare due tipi di robustezza:
- **Robustezza debole alle collisioni**: per ogni messaggio `m`, è difficile trovare un altro messaggio `m'` tale che `H(m') = H(m)`.
Conoscendo l'hash di un messaggio non si è in grado, con _una potenza di calcolo ragionevole_, di trovare un altro input che genera lo stesso hash. Da notare che la proprietà suppone che si conosca il messaggio di origine e che questo non deve aiutare nel trovare un secondo messaggio con hash identico.
- **Robustezza forte alle collisioni**: deve essere difficile trovare una qualsiasi coppia `m` e `m'` tale che `H(m) = H(m')`. Non si parte da un messaggio dato, ma si sceglie una qualsiasi coppia di messaggi `m` e `m'` che generano lo stesso hash. Nel caso in cui la coppia scelta non generi lo stesso hash, non si viola la resistenza alle collisioni deboli.
- **Unidirezionalità**: data un'impronta `H(m)`, deve essere computazionalmente difficile risalire al messaggio originario che l'ha generata.
### Efficienza
Ricapitolando, occorre trovare un algoritmo di hash per cui sia _facile_ calcolare l'impronta, anche se il messaggio in ingresso è molto lungo. Con _facile_ intendiamo l'esistenza di un algoritmo polinomiale in grado di risolvere un qualsiasi problema su una macchina di Turing deterministica.
A questo scopo, introduciamo la funzione di compressione iterata.
### Compressione iterata (Schema di Merkle-Damgard)
La maggior parte degli algoritmi, per garantire efficienza, comprimendo agevolmente stringhe binarie aribtrariamente lunghe, utilizzano uno _schema di Merkle-Damgard_ o _compressione iterata_.

- Si considera il messaggio `m` (di lunghezza arbitraria) e lo si suddivide in `N` blocchi di dimensione prefissata di `r` bit (a seconda dello specifico algoritmo di implementazione di `f`).
- Si considera un vettore di inizializzazione `IV = h0`, con dimensione pari a `n` bit;
- Si applica a `m_0` (primo blocco), formato da `r` bit (`r > n`), la funzione `f` (che ha le caratteristiche di robustezza sia debole che forte alle collisioni e unidirezionalità) e `h0`.
- In uscita, viene prodotta un'impronta `h1` di lunghezza `n` bit.
- Ogni impronta in uscita da ciascuna funzione avrà lunghezza pari a `n`.
- Sequenzialmente, viene elaborato il secondo blocco `m1`, concatenato a `h_(i-1)` (l'impronta generata al passo precedente).
- L'impronta `h`-iesima è ottenuta applicando una funzione `f` all'impronta ottenuta al passo ottenuto precedente concatenata con il messaggio `m`-iesimo.
- L'impronta finale dell'intero messaggio corrisponde con l'ultima impronta generata dall'ultimo blocco.
Ci sono implementazioni come MD5, SHA-1 e SHA-2 che utilizzano questo schema. Gli algoritmi differiscono tra di loro per:
- numero di bit di impronta di uscita;
- numero di bit di blocco di input;
- numero di step per realizzare l'impronta;
- dimensione massima del messaggio di output.
Questo schema è soggetto ad un attacco che si chiama _attacco con estensione_.
### Attacco con estensione del messaggio
L'assenza della lunghezza del messaggio dell'ultimo blocco può dare origine ad una vulnerabilità sull'**autenticità**, presente in tutti gli algoritmi di compressione iterata.

- Dalla sorgente `A` si invia un messaggio `m` concatenato al suo attestato di autenticità `H(s || m)` (e non più `h0` dell'esempio precedente), dove `s` è il segreto condiviso tra mittente e destinatario.
- Se la funzione hash è implementata secondo lo _schema di Merkle-Damgard_, allora è presente una vulnerabilità.
- Supponiamo di avere `m'`, messaggio dell'intrusore, concatenato con `m`, tale che `m || m'`.
- Notare bene che l'intrusore non conosce `s`.
- `H(s || m)` non è altro che l'impronta di uscita dell'ultimo blocco del messaggio `m`.
- Quando si iniziano a processare i blocchi derivanti dalla scomposizione di `m'`, si ha come certificato di autenticità `H(s || m)`.
- Avendo `H(s || m)` come input del primo blocco `m_0'` si ottiene come uscita `H(s || m || m_0')`
- Continuando con questo schema, l'output dell'ultimo blocco sarà `H(s || m || m')`, ovvero il nuovo attestato di autenticità.
Per evitare il problema, occorre aggiungere all'ultimo blocco delle informazioni aggiuntive, e.g. la lunghezza del messaggio `m` e dei bit di padding. Tuttavia, non è sempre robusto, dato che il nuovo input, derivante da:
`segreto + messaggio + padding + length`
può avere un nuovo significato (e.g. attacco a Flickr).
**Esempio per facilitare la comprensione a noi poveri studenti**
Si consideri un messaggio composto da:
- Numero di conto del mittente `10203040`
- Numero di conto del destinatario `90807060`
- Importo pari a `100` da trasferire
- Segreto `s` in accordo con mittente e destinatario pari a `SECRET`
- Bit di padding, da applicare dopo il messaggio m, chiamati `PADDING`
- Lunghezza del messaggio da inviare relativo al conto mittente, conto destinatario e importo da trasferire pari a `LENGTH`
Allora, il messaggio da validare `m` sarà:
`s + numContoMittente + numContoDest + somma + PADDING + LENGTH`
ovvero
`"SECRET1020304090807060100"+ PADDING + LENGTH`
Un eventuale attaccante, senza sapere nulla sul valore di `PADDING` e `LENGTH` può concatenare a fine messaggio:
- `m'` (in questo caso supponiamo che sia pari a `0`)
- Un padding arbitrario
- Una lunghezza arbitraria
Pertanto, il prodotto finale sarà:
`"SECRET1020304090807060100"+ PADDING + LENGTH + 0 + PADDING + LENGTH`
La destinazione, infine, come verifica di autenticità rimuoverà i bit relativi a `PADDING` e `LENGTH` _finali_, e il messaggio può:
- non avere senso e venire giustamente scartato;
- acquisire un nuovo senso (in questo caso un importo di denaro enorme) e provocare danni.
Un esempio di implementazione di algoritmo di hash restistente all'attacco con estensione è SHA-3.
### Attacco al segreto con una collisione
Si potrebbe pensare di invertire la posizione di `s` e di `m` all'interno dell'autenticatore (in modo da avere `H(m || s)`), che eviterebbe l'estensione dell'algoritmo, dato che l'attaccante non conosce il segreto `s`.
Questo stratagemma è utile solo se la funzione hash **è resistente alla collisione debole**. Altrimenti, se **non è** debolmente resistente:
- Supponiamo che un intrusore possa trovare un messaggio `m'` in collisione con `m`.
- Con questa ipotesi si avrebbe `H(m || s) = H(m' || s)`.
- Dunque, all'intrusore sarebbe sufficiente inviare sul canale il messaggio `m'` concatenato con `H(m || s)` che, per la collisione, autenticherebbe `m'`.
Come contromisura per migliorare un algoritmo di hash è l'utilizzo di una doppia compressione, realizzando l'impronta di un'impronta (sfavorendo l'efficienza).
### Robustezza alle collisioni
Le funzioni hash non garantiscono sempre la resistenza alle collisioni. Si può quindi effettuare più volte l'operazione di hash; ad esempio, HMAC è uno standard a due compressioni, ovvero si effettua l'impronta di un'impronta (utilizzato da SSL e IPSec).
La resistenza alle collisioni è fondamentale quando la funzione hash è utilizzata per costruire attestati di autenticità.
Senza di essa, un intrusore potrebbe infatti trovare una coppia di messaggi che producono la stessa impronta. L'intrusore potrà eludere le vittime, facendo firmare un contratto su un messaggio `m` a loro favorevoli, per poi in futuro usare l'attestato di autenticità su un messaggio `m'` (favorevole ora all'intrusore), dato che è presente collisione.
In questo esempio, **non** è rispettata la _collisione forte_, poiché sia `m` che `m'` sono stati scelti dall'attaccante.
### Unidirezionalità
La proprietà di unidirezionalità è importante in due scenari:
**Firma digitale**
- L'intrusore vuole ottenere l'impronta firmata con la chiave privata della sorgente. Per fare questo, inizia a generare casualmente delle sequenze di `r` bit, tramite un PRNG crittograficamente sicuro.
- L'intrusore vuole fingere che `r` sia il risultato della firma con chiave privata di `H(input)` (quindi `S_su(H(y)) = r`).
- L'intrusore calcola `V(r)` (operazione di _Verify_): questa operazione può essere compiuta da chiunque, poiché la trasformata `V` è nota a tutti.
- Su `V` applica una chiave pubblica della sorgente (nota a tutti).
- Dalla funzione di Verify si ottiene in uscita `x`.
- `x = H(input)`. L'obiettivo dell'intrusore è di fingere che `x` sia stato firmato dalla sorgente legittima.
- Se `H^-1(x)` è dotato di significato, l'attacco ha successo (poiché abbiamo ricavato `input`), ma dato che la funzione `H` non è invertibile ciò non è possibile.
**Memorizzazione di un segreto su un file system**
- La chiave di cifratura del segreto è la passphrase che viene sottoposta a `H`.
- Se la funzione non fosse unidirezionale si riuscirebbe a ricavare la passphrase.
### Complessità del calcolo di una collisione
**Ipotesi**: per la proprietà di oracolo casuale, le funzioni hash devono avere un comportamento aleatorio, ovvero devono restituire con uguale probabilità una delle   configurazioni.
**Problema**: data una funzione hash con `n` possibili output e un determinato `H(x)`, se `H` viene applicato a `K` input casuali, quale deve essere il valore di `k` (numero di tentativi per trovare una collisione) tale che la probabilità che almeno un input `y` soddisfi `H(y) = H(x)` sia maggiore di 0.5?
- **Resistenza debole**: si indica con
\=\frac{1}{2^n}}#gh-light-mode-only) \=\frac{1}{2^n}}#gh-dark-mode-only)
La probabilità di un tentativo di trovare una collisione. La probabilità di insuccesso, quindi, è:
 
Se si hanno `k` tentativi, la probabilità di trovare una collisione corrisponde a:
\=1-{(1-\frac{1}{2^n})^k}#gh-light-mode-only)\=1-{(1-\frac{1}{2^n})^k}#gh-dark-mode-only)
Applicando il teorema binomiale e approssimando si ottiene:
\times{2^n}#gh-light-mode-only)\times{2^n}#gh-dark-mode-only)
, per avere un andamento esponenziale deve essere pari almeno a 128 (`n = 7`). Dunque, l'impronta deve essere almeno 128 bit (vedi MD5). Attualmente vengono utilizzati 160 bit per l'impronta (vedi SHA-1).
- **Resistenza forte (paradosso del compleanno)**: nell'ipotesi che le date di nascita siano equiprobabili, è sufficiente scegliere a caso 253 persone per avere una probabilità `> 0,5` che una di queste compia gli anni in un giorno prefissato (resistenza debole). Sono invece sufficienti 23 persone scelte a caso per avere una probabilità `> 0,5` che due o più compiano gli anni nello stesso giorno (resistenza forte).
Quindi, non bastano 128 bit: il numero di bit deve essere il doppio, poiché per la resistenza forte sono necessari  tentativi.
È quindi più facile effettuare un attacco rispetto alla resistenza debole.
## Meccanismi Simmetrici
### Cifrario simmetrico
Nella crittografia classica, è stato individuato un cifrario perfetto chiamato _One-time pad_.
Il cifrario perfetto è per definizione un algoritmo in grado di occultare il testo in chiaro escludendo ogni tipo di attacco.
Ne segue che un testo cifrato con un cifrario perfetto non può essere letto in nessun modo se non si è in possesso della *chiave di cifratura unica* per decifrarlo. Tuttavia, il suo impiego nella pratica è molto complicato.
Per questo motivo si parla di _cifrario computazionalmente sicuro_: per risalre dal testo cifrato al testo in chiaro corrispondente si ha bisogno di una potenza di elaborazione superiore a quella a disposizione dell'intrusore.
Per il principio di Kerckoff la sicurezza deve dipendere dalla chiave e non dall'algoritmo, perché quest'ultimo non può essere mantenuto segreto.
Per la _teoria dell'informazione_ sviluppata da Shannon, il cifrario è computazionalmente sicuro se adotta i criteri di:
- **Confusione**: il cifrato deve essere aleatorio e l'aleatorietà non permette di individuare delle relazioni tra testo cifrato e la chiave usata.
La confusione _nasconde_ la relazione esistente tra testo in chiaro e testo cifrato e rende poco efficace lo studio del secondo basato su statistiche e ridondanze del primo. Rende difficile prevedere che cosa accadrà al testo cifrato, anche modificando un solo simbolo del testo in chiaro.
- **Diffusione**: la modifica di un solo carattere del messaggio in chiaro deve provocare una modifica sostanziale del messaggio cifrato, in modo da non poter usare attacchi con statistica.
La diffusione nasconde la ridondanza del testo in chiaro, spargendola all'interno del testo cifrato. Si impone a ogni simbolo del testo in chiaro di influire su molti/tutti i simboli del testo cifrato.
È difficile prevedere quali e quanti simboli del testo cifrato si modificano se si modifica anche un solo simbolo del testo in chiaro.
Ci sono due tecniche che garantiscono _confusione_ e _diffusione_:
- **Tecnica di sostituzione**: tecnica che garantisce la confusione;
- **Tecnica di trasposizione**: tecnica che garantisce la diffusione.
Dalla _teoria dell'informazione_ di Shannon discende che un _cifrario simmerico_ è computazionalmente sicuro se usa _confusione_ e _diffusione_. In tal caso viene detto anche **cifrario composto**.
I cifrari simmetrici vengono utilizzati per garantire **riservatezza**. In rarissimi casi, viene usato per garantire *autenticazione*, per costruire generatori di numeri pseudo-casuali crittograficamente sicuri e alcuni protocolli di identificazione.
Se utilizzati in modo opportuno, i cifrari simmetrici sono estremamente efficienti e robusti.
Esistono due famiglie distinte di cifrari simmetrici:
- **Cifrario a flusso**: si ispira al cifrario _One-time pad_. È un cifrario che opera su uno o pochi bit alla volta, con una regola variabile al progredire del testo. Garantisce la protezione dei singoli bit di una trasmissione seriale (e.g. nel settore della telefonia, applicazione web etc);
- **Cifrario a blocchi**: si ispira al _cifrario poligrafico_ e al _cifrario composto_. Trasforma con una regola fissa blocchi di messaggio formati da molti bit. La lunghezza del blocco dipende dallo specifico algoritmo. Ad esempio, protezione di pacchetti, di file, posta elettronica etc.
Un _cifrario a flusso_ è più veloce di un _cifrario a blocchi_, poiché non introduce rallentamenti. Se il _cifrario a flusso_ non è impiegato correttamente, è meno sicuro di un cifrario a blocchi non impiegato correttamente.
### Cifrario a flusso
Encryption `E` e decryption `D` sono implementati con degli `XOR`:

- **Encryption**: viene preso il bit `i`-esimo di testo in chiaro `m`, lo si mette in `XOR` (somma modulo 2) con il bit `i`-esmo della chiave `k`:\
`ci = mi XOR ki`
- **Decryption**: al bit `i`-esimo del messaggio cifrato `c`, si mette in `XOR` lo stesso `i`-esmo bit di chiave `k` usato nella fase di cifratura:\
`ci XOR ki = (mi XOR ki) XOR ki = mi`
La chiave deve essere:
- della stessa lunghezza del testo;
- utilizzata una e una sola volta;
- formata da bit pseudocasuali e imprevedibili;
- generata casualmente da due PRNG sincronizzati tra loro, partendo da un seed condiviso tra sorgente e destinazione;
- il seed deve essere scelto in segreto, non deve essere individuabile e deve essere scelto a caso in un periodo `p` lunghissimo.
Ci deve essere _sincronismo_ nella generazione dei bit dei flussi di chiave, sia a livello di sorgente, che destinazione.
Il problema per cui non si ha un cifrario perfetto, ma solo computazionalmente sicuro, risulta nell'ottenimento di chiavi periodiche: dopo un periodo `p`, anche se grandissimo, si possono riottenere nuovamente le stesse chiavi.
Il testo cifrato `c` sarà sicuramente più lungo di 128 bit, per cui un attacco di forza bruta non ha successo: in questi tipi di cifrari la chiave è lunga quanto il testo. Inoltre, la chiave varia da messaggio a messaggio.
I cifrari a flusso si suddividono in:
- **Cifrario a flusso sincrono**: il flusso di chiave dipende solo dal seme;

- **Cifrario a flusso autosincronizzante**: il flusso di chiave dipende dal seme ma anche dai bit di testo cifrato precedenti.

I più usati sono i _cifrario flusso sincrono_, poiché i componenti
del _cifrario a flusso autosincronizzante_ sono più costosi.
### Esempio
Uno degli algoritmi più famosi è RC4, ma ormai è stato disabilitato nei browser a causa delle sue vulnerabilità.
Al giorno d'oggi vengono usati algoritmi come Salsa e Sosemanuk.
### Attacchi attivi
Nel _cifrato a flusso sincrono_, nel caso di attacchi attivi:
- **Se si modifica un bit del cifrato**: la destinazione non decifra correttamente un bit perché è stato cambiato. _Non si ha perdita di sincronismo_: solo il `i`-esimo è stato modificato. Tuttavia, la decifrazione non è corretta;
- **Se si cancella/inserisce un bit**: dal punto in poi in cui è stato aggiunto o cancellato il bit `c_i`, i restanti bit non corrisponderanno mai a quelli inviati. Si dice che si ha perdita di _sincronismo letale_.
Nel _cifrato a flusso autosincronizzante_, nel caso di attacchi attivi:
- **Se modifica, cancella o elimina un bit del cifrato**: si ha una perdita di sincronismo, non permanente, bensì pari a un solo periodo di transitorio. Il transitorio è legato alla dimensione del registro di scorrimento (SHIFT): dipende da quanti cicli il bit modificato/cancellato/inserito rimane all'interno di questo registro.
Da come si può vedere da questi tipi di attacchi, l'_integrità_ non viene garantita.
### Possibili vulnerabilità
Per garantire la _riservatezza_, i cifrari a flusso devono avere certe proprietà e bisogna capire quali sono le loro vulnerabilità:
- uso della chiave una sola volta;
- malleabilità.
### Uso della chiave due volte
Il requisito fondamentale è che la chiave deve essere usata una e una sola volta. Se si usa lo stesso flusso di chiave su messaggi distinti (quindi si usa la stessa chiave più volte) si possono fare attacchi di analisi crittografica sui cifrati, poiché si sfruttano le proprietà dell'operazione di `XOR`.

Ad esempio, si consideri:\
`m1 XOR k = c1`
`m2 XOR k = c2`
Effettuare `c1 XOR c2` è come eseguire uno `XOR` su due messaggi in chiaro:
`m1 XOR m2`.
### Esempio
Nell'implementazione del protocollo WEP si presenta questo tipo di vulnerabilità cioè viene usata la stessa chiave più di una volta.
L'obiettivo è ottenere un _seed_ variabile. Tuttavia questo protocollo presenta alcuni limiti:
- Utilizzato nelle reti wireless (protocollo 802.11), il mittente sceglie di volta in volta un diverso vettore di inizializzazione `IV` (comunicato in chiaro), che concatenato con la chiave, crea il seed. Quest'ultimo viene fornito al PRNG, usato per ottenere il testo cifrato da trasmettere in chiaro.
- In questo modo, sembra che si abbia un seme sempre diverso. Il destinatario estrae `IV`, lo concatena con la chiave segreta condivisa e inizializza la generazione del flusso di chiave esattamente dallo stesso punto da cui è partito il corrispondente.
- `IV` possiede una dimensione limitata (24 bit). Dopo 5000 generazioni c'era una probabilità pari al 50% che `IV` si ripeteva.
- Di conseguenza, dopo 2^{24} permutazioni si ha che la chiave si ripete.
- Quando si spegneva il dispositivo che implementava questo protocollo, il vettore `IV` si inizializzava a zero e poi funzionava a incremento. Il suo comportamento è molto prevedibile, così come è prevedibile il seed.
- Non è stato rispettato il fatto di non riutilizzare la chiave.
WEP è stato dismesso, al suo posto è presente WPA/WPA2.

### Malleabilità
La proprietà di _malleabilità_ consiste nella possibilità di alterare il cifrato in modo da produrre un effetto desiderato sul testo in chiaro originario.
Questo attacco ha successo se e solo se l'intrusore conosce una parte del messaggio `m` che il mittente ha inviato.
1. Il mittente effettua `m XOR k`.
2. L'attaccante prende
`(m XOR k) XOR p`
dove `p` è scelto dall'attaccante e sostituisce il messaggio sul canale con il nuovo messaggio cifrato modificato.
3. La destinazione, decifra
`((m XOR k) XOR p)) XOR k`
che equivale a
`m XOR p`.
Ad esempio, il mittente sta cifrando dei dati strutturati. All'inizio di questi dati è sempre presente la parola "From".
L'obiettivo dell'attaccante è quello di cambiare la provenienza del messaggio `m`.
Si ipotizzi che l'inizio del messaggio sia "From Dario", con rappresentazione esadecimale `44 61 72 69 6F`.
L'obiettivo dell'intrusore è ottenere "From Lucia" con rappresentazione esadecimale `4C 75 63 69 61`.
Sapendo che:
`((m XOR k) XOR p)) XOR k`
diventa
`m XOR p`
bisogna trovare quel `p` tale per cui
`m XOR p = Lucia`
Dunque, `p` deve essere `08 14 11 00 0E`.
### Cifrari a blocchi
Si prende un messaggio da cifrare e lo si suddivide in blocchi di uguale lunghezza prefissata. La lunghezza del blocco dipende dallo specifico algoritmo.
La robustezza di un cifrario a blocchi dipende dalla dimensione della chiave e dalla dimensione del blocco. Se un blocco è di 64 bit, `2^64` sono le possibili uscite.
Se il messaggio è di grande dimensione, è possibile che due blocchi di testo in chiaro abbiano lo stesso cifrato (problema di collisione). Per il paradosso del compleanno, bastano `2^32` tentativi per trovare una collisione.
Esistono tanti algoritmi di cifratura per i cifrari a blocchi (DES, TDES, AES). Oggi lo standard è AES (Advanced Encryption Standard).
Quasi tutti i cifrari a blocchi seguono il modello della **rete di Feistel**. Ogni blocco di testo in chiaro deve produrre un blocco di testo cifrato univoco (Teoria di Shannon), a meno del problema delle collisioni.
Il funzionamento è il seguente:
- il messaggio viene suddiviso in blocchi;
- ogni blocco viene sottoposto a un numero di round `n` (dipende dall'algoritmo).
- i bit vengono divisi a metà tra quelli più significativi (`L_i`) e quelli meno significativi (`R_i`).
- a ogni round, ogni metà di bit di un blocco viene sottoposta ad un'operazione di **sostituzione** (parte destra, `R_i`) e ad un' operazione di **permutazione** (parte di sinistra, `L_i`).
- A ogni round successivo, la parte di sinistra diventa la parte di destra e viceversa.
Per ogni round `i = 0, 1, ...n`:
```
L_i = R_(i-1)
R_i = L_(i-1) XOR F(R_i-1, K_i)
```
Ogni algoritmo è composto da due sotto-algoritmi: uno che implementa la rete di Feistel e un altro che, a partire da una chiave, genera una sottochiave; a ogni round viene data in ingresso una sottochiave specifica.
La decifrazione si esegue con lo stesso algoritmo invertendo soltando le sottochiavi `k_i`
Algoritmi che usano la rete di Feistel:
- DES (Data Encryption Standard)
- TDES (Triple DES).
AES **non lo utilizza**.
### Modalità di cifratura
Per modalità di cifratura si intende un cifrario a blocchi e bisogna capire quando è utile impiegarlo. Esistono le modalità:
- ECB (Electronic Code Book), **usa padding**
- CBC (Cipher Block Chaining), **usa padding**
- CFB (Cipher Feedback), non usa padding
- OFB (Output Feedback), non usa padding
- CTR (Counter), non usa padding
### Electronic Code Book (ECB)
Si prende un messaggio e lo si suddivide in blocchi di uguale lunghezza prefissata. La lunghezza del blocco dipende dallo specifico algoritmo. Se l'ultimo blocco contiene meno bit della lunghezza che dovrebbe avere, lo si completa con dei **bit di padding**. Gli standard del padding sono `PKCS#5` e `PKCS#7`.

Ogni blocco (di dimensione di almeno 64 bit) viene dato in input alla funzione di encryption `E` tramite una chiave fissa condivisa dalle due parti.
Il testo cifrato, quindi, non è altro che la concatenazione dei cifrati ottenuti dai singoli blocchi.
L'operazione di cifratura a blocchi ricorda molto la tecnica di base della sostituzione monoalfabetica della crittografia classica ma, a differenza di quest'ultima, la grossa dimensione dei blocchi la rende immune da un attacco con statistiche.
La chiave deve essere generata da un *PRNG crittograficamente sicuro* e deve essere modificata frequentemente, poiché essa cifra moltissimi blocchi di testo in chiaro e quindi ci sono più possibilità di individuarla: gli attacchi con statistica sono più probabili.
Dunque, l'attacco con forza bruta è possibile ed è necessario dimensionare la chiave *almeno con 128 bit*.
**Vantaggi:**
- **Alto parallelismo**: la cifratura è parallelizzabile;
- **No propagazione dell'errore**: se l'intrusore modifica a caso un bit, si dice che la propagazione dell'errore rimane confinata a quel blocco. Chi decifra avrà solo un blocco _sbagliato_.
Molti algoritmi hanno la funzione `E` e `D` che coincidono. Questo vuol dire che da un punto di vista hardware si utilizza lo stesso circuito altrimenti bisogna realizzare anche due circuiti diversi.
**Svantaggi:**
- **Determinismo**: a blocchi in chiaro identici corrispondono blocchi cifrati assolutamente identici. Sono informazioni in più che un intrusore può sfruttare a suo favore. L'attacco con statistica può avere successo perché si inizia a studiare il pattern dei blocchi;
- **Padding**: se il blocco è di dimensione inferiore, bisogna riempirlo e questo comporta un overhead in termini di banda perché si usano bit in più;
- **Malleabilità**: un intrusore è in grado di modificare il testo cifrato, in modo tale che la destinazione, nel momento in cui decifra il messaggio, ottiene un testo da lui scelto. Ciò compromette la proprietà di **integrità** del messaggio.
Ad esempio, si consideri una transazione bancaria:
- nel primo blocco si ha il mittente;
- nel secondo blocco si ha il destinatario;
- nel terzo blocco si ha la cifra da trasferire;
- l'intrusore sostituisce al blocco del destinatario il suo blocco;
- se il mittente è "Luca", il destinatario è "Lucia" e la somma da trasferire "100", l'attaccante basta che sostituisca "Lucia" con il suo nome.
Questa modalità si usa solo in casi specifici: ad esempio, cifrare delle informazioni che sono già per natura aleatoria (per evitare determinismo) o un testo che risiede interamente in un solo blocco (per evitare la possibile sostituzione di blocchi). Ad esempio, si usa per cifrare una chiave di sessione.
### Cipher Block Chaining (CBC)
Si prende un messaggio e lo si suddivide in blocchi di uguale lunghezza prefissata. La lunghezza del blocco dipende dallo specifico algoritmo. Se l'ultimo blocco contiene meno bit della lunghezza che dovrebbe avere lo si completa con dei bit di padding. Lo standard dei padding sono `PKCS#5` e `PKCS#7`.

Per ogni blocco, il testo in chiaro viene messo in `XOR` con il cifrato del blocco precedente e il risultato viene messo in input alla funzione di cifratura `E`, ottenendo un testo cifrato `c_i`. Solo per quanto riguarda il blocco `1`, il testo in chiaro viene dato in `XOR` con quello che si chiama _vettore di inizializzazione_ (`IV`), altrimenti il blocco `1` sarebbe sempre deterministico. `IV` può essere o concordato o inviato come primo blocco del cifrato, e la sua dimensione è grande quanto quella del blocco.
In fase di decifrazione, le operazioni sono inverse ed è sufficiente invertire il verso delle frecce. Se si perde `IV` dopo aver cifrato il messaggio, si riesce comunque a decifrare tutto il lato destinazione, tranne il primo blocco.
Per ottenere l'aleatorietà del cifrato, questo vettore deve essere:
- **(Pseudo)casuale**: si genera un numero randomico;
- **Imprevedibile**: anche se si genera un numero randomico, ma si conosce il *seed*, si può prevedere che numero verrà dopo. È importante questo punto altrimenti viola la confidenzialità;
- **Non ripetibile**: se si ripete il vettore e si hanno due messaggi uguali, si ottiene lo stesso cifrato.
Il vettore non deve essere necessariamente segreto:non è un requisito fondamentale cifrare `IV` per garantire la riservatezza. L'intrusore non conosce la chiave `k`.
Vantaggi:
- aleatorietà;
- Un cambiamento in un singolo blocco ha effetto su tutti i blocchi cifrati seguenti.
Svantaggi:
- **Padding**: se il blocco è di dimensione inferiore, bisogna riempirlo e questo comporta un overhead in termini di banda, poichè si usano bit in più;
- In questo caso, non si può procedere in modo parallelo con più CPU, perché è necessario il cifrato del passo precedente in fase di cifrazione. Invece, il parallelismo lo si ottiene in fase di decifratura, se si hanno già tutti i pezzi di cifrato;
- Se un attaccante modifica un qualcunque bit di un blocco, l'errore si propaga nei blocchi successivi.
Molti algoritmi hanno la funzione `E` e `D` che coincidono. Questo vuol dire da un punto di vista hardware si usa lo stesso circuito altrimenti bisogna realizzare anche due circuiti diversi.
### Chipher Feedback Block (CFB)
DARE UN'OCCHIATA: https://it.wikipedia.org/wiki/Modalità_di_funzionamento_dei_cifrari_a_blocchi
Questo schema ricorda un _cifrario a flusso autosincronizzante_.

Nel dettaglio, può essere rappresentato nel seguente modo:

Si prende un registro a scorrimento che viene inizializzato con un vettore di inizializzazione che è casuale, non necessariamente segreto, imprevedibile e usato una sola volta. Il registro è formato da due parti: una parte formata dagli s bit meno significativi e l'altra dai `b-s` bit più significativi. A scorrimento vuol dire che ad ogni clock `s` bit "escono" dal registro. Lo stato del registro a scorrimento viene sottoposto ad una cifratura con la chiave `K`. L'uscita va a finire in un altro registro a scorrimento che è formato da `s` bit più significativi e `b-s` bit meno significativi. Il cifrato finale è ottenuto mettendo in `XOR` `s` bit del testo in chiaro con gli `s` bit più significati del vettore a scorrimento di cifratura. Il risultato è il cifrato costituito da `s` bit.

In decifrazione, si procede analogamente. Per decifrare il primo blocco di testo in chiaro bisogna avere il cifrato ottenuto al passo precedente vuol dire scorrere indietro. La prima cosa da fare è partire dall'ultimo blocco e recuperare a ritroso. Inoltre, si usa sempre la stessa funzione `E` cioè l'implementazione dell'algoritmo è la stessa (quindi stesso circuito hardware).
Si ha la propagazione dell'errore (una modifica su un cifrato si propaga sul successivo), ma si esaurisce dopo un certo lasso di tempo (si vedano cifrari a flusso auto-sincronizzante).
Si usa, ad esempio, quando si vuole trasferire flussi di carattere. Se il canale è rumoroso, questa modalità non è adatta perché la modifica produce un transitorio e il testo decifrato viene scartato.
### Output Feedback (OFB)
Questo schema ricorda un _cifrario a flusso sincrono_.

Nel dettaglio, può essere rappresentato nel seguente modo:

Come in CFB, anche in questo caso `IV` serve per inizializzare il registro. Il procedimento iniziale è identico, tranne per l'ultimo passaggio: C1 viene infatti prodotto allo stesso modo, ma non viene mandato in ingresso al registro, al passo successivo.

In decifrazione, si usa sempre la stessa funzione di encryption `E` (stesso circuito hardware).
Il vettore non deve essere necessariamente segreto e non deve essere necessariamente imprevedibile ma deve essere usato una e una sola volta perché se questo schema ricorda un cifrario a flusso sincrono, si ha un problema di confidenzialità (proprietà dell'`XOR`).
L'OFB si preferisce usarlo quando i canali sono rumorosi (ad esempio i satelliti) perché la modifica di un blocco cifrato non si propaga sul blocco successivo.
### Counter (CTR)
Il comportamento è quello di un cifrario a flusso sincrono.

Il testo in chiaro viene suddiviso in blocchi, ciascuno dei quali viene lavorato indipendentemente dagli altri. In questa modalità viene utilizzato un contatore corrispondente alle dimensioni del blocco di testo in chiaro. Il requisito essenziale è che il suo valore sia differente per ciascun blocco da cifrare; in genere viene inizializzato con un determinato valore random e imprevedibile generato da un PNRG e poi incrementato di un'unità per ogni blocco successivo.
Per la cifratura, il contatore viene crittografato e poi si applica uno XOR col blocco di testo in chiaro per produrre il blocco di testo cifrato.
Per la decifratura si utilizza la stessa sequenza di valori del contatore ai quali si applica lo XOR con i blocchi di testo cifrato.
Vantaggi:
- **Alto parallelismo**: I blocchi possono lavorare in parallelo sia in fase di `E` che di `D`. Se si dispone di più CPU, l'esecuzione è parallela.
Si utilizza la sola funzione di cifratura `E` sia per cifrare che per decifrare (stesso circuito hardware).
Questo schema viene usato, ad esempio, su reti ATM.
### Esempio
In questo esempio la modalità CBC non è usata correttamente: si viola la confidenzialità se il vettore `IV` non è imprevedibile.
Se la versione di TLS è la 1.0 si rischia di subire l'attacco _Beast Attack_ (Browser Exploit Against SSL/TLS) però è bene ricordare che l'attacco è molto difficile da realizzare.
L'intrusore deve riuscire ad entrare in una sessione già avviata tra client e server e alterare il normale flusso dei dati (attacco _Man In The Middle_). Secondo la classificazione degli attacchi, questo fa parte della categoria _attacco con testo in chiaro scelto_.
All'inizio di una connessione TCP, il client e il server negoziano gli algoritmi di cifratura, la chiave della sessione e i parametri che vengono usati dai cifrari come il vettore di inizializzazione e anche la modalità di cifratura da usare. I dati a livello applicativo hanno una certa dimensione e vengono suddivisi in blocchi perché vanno a finire in pacchetti SSL il cui payload è di dimensione inferiore.

Si suppone che ci siano due persone che stanno parlando tra di loro: Luca e Lucia.
Luca inizia sempre la conversazione con "Mia amata Lucia". Si suppone che "Mia amata" finisca in un blocco e "Lucia" in un altro. Le operazioni sono le seguenti:
- "Mia amata" `XOR` con il vettore di inizializzazione casuale, imprevedibile e usato una e una sola volta, producendo un cifrato;
- "Lucia" `XOR` con il cifrato del passo precedente producendo un nuovo cifrato.
L'obiettivo dell'intrusore è cercare di capire se dando un input opportuno, si ha lo stesso output del blocco 2 così da poter confrontare e dedurre che "Lucia" è stato l'input del passaggio 2.

Basta conoscere come si comporta l'`XOR`, cioè se si da due volte lo stesso input, è come se annullasse l'`XOR`.
Si indica con:
- `K1` il nuovo vettore di inizializzazione che ovviamente sarà diverso rispetto a quello del passaggio 2;
- `K` è il residuo che è stato usato nel passaggio 2.
Bisogna eliminare l'effetto `K1` perché è quello che SSL userebbe come input e vedere se si produce lo stesso cifrato. Avendo ipotizzato, per semplicità, che il vettore di inizializzazione è prevedibile, è possibile costruire un messaggio che abbia la forma:
`Lucia XOR K1 XOR K XOR K1`
`Lucia XOR K`
### Dimensione del blocco
Non solo la dimensione della chiave è importante ma anche la dimensione `n` del blocco perché definisce quanti sono i dati possono essere cifrati con la stessa chiave. La cifratura deve essere robusta fino a 2^n input diversi.
Purtroppo molte modalità di cifratura diventano insicure dopo 2^(n/2) cifrature a causa dell'aumento di probabilità di collisioni tra due blocchi di cifrato per il paradosso del compleanno.
Se si usa un blocco a 64 bit c'è questa problematica. Al momento, si suggerisce di adottare algoritmi che lavorano su blocchi di dimensione maggiore.
Ad esempio, la modalità CBC va contro a questo attacco: se ci sono due testi uguali cifrati in modalità CBC vuol dire che se si mettono ogni bit in XOR dei due cifrati, si ottiene l'XOR dei due messaggi in chiaro. Si guardi paragrafo precedente sulla malleabilità. Per il paradosso del compleanno la probabilità di avere una collisione tra due blocchi è proporzionale a 2^(n/2).
### Gestione della chiave
Quando si usano cifrari simmetrici, l'algoritmo deve essere robusto, la chiave deve essere generata, memorizzata e dimensionata in maniera opportuna e devono essere impiegate modalità di cifratura corrette. Se tutto questo è vero resta un problema: la chiave deve essere distribuita in modo sicuro.
Ci sono due famiglie:
- **Con precedente KA (key agremeent)**: modello che prevede un accordo fuori banda con canale dedicato;
- **Senza precedente KA (key agremeent)**: modello che prevede che non ci sia scambiato niente in precedenza.
### Con precedente KA (key agremeent)
Questo schema prevede che fuori banda e in maniera **assolutamente** robusta sia stata precondivisa una _master key_. Non è opportuno che `A` cifri molti dati con questa _master key_ perché questa chiave meno si cambia meglio è, dato che poi bisogna creare un canale fuori banda (costo elevato). La master key quindi deve avere una vita lunga e ogni tanta va rinnovata.

All'avvio della sessione, il mittente genera una chiave di sessione `k` tramite PNRG crittograficamente sicuro. Questa chiave `k` viene cifrata con la _master key_. Poi, il mittente cifra anche il messaggio con la chiave `k` e il relativo cifrato viene mandato al destinatario concatenato al cifrato della chiave `k`. Il destinatario, conoscendo la master key, decifra la chiave `k`, che userà per decifrare a sua volta il messaggio `m`. La chiave di sessione viene usata solo una volta.
Uno svantaggio di questo schema è che non è scalabile: quando il numero di utenti diventa alto il numero di scambi della _master key_ diventa improponibile.
### Key Distribution Center (KDC)
È impossibile che ogni utente stabilisca una chiave segreta con ognuno degli altri utenti. Si ipotizzi una comunità formata da `Nu`, ogni utente deve:
- Fare `Nu - 1` incontri diretti;
- Memorizzare `Nu - 1` chiavi segrete;
- Aggiornarsi ogni volta che nella comunità si aggiunge un nuovo utente.
Occorrono dunque: Nc = Nu * (Nu-1)/2 canali _sicuri_; altrettante sono le chiavi segrete in circolazione. Per ridurre, quind