8 min

DJB e security-by-construction: da qmail a Curve25519

Uno sguardo pratico alle idee di Daniel J. Bernstein sulla security-by-construction—da qmail a Curve25519—e cosa significa nella pratica una crittografia “semplice e verificabile”.

DJB e security-by-construction: da qmail a Curve25519

Cosa significa security-by-construction (senza gergo)

Security-by-construction significa costruire un sistema in modo che gli errori comuni siano difficili da commettere e che il danno degli errori inevitabili sia limitato. Invece di affidarsi a una lunga checklist ("ricordati di validare X, sanificare Y, configurare Z…"), progetti il software in modo che la strada più sicura sia anche la più semplice.

Pensa a una confezione a prova di bambino: non presume che tutti saranno perfettamente attenti; presume che gli esseri umani siano stanchi, occupati e a volte sbaglino. Un buon design riduce la quantità di "comportamento perfetto" richiesta a sviluppatori, operatori e utenti.

Perché la semplicità riduce il rischio

I problemi di sicurezza spesso si annidano nella complessità: troppe funzionalità, troppe opzioni, troppe interazioni tra componenti. Ogni manopola in più può creare una nuova modalità di guasto—un modo inaspettato in cui il sistema può rompersi o essere usato male.

La semplicità aiuta in due modi pratici:

  • Meno codice da auditare: meno rami, meno casi speciali, meno comportamenti nascosti.
  • Meno modi di configurare male: quando c’è un solo default sicuro invece di dieci scelte “flessibili”, c’è meno margine per l’insicurezza accidentale.

Non si tratta di minimalismo fine a sé stesso. Si tratta di mantenere l’insieme dei comportamenti sufficientemente piccolo da poterlo davvero capire, testare e ragionare su cosa succede quando qualcosa va storto.

Cosa copre questo post (e cosa non copre)

Questo articolo usa il lavoro di Daniel J. Bernstein come insieme di esempi concreti di security-by-construction: come qmail mirava a ridurre i modi di guasto, come il pensiero constant-time evita fughe invisibili, e come Curve25519/X25519 e NaCl spingono verso una crittografia difficile da usare male.

Cosa non farà: fornire una storia completa della crittografia, dimostrare la sicurezza di algoritmi, o dichiarare che esiste una singola “migliore” libreria per ogni prodotto. E non pretenderà che i buoni primitivi risolvano tutto—i sistemi reali falliscono ancora per la gestione delle chiavi, errori d’integrazione e gap operativi.

L’obiettivo è semplice: mostrare pattern di progettazione che rendono gli esiti sicuri più probabili, anche quando non sei uno specialista di crittografia.

Chi è Daniel J. Bernstein e perché la gente cita il suo lavoro

Daniel J. Bernstein (spesso “DJB”) è un matematico e informatico il cui lavoro ricorre spesso nell’ingegneria della sicurezza pratica: sistemi di posta (qmail), primitivi e protocolli crittografici (in particolare Curve25519/X25519) e librerie che confezionano la crittografia per l’uso nel mondo reale (NaCl).

La gente cita DJB non perché abbia scritto l’unico modo “giusto” per fare sicurezza, ma perché i suoi progetti condividono un insieme coerente di istinti ingegneristici che riducono il numero di modi in cui le cose possono andare male.

Cosa prendono in prestito gli ingegneri dallo stile di DJB

Un tema ricorrente è interfacce più piccole e più strette. Se un sistema espone meno punti d’ingresso e meno scelte di configurazione, è più facile da revisionare, più facile da testare e più difficile da usare male per sbaglio.

Un altro tema è assunzioni esplicite. I fallimenti di sicurezza spesso nascono da aspettative non dette—sulla casualità, sul comportamento temporale, sulla gestione degli errori o su come le chiavi sono conservate. Gli scritti e le implementazioni di DJB tendono a rendere concreto il modello di minaccia: cosa è protetto, da chi e in quali condizioni.

Infine, c’è un’inclinazione verso default più sicuri e correttezza noiosa. Molti design in questa tradizione cercano di eliminare gli spigoli taglienti che portano a bug sottili: parametri ambigui, modalità opzionali e scorciatoie prestazionali che perdono informazioni.

Non una biografia—una prospettiva ingegneristica

