Crittografia
Password e hashing con Argon2id
Perché gli hash veloci non vanno bene per le password, a cosa servono salt e work factor e come calcolare e verificare un hash con Argon2id in Python usando argon2-cffi.
Distingui codifica, cifratura e hashing con laboratori su Base64, SHA-256 e OpenSSL. Impara perché Base64 non protegge niente e perché gli hash sono unidirezionali.
Codifica, cifratura e hashing trasformano tutti e tre i dati in qualcos'altro, ed è per questo che la gente li confonde. Hanno obiettivi diversi. La codifica cambia il modo in cui i dati sono scritti, quindi chiunque può invertirla. La cifratura nasconde i dati a chi non ha una chiave, e con quella chiave la puoi invertire. L'hashing produce un'impronta breve che non puoi riportare all'input originale. Se vuoi ricordare una frase sola: Base64 non è cifratura, e un hash non nasconde niente quando l'input è corto.
Questo articolo separa le tre idee, poi ti chiede di eseguirle tutte e tre in un terminale sullo stesso testo. I risultati li confronti tu.
Ti serve un terminale con base64, sha256sum e openssl. Su Windows 11 i comandi qui sotto girano in Git Bash, che arriva con Git for Windows. C'è anche una versione PowerShell del passo di hashing. Non servono conoscenze di crittografia, ma leggere prima l'articolo sul cifrario di Cesare aiuta a capire l'idea di chiave.
Puoi lanciare senza rischi ogni comando del laboratorio sulla tua macchina. Usa un file di prova e non un segreto vero.
Le tre operazioni rispondono a tre domande diverse.
| Operazione | Domanda a cui risponde | Serve una chiave? | È reversibile? |
|---|---|---|---|
| Codifica | Come rappresento questi byte come testo o nel formato che un altro sistema si aspetta? | No | Sì, da chiunque |
| Cifratura | Chi può leggere questi dati? | Sì | Sì, da chi ha la chiave giusta |
| Hashing | Questi dati sono cambiati, o due valori coincidono? | No | No, per come è progettato |
Tieni a mente questa tabella durante il laboratorio. Il resto dell'articolo spiega ogni riga.
La codifica riscrive i dati in un'altra forma perché un sistema possa trasportarli. Base64 è l'esempio classico. Prende dei byte e li scrive usando 64 caratteri stampabili: da A a Z, da a a z, da 0 a 9, + e /, con = per il riempimento. Il formato è definito da RFC 4648. Gli allegati email, i data URL e molti payload delle API lo usano perché il mezzo di trasporto si aspetta del testo.
Base64 non ha segreti. La corrispondenza è pubblica e fissa. Qualsiasi strumento che conosca la regola può riportare l'output all'input. È lo scopo della codifica: far parlare i sistemi tra loro, senza alcuna pretesa di segretezza.
La cifratura prende dati leggibili, detti testo in chiaro, e con un algoritmo e una chiave li trasforma in testo cifrato. Chi non ha la chiave non dovrebbe ricavare niente di utile dal testo cifrato. Chi ha la chiave può decifrarlo.
Le famiglie sono due. La cifratura simmetrica usa la stessa chiave segreta per cifrare e decifrare. AES è la scelta comune. La cifratura asimmetrica usa una chiave pubblica e una privata. Questo articolo usa la cifratura simmetrica, come il comando OpenSSL del laboratorio.
Una buona cifratura dipende da più cose oltre all'algoritmo. Devi scegliere una modalità sicura, generare le chiavi con una buona fonte di casualità e proteggere la chiave. Il comando OpenSSL del laboratorio usa una password e PBKDF2 per derivare la chiave. Il testo di aiuto di OpenSSL dice che PBKDF2 usa 10.000 iterazioni per impostazione predefinita, a meno che tu non le cambi con -iter.
Una funzione di hash crittografica prende un input di qualsiasi lunghezza e restituisce un output di lunghezza fissa. SHA-256 è specificato in FIPS 180-4, e il suo output è sempre di 256 bit, scritti come 64 caratteri esadecimali. Per chi comincia contano due proprietà:
L'hashing è unidirezionale. Niente nell'output ti dice il testo originale. Puoi controllare se un input candidato produce un hash noto. Non puoi recuperare l'input dal solo hash, se non provando a indovinare e verificando.
Un hash è un'impronta e non un lucchetto. Chiunque può calcolare lo SHA-256 di un file pubblico. Un hash dimostra che due copie coincidono. Il file non lo nasconde.
La maggior parte degli errori di sicurezza reali in quest'area nasce dall'usare lo strumento sbagliato.
Imparare presto la differenza ti evita queste trappole.
Apri Git Bash in una cartella vuota. Codifica una frase breve, poi decodifica il risultato.
echo -n 'hello lab' | base64L'output che ho ottenuto su questa macchina con OpenSSL 3.2.4 e Git Bash:
aGVsbG8gbGFiOra decodifica quell'output:
echo -n 'aGVsbG8gbGFi' | base64 -dOutput:
hello labNota due cose. Primo, non c'è nessuna chiave. Secondo, echo -n conta. Senza -n, echo aggiunge un a capo e l'output Base64 cambia. Esegui echo 'hello lab' | base64 e confronta: io ho ottenuto aGVsbG8gbGFiCg==. Qui Base64 trasforma 9 byte in 12 caratteri, quindi l'output è più lungo dell'input.
Chiediti: qualcuno senza alcun segreto potrebbe invertire questa operazione? Sì. È il test che separa la codifica dalla cifratura.
Passa la stessa frase a SHA-256:
echo -n 'hello lab' | sha256sumOutput:
8fc6baba79a0d60b37dee612ccc1ec096ef52dba8cb0a24a6451e8bfb0e4d761 *-Ora aggiungi un punto esclamativo e rieseguilo:
echo -n 'hello lab!' | sha256sumOutput:
36902fb7fdea33458ad09dfeb52118dc47f99a8ca2b9001080366d55511de915 *-I due hash non hanno nessuno schema visibile in comune. Un carattere ha cambiato tutto l'output. Sono entrambi di 64 caratteri esadecimali, cioè 256 bit.
Il - finale significa che l'input è arrivato dallo standard input e non da un file. Il * che lo precede è il marcatore di modalità binaria che stampa il sha256sum di Git Bash. Altri sistemi possono stampare due spazi, e l'hash è lo stesso. L'hash di un file funziona allo stesso modo.
Crea un piccolo file e calcolane l'hash:
echo 'secret note' > note.txt
sha256sum note.txtSulla mia macchina, con terminazioni di riga Unix nel file, l'output è stato:
b5f891a5e064fec661025d0b0ec2de157487a6c2e5cc46dc0971fee56f86c645 *note.txtCambia il file, anche di un solo carattere, e ricalcola l'hash. Il valore sarà diverso. Le verifiche di integrità funzionano così. La verifica ti dice che il file è cambiato. Non ti dice che cosa è cambiato, e non impedisce a chi attacca di cambiare sia il file sia l'hash pubblicato.
Se preferisci PowerShell, Windows ha un comando integrato:
Get-FileHash note.txt -Algorithm SHA256Controlla nell'output la riga Hash, che mostra 64 caratteri esadecimali. L'hash dipende dai byte esatti del file. Il Set-Content di PowerShell scrive terminazioni di riga Windows (CRLF), diverse dalla terminazione LF dell'esempio Bash qui sopra. Sulla mia macchina, un file scritto così ha dato questo hash:
406A5646AE75CA75D032F306ABD33085DC9F656E3109BB962AEAB335EEC9ED85Gli stessi byte passati a sha256sum in Git Bash hanno dato lo stesso valore in minuscolo. Se il tuo hash è diverso da quello dell'esempio Bash ma corrisponde al risultato PowerShell per il tuo file, il motivo sono le terminazioni di riga.
Crea una nota e cifrala con AES-256-CBC. OpenSSL ti chiederà una password. Scegline una che ricorderai e che userai solo per questo laboratorio.
echo 'secret note' > note.txt
openssl enc -aes-256-cbc -pbkdf2 -salt -in note.txt -out note.encOpenSSL chiede la password e il file cifrato viene scritto in note.enc. L'opzione -salt aggiunge un salt casuale, quindi cifrare due volte lo stesso file dà output diversi. Un file cifrato con -salt comincia con il testo ASCII Salted__. L'ho controllato sulla mia macchina. Il resto del file è binario e illeggibile.
Ora decifralo:
openssl enc -d -aes-256-cbc -pbkdf2 -in note.encInserisci la stessa password. Output:
secret noteLa decifratura funziona solo con la password giusta. Ho provato con una sbagliata. OpenSSL ha stampato bad decrypt ed è uscito con un errore.
Adesso cambia un byte del file cifrato e prova a decifrarlo. Io ho invertito un bit dentro il blocco di testo cifrato di una copia chiamata tampered.enc. La decifratura è fallita con bad decrypt e il comando è uscito con stato 1.
Questo fallimento è un caso fortunato dovuto al padding di CBC. Altre modifiche possono produrre output senza senso e nessun errore. La lezione è che la cifratura semplice non ti dà un segnale affidabile del fatto che il testo cifrato sia stato modificato. I sistemi che hanno bisogno sia di segretezza sia di integrità usano la cifratura autenticata, come AES-GCM, o aggiungono un codice di autenticazione del messaggio. È un argomento avanzato, ma spiega perché un file cifrato non è automaticamente un file affidabile.
Passa lo stesso testo a ciascuna operazione e confronta:
| Operazione | Comando | Output su hello lab (osservato) |
|---|---|---|
| Codifica | echo -n 'hello lab' | base64 |
aGVsbG8gbGFi |
| Hashing | echo -n 'hello lab' | sha256sum |
8fc6baba...e4d761 |
| Cifratura | openssl enc ... -in note.txt |
Dati binari, serve la password per leggerli |
Chiediti che cosa ti permette di fare ciascun output:
hello lab.hello lab, ma non di recuperare l'input dal solo hash.secret note solo con la password.Chiamare Base64 "cifratura" o "offuscamento" per i segreti. Base64 non nasconde niente. Chiunque veda una stringa Base64 può decodificarla. Conserva i segreti in un secrets manager o in un archivio cifrato, mai in forma codificata.
Usare un hash come lucchetto. Un hash è unidirezionale, ma gli input corti o comuni si possono indovinare. Calcola l'hash di hello lab e chi attacca con un elenco di frasi comuni lo recupera senza rompere SHA-256. Per questo l'hashing delle password richiede salt e algoritmi lenti.
Usare SHA-256 semplice per le password. SHA-256 è veloce per progetto, il che va bene per i checksum dei file e male per la conservazione delle password. Usa una funzione di hashing delle password fatta apposta.
Fidarsi di un hash che viene dallo stesso posto del file. Un checksum sulla stessa pagina compromessa non dimostra niente. Ottieni l'hash atteso attraverso un canale separato e affidabile, o verifica una firma digitale.
Dimenticare che la cifratura richiede la gestione delle chiavi. Il laboratorio usa una password digitata a un prompt. I sistemi reali devono conservare, ruotare e proteggere le chiavi. Perdere la chiave vuol dire perdere i dati.
Scriversi la crittografia da soli. Usa librerie e protocolli consolidati. I comandi del laboratorio servono per imparare e non sono un modello per sistemi di produzione.
Ripeti i tre laboratori con una frase diversa, poi con un file di qualche kilobyte. Nota che l'output Base64 cresce di circa un terzo, l'hash resta di 64 caratteri e il file cifrato cresce solo per l'intestazione e il padding.
Poi leggi qualcosa sull'hashing delle password. Lì il problema è il costo dei tentativi, più che l'hashing in sé. Il prossimo articolo della serie, Come funziona l'hashing delle password con Argon2id, spiega i salt, i fattori di lavoro e perché contano gli hash lenti. Se prima vuoi un ripasso sul lato dei cifrari, leggi Cifrario di Cesare in Python. Mostra una chiave e un algoritmo in una forma che puoi provare.
Quando studi protocolli reali, cerca tre cose in ciascuno: che cosa codifica, che cosa cifra e di che cosa calcola l'hash. Gran parte della confusione sparisce quando ti chiedi quale dei tre lavori stia facendo ciascuna parte.
Crittografia
Perché gli hash veloci non vanno bene per le password, a cosa servono salt e work factor e come calcolare e verificare un hash con Argon2id in Python usando argon2-cffi.
Crittografia
Chiavi pubbliche e private da zero: cosa dimostra una firma, perché la chiave privata resta segreta e un lab con Ed25519 in cui un file modificato non passa la verifica.
Crittografia
La crittografia da zero: cifratura, chiavi, hash, firme e perché un cifrario di Cesare insegna i concetti ma non protegge nulla.