Come generare una password o un keyfile sicuri (Trilogia Della Password – 1 di 3)
![]()
La generazione di una password o di un keyfile si basa sulla casualità ossia sulla capacità del sistema di generare sequenze di simboli non prevedibili. In questo contesto bisogna avere chiari 3 concetti legati fra loro: casualità, sorgente d'entropia, entropia della password.
- Definizioni varie
- Entropia del kernel (CSPRNG)
- Come generare password e keyfile
- Bonus – Modalità paranoia
- Riepilogo finale
Definizioni varie
Casualità In generale, posso parlare di casualità quando non riesco ad individuare un pattern o un'organizzazione deterministica in una sequenza di dati.
Da un punto di vista crittografico, una sequenza è considerata “casuale” se non esiste un algoritmo che possa prevedere il bit successivo con una probabilità superiore al 50% (puro caso).
Sorgente d'entropia La casualità è resa possibile dalla sorgente d'entropia che è il dispositivo o il processo fisico che genera il rumore grezzo per produrre dati casuali. È l'origine dell'incertezza e il suo grado di “purezza” è fondamentale per la produzione della casualità.
Le sorgenti di entropia possono essere fisiche (TRNG) o software (PRNG). In quest'ultimo caso si parla di pseudo-casualità perché si tratta di algoritmi che espandono un seed (un seme) casuale in una sequenza anche arbitrariamente lunga che sembra casuale.
Bit di entropia di una sequenza O entropia della sequenza, è la misura numerica di quanto sia imprevedibile la sequenza casuale di dati.
Quindi:
- la sorgente di entropia è il nostro generatore di incertezza;
- la casualità, la cui qualità dipende totalmente dalla prima, è il processo che produce la sequenza di dati;
- l'entropia, in termini di bit, che è funzione della lunghezza della sequenza e del set di simboli a disposizione, è la misura di quanto la sequenza sia crittograficamente non prevedibile.
Entropia del kernel (CSPRNG)
Su una qualunque linuxbox il protagonista per la generazione dell'entropia necessaria è ovviamente sua maestà il kernel, che è fatto per essere anche un CSPRNG, impronunciabile acronimo che sta per Cryptographically Secure Pseudo-Random Number Generator.
Il kernel infatti, già dall'avvio, raccoglie entropia (entropy harvesting), rumore casuale direttamente dall'hardware (interrupt del disco, tastiera, mouse, istruzioni CPU come RDRAND). Questi dati grezzi vengono mescolati in un serbatoio (l'entropy pool) da cui l'algoritmo (ChaCha20) pesca per espandere questi dati in un flusso infinito di dati pseudo casuali.
Per avere una misura di quanta casualità abbia il sistema possiamo ricorrere al seguente costrutto:
cat /proc/sys/kernel/random/entropy_avail
un valore da 256 in poi ci dice abbiamo entropia sufficiente per generare chiavi e quant'altro.
Finita la parte di teoria, vediamo in quanti modi possiamo generare una password bella robusta o un keyfile.
Come generare password e keyfile
Per generare sequenze di dati casuali possiamo seguire 2 vie:
- attingere direttamente alla sorgente di entropia fornita dal kernel;
- usare la sorgente per prelevare un seme e generare la sequenza casuale via software che è la via di openssl e di pwgen.
Modalità
dd if=/dev/urandom of=mykey.bin bs=4096 count=1 iflag=fullblock status=nonehead -c 4K /dev/urandom > mykey.bintr -dc '[:graph:]' < /dev/urandom | head -c 4096 > mykey.txtpwgen -s 4095 1 > mykey.txtopenssl rand -out mykey.bin 4096
Analisi
Come si comportano questi procedimenti? Proviamo ad analizzarli.
Casualità Una prima distinzione va fatta sulla modalità di creazione della casualità.
I primi 3 metodi fanno riferimento direttamente alla sorgente senza introdurre altre stratificazioni software. Sul piano teorico, rappresentano il punto più vicino alla casualità fisica che si possa avere su un computer.
Openssl e pwgen no, sono due consumatori per /dev/urandom.
Devono prima pescare un seme da /dev/urandom.
Una volta ottenuto il seme, usano i propri algoritmi (quelli di openssl sono solitamente basati su AES-CTR o SHA2) per generare una sequenza infinita di numeri casuali.
Pwgen fa una cosa simile ma è stato progettato di base per costruire password pronunciabili (minore entropia intrinseca).
Velocità
Openssl, fra tutti, è il più veloce, soprattutto se si ha l'esigenza di produrre casualità per volumi di decine di giga o di tera.
Ciò è dovuto al fatto che openssl effettua pochissime syscall per prelevare il seme per poi spremere solo la cpu che, supportando quasi certamente le istruzioni hardware AES-NI, rende il processo poco impegnativo per la cpu stessa e brutalmene efficiente.
Agire solo sul kernel, pur conservando la purezza della casualità, causa un rallentamento notevole dovuto al grandissimo numero di syscall necessarie e dalla corrispondente attivazione degli algoritmi di cifratura. Inoltre, mentre dd può contare su un buffer relativamente più capiente, cat, tr, head o qualuque altro oggetto che “beva” direttamente da /dev/urandom, hanno dei buffer molto più piccoli a disposizione, 1MB vs 8-16KB, il cui rapporto funge da moltiplicatore sul numero di operazioni necessarie.
Sicurezza Tutti i procedimenti indicati, sono estremamente sicuri, anzi, crittograficamente sicuri. Fermo restando che vale sempre l'osservazione fatta sopra sulla metrica dell'entropia di una sequenza casuale: è funzione del set di caratteri e della lunghezza della sequenza. Conti alla mano, con un alfabeto di una settantina di caratteri, una sequenza di almeno 20 caratteri possiederà un'entropia di circa 120 bit che la rendono impenetrabile per gli attuali sistemi di calcolo.
Usare direttamente la sorgente d'entropia, è sicuramente più vantaggioso perché ci fidiamo del kernel. Inoltre è più isolato perché lavora a livello del kernel dove i processi utente, anche quelli malevoli, non possono accedere alle sue zone di memoria.
Openssl, pwgen e tutti quelli che sono generatori secondari, possono incorrere in quella che si diefinisce duplicazione della casualità. Se l'applicazione non è scritta bene, c'è il rischio che il processo, forkandosi, possa “duplicare” la casualità. In sostanza, i processi padre e figlio finiranno per produrre la stessa sequenza di simboli casuali. Una catastrofe. Vecchie versioni di openssl, ante 1.1.1 per es., hanno sofferto di questa anomalia.
Valutazione
I metodi 1,2,5 producono un keyfile di dati binari, quindi con un entropia enorme quasi vicina all'ottimo teorico. Come detto, openssl è mostruosamente più veloce.
Volendo essere purista, per un keyfile i metodi 1 e 2 (praticamente equivalenti) sono da preferire. Per produrre tera di dati casuali (storage, test di rete) sicuramente openssl. Se il volume non dovesse essere esagerato e siamo paranoici, anche il metodo 1 può andare bene.
Per la produzione di una password complessa con simboli stampabili (non necessariamente pronunciabile altrimenti l'entropia sarebbe ancora più bassa) il metodo 3 è da preferire da un punto di vista matematico.
Mettendo da parte openssl che fa comunque un ottimo lavoro (universalmente riconosciuto con tanto di certificazioni FIPS-2), voglio spendere due parole su pwgen.
pwgen, con il flag -s, crea delle sequenze completamente randomiche su un alfabeto di 62 caratteri.
Se la lunghezza della sequenza è >= 20, e quindi con un'entropia teorica di ~115-120, avremo delle password abbastanza inviolabili dagli attuali sistemi di calcolo.
Usare il flag -y potrebbe sembrare una buona idea perché si forza ad estendere il set di caratteri.
Sicuramente da una parte aumenta l'entropia della sequenza generata, visto che l'alfabeto è molto più vasto. Dall'altra però la forzatura va a compromettere l'uniformità della distribuzione casuale dei simboli.
Conclusione
- password/keyfile stampabile:
tr -dc '[:graph:]' < /dev/urandom | head -c [n] > mykey.txt - password/keyfile binario:
head -c [n] /dev/urandom > mykey.bin(equivalentementedd if=/dev/urandom of=mykey.bin bs=4096 count=1 iflag=fullblock status=none) - cancellazione disco:
openssl rand [dim_disco] | dd of=/dev/sdX bs=1M status=progress
Bonus – Modalità paranoia
Generazione password
Se non ci fidassimo della sorgente di casualità, possiamo aggiungere alla sorgente di entropia un nostro segreto personale e “mescolarli” attraverso una funzione sha256 o sha512 rendendo il tutto ancora crittograficamente sicuro.
(head -c 4K /dev/urandom ; read -s -p "Per aumentare l'entropia inserisci una frase segreta o premi tasti a caso: " secret) | sha512sum | cut -d' ' -f1 > mykey.txt.
È certamente una password molto robusta per violare la quale occorrerebbe compromettere la sorgente d'entropia e indovinare il segreto personale (N.B. stiamo facendo l'ipotesi che si provi a rompere il meccanismo di generazione della chiave non che si provi l'enumerazione attraverso un attacco brute-force).
Generazione keyfile
Per produrre un keyfile di 4096 bytes con la stessa premessa, faremo uso di openssl.
- si genera esplicitamente un seme dalla sorgente di entropia del kernel
- come prima, con sha2 si mescola il seme con un nostro segreto
- si dà in pasto ad openssl.
# Creiamo un seme unico mescolando /dev/urandom e il mio input con sha512
# Usiamo quel seme per generare 4KB di dati casuali "espansi"
(head -c 256 /dev/urandom; read -s -p "Per aumentare l'entropia inserisci una frase segreta o premi tasti a caso: " secret; echo "$secret") | sha512sum | cut -d " " -f1 | openssl enc -aes-256-ctr -pbkdf2 -pass stdin -nosalt -in /dev/zero | head -c 4096 > mykey.bin
L'errore che compare alla fine è un falso negativo, dovuto al fatto che openssl sta ancora provando a scrivere dati e head li tronca all'improvviso.
Giusto due righe di spiegazione:
head -c 256 /dev/urandom: preleva un po' di dati casuali “puri”read -sp secret: si inserisce una password, una frase o tasti schiacciati a caso per aumentare l'entropiasha512sum | cut -d " " -f1: si mescola tutto e si preleva solo l'esadecimale di 64 bytesopenssl enc -aes-256-ctr -pbkdf2 -pass stdin -nosalt -in /dev/zero: openssl, che riceve il seme sullo stdin, fa la sua magia producendo un fiume di dati casualihead -c 4096: tronca il “fiume” alla lunghezza voluta per il nostro keyfile
Quindi:
- Contro i bug del kernel: se la nostra sorgente d'entropia fosse compromessa e diventasse deterministica, occorrerebbe comunque conoscere il nostro segreto
- Contro il keylogging: anche se il nostro segreto potesse essere svelato, la sorgente d'entropia garantirebbe comunque l'inviolabilità della nostra password
GPG Random
Quando si parla di paranoia, un altro ottimo candidato per produrre password e keyfile è certamente GPG.
GPG non si llimita a copiare dati da /dev/urandom ma utilizza una propria libreria crittografica chiamata Libgcrypt, che implementa un generatore di numeri pseudocasuali, crittograficamente sicuro, molto sofisticato.
A differenza di ciò che abbiamo visto finora, GPG permette di impostare un livello di qualità per la casualità da produrre.
- livello 0 (Debole): per scopi didatti, usato raramente.
- livello 1 (Avanzato): adatto per chiavi di sessione e cifratura standard. Corrisponde ad una casualità di alta qualità
- livello 2 (Forte): utilizzato per le chiavi a lungo termine (le chiavi private di GPG). È un livello estremamente conservativo. Se GPG valuta di non avere entropia sufficientemente fresca, si mette in attesa.
GPG non si fida ciecamente del kernel e così Libgcrypt crea un proprio pool di entropia in user space.
- Libgcrypt pesca al solito un seme da
/dev/urandom - il seme viene rimescolato con sha2 o aes
- per evitare che i dati casuali finiscano sullo swap, libgcrypt usa pagine di memoria protetta (mlock)
Impostando il livello 2, libgcrypt può decidere di scartare molti più dati se ritiene che il pool di entropia non sia abbastanza “fresco”. Inoltre, in virtù della sua diffidenza, non consegna mai tutto ciò che arriva dal pool di entropia del kernel così come viene, ma viene rimescolato con sha2 o simili e al livello 2 tutto questo, oltre che avvenire con molta più intensità, avviene anche con molta più frequenza (GPG “chiede” spesso bit freschi al kernel) per scongiurare l'eventualità che un attaccante possa prevedere i bit successivi basandosi su quelli passati
Inoltre, GPG salva una parte di questa entropia “pregiata” in ~/.gnupg/random_seed per avere una base di casualità sicura dalle sessioni precedenti nel caso in cui un computer, appena avviato, non avesse ancora accumulato sufficiente entropia.
Si capisce bene come il livello di paranoia di GPG sia fuori scala ma per fortuna tutta questa potenza è totalmente nascosta sotto il cofano di GPG. Infatti per produrre il nostro keyfile binario basta:
gpg --dev-random 2 4096
Riepilogo finale
E dunque, 3 delle 5 modalità sopra descritte, la 2, la 3 e la 5, possono essere ripensate con l'entropia di GPG invee che con quella tipica del kernel.
2) Keyfile binario
Kernel entropy
head -c 4K /dev/urandom > mykey.binLibgcrypt entropy
gpg --dev-random 2 4096 -o mykey_gpg.bin
3) Password complessa con caratteri stampabili
Kernel entropy
tr -dc '[:graph:]' < /dev/urandom | head -c 4096 > mykey.txtLibgcrypt entropy
gpg --gen-random 1 10000 | tr -dc 'A-Za-z0-9' | head -c 4096 > mykey_gpg.txt
5) Keyfile binario con openssl
Kernel entropy
openssl rand -out mykey.bin 4096Libgcrypt entropy
(gpg --gen-random 2 128) | openssl rand -out mykey_gpg.bin -rand /dev/stdin 4096
#entropy #shannon #csprng #password #keyfile #dd #openssl #pwgen #AesCtr #AesNi #sha2 #gpg #bruteforce