Questo articolo non è una biografia o un dibattito sulle personalità. È una lettura ingegneristica: quali pattern si osservano in qmail, nel pensiero constant-time, in Curve25519/X25519 e in NaCl, e come quei pattern si traducono nel costruire sistemi più semplici da verificare e meno fragili in produzione.

qmail: un esempio pratico di progettazione per meno modalità di errore

qmail è stato creato per risolvere un problema poco glamour: consegnare posta in modo affidabile trattando il server di posta come un bersaglio ad alto valore. I sistemi di posta stanno su Internet, accettano input ostile tutto il giorno e toccano dati sensibili (messaggi, credenziali, regole di instradamento). Storicamente, un bug in un demonio di posta monolitico poteva significare una compromissione completa del sistema—o la perdita silenziosa di messaggi che nessuno nota finché non è troppo tardi.

Dividi il lavoro, riduci il raggio d’azione

Un’idea definitoria in qmail è spezzare la “consegna della posta” in piccoli programmi che fanno una sola cosa: ricezione, coda, consegna locale, consegna remota, ecc. Ogni pezzo ha un’interfaccia stretta e responsabilità limitate.

Quella separazione conta perché i guasti diventano locali:

  • Se un componente crasha, non corrompe automaticamente la coda né manda giù l’intero sistema.
  • Se un componente ha un bug di sicurezza, l’attaccante non ottiene istantaneamente i privilegi di ogni altra parte.
  • Se puoi ragionare su un componente in isolamento, puoi testarlo e auditare più efficacemente.

Questa è security-by-construction in forma pratica: progetti il sistema in modo che “un errore” sia meno probabile che diventi “fallimento totale”.

Abitudini di design da copiare

qmail mostra anche abitudini che si traducono bene oltre la posta:

  • Confini chiari: definire esattamente quali input accetta un componente e quali output produce. Contratti piccoli ed espliciti sono più facili da far rispettare.
  • Gestione rigorosa degli input: tratta tutto ciò che viene dalla rete come potenzialmente malevolo; valida presto, rifiuta i casi strani ed evita ipotesi “utile”.
  • Least privilege di default: esegui i componenti con solo i permessi necessari, così un bug non diventa automaticamente un takeover completo.

La conclusione non è “usa qmail”. È che spesso si ottengono grandi guadagni di sicurezza riprogettando intorno a meno modalità di guasto—prima ancora di scrivere più codice o aggiungere più manopole.

Ridurre la superficie d’attacco con interfacce strette

“Superficie d’attacco” è la somma di tutti i posti dove il tuo sistema può essere toccato, spinto o ingannato a fare la cosa sbagliata. Un’analogia utile è una casa: ogni porta, finestra, apertura del garage, chiave di riserva e sportello per le consegne è un potenziale punto d’accesso. Puoi installare serrature migliori, ma sei anche più al sicuro avendo meno punti d’accesso.

Il software è lo stesso. Ogni porta che apri, formato di file che accetti, endpoint di amministrazione che esponi, manopola di configurazione che aggiungi e hook per plugin che supporti aumenta il numero di modi in cui le cose possono fallire.

Interfacce strette: API più piccole, meno modalità di errore

Un’“interfaccia stretta” è un’API che fa meno, accetta meno variazioni e rifiuta input ambigui. Questo spesso sembra restrittivo, ma è più facile da mettere in sicurezza perché ci sono meno percorsi di codice da auditare e meno interazioni sorprendenti.

Considera due design:

  • Interfaccia ampia: “Carica qualsiasi tipo di file; rileviamo il formato; compressione opzionale; crittografia opzionale; metadata opzionali; più schemi di auth.”
  • Interfaccia stretta: “Carica byte; devi dichiarare il content type da una piccola allowlist; la dimensione massima è fissa; la crittografia è gestita internamente; un solo metodo di auth.”

Il secondo design riduce ciò che gli attaccanti possono manipolare. Riduce anche ciò che il tuo team può configurare male per errore.

Perché meno opzioni possono essere più sicure

Le opzioni moltiplicano i test. Se supporti 10 interruttori, non hai 10 comportamenti—hai combinazioni. Molti bug di sicurezza vivono in quelle giunzioni: “questa flag disabilita un controllo”, “questa modalità salta la validazione”, “questa impostazione legacy aggira i rate limit”. Le interfacce strette trasformano la “sicurezza scegli-la-tu” in un unico percorso ben illuminato.

Checklist: dove la complessità si nasconde

Usa questo per individuare la superficie d’attacco che cresce silenziosamente:

  • Molti tipi di input: più formati di file, codifiche o parsing “auto-detect”.
  • Troppi ingressi: porte di rete extra, pannelli admin, endpoint di debug, webhook.
  • Feature flag che cambiano la logica di sicurezza: interruttori che alterano validazione, auth o comportamento crittografico.
  • Pluggability: script, template, plugin o “espressioni personalizzate” valutate a runtime.
  • Modalità di retrocompatibilità: protocolli legacy, cifrature vecchie, versioni API deprecate.
  • Default impliciti: comportamento che cambia a seconda di variabili d’ambiente o config mancanti.

Quando non puoi ridurre l’interfaccia, rendila rigorosa: valida presto, rifiuta campi sconosciuti e tieni le “feature power” dietro endpoint separati e ben scanditi.

Pensiero constant-time: prevenire fughe che non vedi

Il comportamento “constant-time” significa che un calcolo richiede (più o meno) lo stesso tempo indipendentemente da valori segreti come chiavi private, nonce o bit intermedi. L’obiettivo non è essere veloci; è essere noiosi: se un attaccante non può correlare il tempo di esecuzione ai segreti, è molto più difficile estrarre quei segreti osservando.

Le fughe di timing contano perché gli attaccanti non devono sempre violare la matematica. Se possono eseguire la stessa operazione molte volte (o osservarla su hardware condiviso), differenze piccole—microsecondi, nanosecondi, persino effetti di cache—possono rivelare pattern che si accumulano fino al recupero della chiave.

Dove si nasconde la variabilità di timing

Anche codice “normale” può comportarsi diversamente a seconda dei dati:

  • Branche su dati segreti: if (secret_bit) { ... } cambia il flusso di controllo e spesso il tempo di esecuzione.
  • Lookup in tabelle indicizzate da segreti: l’esempio classico è una tabella dove indici dipendenti dal segreto toccano diverse linee di cache.
  • Effetti di cache e memoria: pattern di accesso alla memoria dipendenti dal segreto possono trapelare tramite cache CPU, page fault o prefetching.
  • Istruzioni a tempo variabile: alcune operazioni su numeri grandi, divisioni o loop con uscita anticipata possono impiegare più tempo per certi input.

Modi pratici per auditare il rischio di timing

Non serve leggere l’assembly per ottenere valore da un audit:

  1. Traccia l’influenza dei segreti: elenca quali variabili sono segrete (chiavi private, segreti condivisi, tag di autenticazione) e dove fluiscono.
  2. Cerca segnali d’allarme: if dipendenti da segreti, indici di array, loop con terminazione basata su segreti e logiche “fast path/slow path”.
  3. Tratta le dipendenze come parte del modello di minaccia: verifica che le librerie crittografiche dichiarino esplicitamente comportamento constant-time per le operazioni rilevanti.
  4. Testa la varianza: esegui operazioni molte volte con segreti diversi e misura le distribuzioni; differenze grandi e consistenti sono un campanello d’allarme.

Il pensiero constant-time è meno eroico e più disciplinare: progetta codice in modo che i segreti non possano pilotare il timing.

Curve25519 e X25519: crittografia progettata per essere difficile da usare male

Build from a tight plan
Trasforma un piano chiaro in codice funzionante, con meno parti da revisionare.

Lo scambio di chiavi su curve ellittiche è un modo per cui due dispositivi creano lo stesso segreto condiviso pur inviando solo messaggi “pubblici” sulla rete. Ciascuna parte genera un valore privato (segreto) e un valore pubblico corrispondente (sicuro da inviare). Dopo lo scambio dei valori pubblici, entrambe combinano il proprio valore privato con il pubblico dell’altra per ottenere lo stesso segreto condiviso. Un eavesdropper vede i valori pubblici ma non può ricostruire il segreto condiviso in modo praticabile, quindi le due parti possono derivare chiavi di cifratura e comunicare privatamente.

Perché Curve25519/X25519 sono diventati popolari

Curve25519 è la curva sottostante; X25519 è la funzione standardizzata che indica “fai questa cosa specifica” per lo scambio di chiavi costruita sopra di essa. Il loro appeal è in gran parte security-by-construction: meno possibilità di commettere errori, meno scelte di parametri e meno modi per selezionare un’impostazione non sicura.

Sono anche veloci su una vasta gamma di hardware, cosa che conta per server con molte connessioni e per telefoni che vogliono risparmiare batteria. Il design incoraggia implementazioni più facili da mantenere constant-time (aiutando a resistere agli attacchi di timing), il che riduce il rischio che un attaccante abile possa estrarre segreti misurando piccole differenze prestazionali.

Cosa fa—e cosa non fa

X25519 ti dà l’accordo di chiave: aiuta due parti a derivare un segreto condiviso per la cifratura simmetrica.

Non fornisce autenticazione da solo. Se esegui X25519 senza verificare anche con chi stai parlando (per esempio con certificati, firme o una chiave pre-condivisa), puoi comunque essere ingannato a parlare in modo sicuro con la parte sbagliata. In altre parole: X25519 aiuta a prevenire l’intercettazione, ma non impedisce l’usurpazione da solo.

L’idea principale di NaCl: meno scelte, meno errori

NaCl (Networking and Cryptography library) è stato costruito con un obiettivo semplice: rendere difficile agli sviluppatori applicativi montare per errore una crittografia non sicura. Invece di offrire un buffet di algoritmi, modalità, regole di padding e manopole di configurazione, NaCl ti spinge verso un piccolo insieme di operazioni di alto livello già cablate in modo sicuro.

“box” e “secretbox” come mattoni più sicuri

Le API di NaCl hanno nomi in base a cosa vuoi fare, non a quali primitivi vuoi assemblare.

  • crypto_box (“box”): cifratura autenticata a chiave pubblica. Gli dai la tua chiave privata, la chiave pubblica del destinatario, un nonce e un messaggio. Ottieni un ciphertext che (a) nasconde il messaggio e (b) dimostra che proviene da qualcuno che conosce la chiave giusta.
  • crypto_secretbox (“secretbox”): cifratura autenticata a chiave condivisa. Stesso concetto, ma con una singola chiave segreta condivisa.

Il vantaggio principale è che non scegli separatamente “modalità di cifratura” e “algoritmo MAC” e poi speri di averli combinati correttamente. I default di NaCl impongono composizioni moderne e resistenti all’uso scorretto (encrypt-then-authenticate), quindi i modi di fallimento comuni—come dimenticare i controlli di integrità—sono molto meno probabili.

Il compromesso: meno scelte vs. flessibilità

La rigidità di NaCl può sembrare limitante se hai bisogno di compatibilità con protocolli legacy, formati specializzati o algoritmi richiesti da norme. Scambi “posso regolare ogni parametro” con “posso distribuire qualcosa di sicuro senza diventare un esperto di crittografia”.

Per molti prodotti, questo è proprio il punto: vincola lo spazio di progettazione così che esistano meno bug. Se hai davvero bisogno di personalizzazione, puoi scendere ai primitivi di basso livello—ma stai tornando volontariamente sugli spigoli taglienti.

Default sicuri e il costo di troppe manopole

Get rewarded for learning
Condividi ciò che costruisci e guadagna crediti creando contenuti su Koder.ai.

“Secure by default” significa che l’opzione più sicura e ragionevole è quella che ottieni quando non fai nulla. Se uno sviluppatore installa una libreria, copia un esempio rapido o usa i default del framework, il risultato dovrebbe essere difficile da usare male e difficile da indebolire per errore.

I default contano perché la maggior parte dei sistemi reali gira con essi. I team lavorano veloci, la documentazione viene letta di sfuggita e la configurazione cresce organicamente. Se il default è “flessibile”, spesso si traduce in “facile da configurare male”.

Come i default creano rischio silenziosamente

I fallimenti crittografici non sono sempre causati da “math sbagliata”. Spesso derivano dalla scelta di un’impostazione pericolosa perché era disponibile, familiare o comoda.

Trappole comuni nei default includono:

  • Randomness debole o prevedibile: usare PRNG non crittografici, riutilizzare seed o ricadere su sorgenti a bassa entropia in container/VM. Se la generazione delle chiavi dipende da random fragili, tutto ciò che ci sta sopra eredita la debolezza.
  • Algoritmi obsoleti ancora supportati per compatibilità: lasciare SHA-1, MD5 o vecchie dimensioni RSA abilitate “per sicurezza”, e poi scoprire che vengono usate in produzione perché il sistema ha negoziato verso di esse.
  • Modalità e parametri custom o insoliti: offrire tante manopole per modalità di cifratura, regole di padding, gestione dei nonce o schemi fatti in casa. Più scelte ci sono, più modi di creare un protocollo che sembra cifrato ma non è sicuro.

Una regola pratica: meno opzioni, esiti più sicuri

Preferisci stack che rendono la strada sicura quella più semplice: primitivi rivisti, parametri conservativi e API che non ti chiedono di prendere decisioni fragili. Se una libreria ti costringe a scegliere tra dieci algoritmi, cinque modalità e molte codifiche, ti sta chiedendo di fare security engineering via configurazione.

Quando puoi, scegli librerie e design che:

  • predefiniscono algoritmi moderni e ampiamente revisionati
  • rimuovono le opzioni deprecate invece di nasconderle in “impostazioni avanzate”
  • rendono impossibili (o almeno dolorosamente esplicite) le operazioni non sicure

Security-by-construction è, in parte, rifiutare di trasformare ogni decisione in un dropdown.

Come appare “semplice e verificabile” nel codice reale

“Verificabile” per la maggior parte dei team di prodotto non significa “formalmente provato”. Significa poter costruire fiducia rapidamente, ripetutamente e con meno opportunità di fraintendere cosa fa il codice.

Cosa può significare “verificabile” (praticamente)

Un codebase diventa più verificabile quando:

  • La leggibilità è alta: funzioni piccole, nomi chiari e poco “magic”. Riesci a spiegare il flusso a un nuovo ingegnere senza una lavagna piena di eccezioni.
  • Ci sono test vector noti: dato un input, l’output è fisso e documentato (critico per la crittografia). Questi catturano cambiamenti accidentali che sembrano ancora “funzionare” nei test casuali.
  • Build riproducibili: lo stesso sorgente produce lo stesso binario, così puoi confermare che ciò che è in esecuzione è ciò che è stato revisionato.
  • Gli audit sono fattibili: non “economici”, ma limitati—gli auditor possono coprire i percorsi importanti senza annegare in opzioni e stati di configurazione.

Perché percorsi di codice più semplici sono più facili da revisionare

Ogni ramo, modalità e feature opzionale moltiplica ciò di cui i reviewer devono occuparsi. Interfacce più semplici restringono lo spazio degli stati possibili, migliorando la qualità della review in due modi:

  1. I reviewer possono concentrarsi su pochi flussi critici per la sicurezza invece di inseguire casi limite.
  2. È più facile notare quando qualcosa è “fuori posto” (un’allocazione inaspettata, un parsing rischioso, un confronto sensibile al timing).

Un workflow leggero di verifica che puoi adottare

Tieni le cose noiose e ripetibili:

  • Test: aggiungi unit test più test vector per ogni primitivo; eseguili in CI a ogni cambiamento.
  • Review: richiedi una checklist orientata alla sicurezza per le modifiche che toccano chiavi, randomness, serializzazione e confronti.
  • Monitoraggio: registra motivi d’errore ad alto livello (non i segreti), allerta su picchi di fallimenti di decrittazione/verifica e traccia le versioni delle dipendenze così sai quando il codice crittografico è cambiato sotto di te.

Questa combinazione non sostituirà la revisione esperta, ma alza il livello minimo: meno sorprese, rilevamento più rapido e codice che puoi davvero ragionare.

Dove i sistemi crittografici ancora falliscono (anche con buoni primitivi)

Anche se scegli primitive ben considerate come X25519 o un’API minimale in stile NaCl, i sistemi si rompono ancora nelle parti sporche: integrazione, codifica e operazioni. La maggior parte degli incidenti reali non è “la matematica era sbagliata”, ma “la matematica è stata usata male”.

Insidie d’integrazione (i soliti sospetti)

Gli errori di gestione delle chiavi sono comuni: riusare chiavi a lungo termine dove serve una chiave effimera, conservare chiavi nel controllo versione, o confondere "chiave pubblica" e "chiave segreta" perché sono entrambe semplici array di byte.

L’uso scorretto dei nonce è un recidivo. Molte schede di cifratura autenticata richiedono un nonce unico per chiave. Duplicare un nonce (spesso tramite reset di contatori, race multi-processo o assunzioni “abbastanza casuale”) può compromettere la confidenzialità o l’integrità.

Problemi di codifica e parsing creano fallimenti silenziosi: confusione base64 vs hex, perdita di zeri iniziali, endianness incoerente o accettare più codifiche che poi confrontano in modo diverso. Questi bug possono trasformare una “firma verificata” in una “qualcosa verificato” sbagliata.

La gestione degli errori può essere pericolosa in entrambe le direzioni: restituire errori dettagliati che aiutano un attaccante, o ignorare i fallimenti di verifica e proseguire comunque.

Insidie operative che annullano la buona crittografia

I segreti trapelano attraverso log, report di crash, analytics ed endpoint di debug. Le chiavi finiscono anche in backup, immagini di VM e variabili d’ambiente condivise troppo ampiamente. Nel frattempo, aggiornamenti di dipendenze (o la loro assenza) possono lasciarti bloccato su un’implementazione vulnerabile anche se il design era solido.

Checklist di mitigazione (per non-criptografi)

  • Tratta i nonce come requisito di progetto: documenta le regole di unicità e testa il riuso.
  • Definisci una codifica canonica per chiavi/messaggi; rifiuta tutto il resto.
  • Fallisci chiudendo: se la verifica fallisce, fermati e mostra un errore generico.
  • Tieni i segreti fuori dai log; aggiungi test automatici di redazione dei log.
  • Conserva le chiavi in un secret manager dedicato; ruotale e limita l’accesso.
  • Pin e revisa le dipendenze crittografiche; pianifica aggiornamenti e audit.

Scegliere approcci di crypto engineering per il tuo prodotto

Build with shared guardrails
Coinvolgi altri così piani, cambi e rollback restano visibili e revisionabili.

I buoni primitivi non producono automaticamente un prodotto sicuro. Più scelte esponi—modalità, padding, codifiche, "tweak" custom—più modi i team possono accidentalmente costruire qualcosa di fragile. Un approccio security-by-construction parte scegliendo un percorso ingegneristico che riduca i punti di decisione.

Un framework pratico per decidere

Usa una libreria di alto livello (API one-shot come “cifra questo messaggio per quel destinatario”) quando:

  • Il tuo team non è dedicato alla crittografia.
  • Hai bisogno di default sicuri (gestione nonce, autenticazione, formati delle chiavi) più che di flessibilità.
  • Vuoi minimizzare il “glue code” che può reintrodurre modalità di errore.

Componi primitivi di basso livello (AEAD, hash, scambio di chiavi) solo quando:

  • Hai una specifica di protocollo chiara e requisiti reali di interoperabilità.
  • Puoi assegnare responsabilità per review, test vector e manutenzione a lungo termine.
  • Puoi dimostrare di non stare reinventando un protocollo esistente.

Una regola utile: se il tuo design doc contiene “sceglieremo la modalità più tardi” o “saremo semplicemente attenti con i nonce”, stai già pagando per troppe manopole.

Domande da fare a vendor e team interni

Chiedi risposte concrete, non linguaggio di marketing:

  • Design API: L’API rende difficile rappresentare stati non sicuri? Dimensioni dei nonce, dimensioni delle chiavi e scelte algoritmiche sono vincolate?
  • Defaults: Cosa succede se gli sviluppatori forniscono solo una chiave e il plaintext? La cifratura è sempre autenticata (AEAD) o si può accidentalmente fare “solo cifra”?
  • Postura sui side-channel: Quali operazioni sono pensate per essere constant-time? Qual è il modello di minaccia per timing, cache e leak da branche?
  • Gestione chiavi: Come vengono generate, conservate, ruotate e azzerate le chiavi? I formati di chiave sono espliciti e versionati?
  • Audit e manutenzione: Quando è stato l’ultimo audit indipendente? Come vengono gestite le vulnerabilità? Esiste un changelog con modifiche rilevanti per la sicurezza?

Igiene ingegneristica che paga

Tratta la crittografia come codice critico per la sicurezza: mantieni l’API piccola, pinna versioni, aggiungi known-answer tests e fai fuzzing su parsing/serializzazione. Documenta cosa non supporterai (algoritmi, formati legacy) e costruisci migrazioni invece di “switch di compatibilità” che rimangono per sempre.

Azioni concrete: applicare security-by-construction questa settimana

Security-by-construction non è uno strumento nuovo da comprare—è un insieme di abitudini che rendono più difficile creare intere categorie di bug. Il filo comune nell’ingegneria in stile DJB è: mantieni le cose abbastanza semplici da poterle comprendere, rendi le interfacce abbastanza strette da limitare l’uso scorretto, scrivi codice che si comporti allo stesso modo anche sotto attacco e scegli default che falliscano in modo sicuro.

I punti da tenere sulla lavagna

  • La semplicità è una caratteristica di sicurezza. Componenti più piccoli, meno stati e meno rami di configurazione lasciano meno posti per comportamenti sorprendenti.
  • Interfacce strette prevengono l’uso “creativo”. Preferisci API che accettano un solo formato corretto piuttosto che molte input “quasi corretti”.
  • Il pensiero constant-time riduce fughe invisibili. Anche se il tuo primitivo è solido, il codice circostante può trapelare segreti tramite timing, branche o pattern di accesso alla memoria.
  • Default sicuri battono opzioni infinite. Ogni manopola aggiunge una nuova combinazione da testare—e solitamente un nuovo modo per configurare male.

Lista d’azione per una settimana per i team

  1. Inventario: elenca ovunque fai crittografia (impostazioni TLS, hashing password, firma token, scambio chiavi, generazione numeri casuali). Nota libreria esatta e configurazione in uso.
  2. Sostituisci pattern rischiosi: rimuovi crypto fatta in casa, encoding/decoding “furbo” e API ricche di funzionalità che possono essere usate male. Standardizza su un piccolo set di primitivi opinati e un unico modo per invocarli.
  3. Costringi le interfacce: incapsula le chiamate crittografiche dietro un modulo interno stretto con superficie minima (pochi parametri, tipi forti, validazione degli input chiara).
  4. Aggiungi test che catturano regressioni: known-answer tests per i primitivi, fuzz tests per i parser e controlli su “nessuna branch dipendente dal segreto” nei percorsi caldi.
  5. Blocca i default: imposta baseline sicure nel codice (non nelle wiki) e richiedi una review esplicita per deviare.

Se vuoi una checklist strutturata per questi passaggi, considera di aggiungere una pagina interna di “crypto inventory” accanto ai documenti di sicurezza (ad esempio, /security).

Nota sulla “security-by-construction” nella consegna rapida di app

Queste idee non sono limitate alle librerie crittografiche—si applicano a come costruisci e distribuisci software. Se usi un flusso di lavoro basato su generazione rapida (per esempio, Koder.ai, dove crei app web/server/mobile via chat), gli stessi principi appaiono come vincoli di prodotto: mantenere pochi stack supportati (React per il web, Go + PostgreSQL per il backend, Flutter per mobile), enfatizzare la pianificazione prima di generare cambiamenti e rendere economico il rollback.

Nella pratica, funzionalità come planning mode, snapshot e rollback ed esportazione del codice sorgente aiutano a ridurre il "raggio d’azione" degli errori: puoi rivedere l’intento prima che i cambiamenti atterrino, ripristinare rapidamente quando qualcosa va storto e verificare che ciò che gira corrisponda a ciò che è stato generato. È lo stesso istinto security-by-construction della compartimentazione di qmail—applicato alle pipeline moderne di delivery.

Domande frequenti

What does “security-by-construction” mean in practice?

Security-by-construction significa progettare il software in modo che la via più sicura sia anche la più semplice. Invece di affidarsi alle persone che ricordino lunghe liste di controllo, si vincola il sistema in modo che gli errori comuni siano difficili da commettere e gli errori inevitabili abbiano un impatto limitato (raggio d’azione più piccolo).

Why does simplicity reduce security risk?

La complessità crea interazioni nascoste e casi limite difficili da testare e facili da configurare male.

Vantaggi pratici della semplicità includono:

  • meno percorsi di codice da auditare e fuzzare
  • meno combinazioni di configurazione che possono disabilitare protezioni per errore
  • più facile ragionare sui modi di guasto quando qualcosa va storto
What is a “tight interface,” and how do I design one?

Un’interfaccia stretta fa meno e accetta meno variazioni. Evita input ambigui e riduce le modalità opzionali che creano una “sicurezza per configurazione”.

Un approccio pratico:

  • allowlist degli input (tipi, dimensioni, codifiche)
  • rifiutare campi sconosciuti invece di fare parsing “best-effort”
  • mantenere operazioni potenti/pericolose dietro endpoint separati e chiaramente definiti
What can qmail teach modern systems about limiting blast radius?

qmail suddivide la gestione della posta in piccoli programmi (ricevere, mettere in coda, consegnare, ecc.) con responsabilità ristrette. Questo riduce i modi di guasto perché:

  • un crash in una parte è meno probabile che corrompa tutto
  • un bug in un componente non concede automaticamente privilegi completi
  • ogni componente è più facile da testare e auditare in isolamento
What is “constant-time,” and why should I care?

Il comportamento constant-time mira a rendere il tempo di esecuzione (e spesso i pattern di accesso alla memoria) indipendenti dai valori segreti. Questo è importante perché gli attaccanti possono a volte inferire segreti misurando tempi, effetti sulla cache o differenze tra “fast path” e “slow path” su molte prove.

Si tratta di prevenire «fughe invisibili», non solo di scegliere algoritmi forti.

How can I spot timing-leak risks without reading assembly?

Identifica cosa è segreto (chiavi private, segreti condivisi, chiavi MAC, tag di autenticazione) e poi cerca posti dove i segreti influenzano il controllo di flusso o l’accesso alla memoria.

Segnali d’allarme:

  • branche if su dati segreti
  • lookup in array/tabelle indicizzati da segreti
  • loop che terminano in base a segreti
  • confronti che ritornano in anticipo (uguaglianza non constant-time)

Verifica anche che la tua dipendenza crittografica dichiari esplicitamente comportamento constant-time per le operazioni che usi.

Why are Curve25519/X25519 considered “harder to misuse”?

X25519 è una funzione specifica di scambio chiavi costruita su Curve25519. È popolare perché riduce i "foot-gun": meno parametri da scegliere, prestazioni solide e un design che favorisce implementazioni constant-time.

È una corsia di default più sicura per lo scambio di chiavi—purché tu gestisca correttamente autenticazione e gestione delle chiavi.

Does X25519 authenticate the other side by itself?

No. X25519 fornisce accordo di chiave (un segreto condiviso) ma non prova l’identità dell’altra parte.

Per prevenire l’usurpazione, abbinalo a meccanismi di autenticazione come:

  • certificati/firme (es. in TLS)
  • una chiave pre-condivisa
  • uno schema di firma a livello applicativo

Senza autenticazione, potresti comunque trovarsi a "parlare in modo sicuro" con la parte sbagliata.

What’s the big idea behind NaCl’s “box” and “secretbox” APIs?

NaCl riduce gli errori offrendo operazioni di alto livello già composte in modo sicuro, invece di esporre un buffet di algoritmi e modalità.

Costrutti comuni:

  • crypto_box: cifratura autenticata a chiave pubblica (tu + chiave del destinatario + nonce → ciphertext)
  • crypto_secretbox: cifratura autenticata a chiave condivisa

Il beneficio pratico è evitare errori di composizione comuni (come cifrare senza protezione d’integrità).

Where do real systems fail even when they use good crypto primitives?

I buoni primitivi falliscono quando integrazione e operazioni sono approssimative. Problemi comuni:

  • riuso di nonce (spesso da reset dei contatori, race tra processi o assunzioni su random)
  • codifiche incoerenti (hex vs base64, perdita di zeri iniziali, differenze di endianness)
  • gestione degli errori pericolosa (troppi dettagli o ignorare fallimenti di verifica)
  • perdite di chiavi tramite log, crash report, backup o variabili d’ambiente troppo larghe

Mitigazioni:

  • documentare regole di unicità dei nonce e testarne il riuso
  • imporre una codifica canonica e rifiutare le altre
  • fallire chiudendo su errori di verifica con messaggi generici
  • conservare le chiavi in un secret manager e limitare/ruotare gli accessi

Related posts