Una cosa che non ho visto menzionata qui, sebbene sia un'estensione della risposta di Marcus Adams, è che non dovresti usare una singola informazione sia per identificare che per autenticare un utente se c'è la possibilità di attacchi temporizzati , il che può usa le differenze nei tempi di risposta per indovinare quanto è arrivato un confronto di stringhe.
Se stai utilizzando un sistema che utilizza una "chiave" per cercare l'utente o la credenziale, quella parte di informazione potrebbe essere indovinata in modo incrementale nel tempo inviando migliaia di richieste ed esaminando il tempo impiegato dal tuo database per trovare (o meno trova) un record. Ciò è particolarmente vero se la "chiave" è archiviata in testo normale invece di un hash unidirezionale della chiave. Ti consigliamo di archiviare le chiavi degli utenti in un testo normale o crittografate simmetricamente se devi essere in grado di visualizzare nuovamente la chiave per l'utente.
Avendo una seconda informazione, o "segreto", puoi prima cercare l'utente o la credenziale utilizzando la "chiave", che potrebbe essere vulnerabile a un attacco di temporizzazione, quindi utilizzare una funzione di confronto a tempo per controllare il valore di il segreto".
Ecco l'implementazione di quella funzione da parte di Python:
https://github.com/python/cpython/blob/cd8295ff758891f21084a6a5ad3403d35dda38f7/Modules/_operator.c#L727
Ed è esposto nella hmac
libreria (e probabilmente in altri):
https://docs.python.org/3/library/hmac.html#hmac.compare_digest
Una cosa da notare qui è che non penso che questo tipo di attacco funzionerà su valori che sono sottoposti ad hashing o crittografati prima della ricerca, perché i valori che vengono confrontati cambiano casualmente ogni volta che un carattere nella stringa di input cambia. Ho trovato una buona spiegazione di questo qui .
Le soluzioni per l'archiviazione delle chiavi API sarebbero quindi:
- Usa una chiave e un segreto separati, usa la chiave per cercare il record e usa un confronto a tempo per controllare il segreto. Ciò consente di mostrare nuovamente all'utente la chiave e il segreto.
- Utilizzare una chiave e un segreto separati, utilizzare la crittografia simmetrica e deterministica sul segreto ed eseguire un normale confronto dei segreti crittografati. Ciò consente di mostrare di nuovo all'utente la chiave e il segreto e potrebbe evitarti di dover implementare un confronto temporizzato.
- Usa una chiave e un segreto separati, visualizza il segreto, hash e memorizzalo, quindi fai un normale confronto del segreto con hash. Ciò elimina la necessità di utilizzare la crittografia a due vie e ha l'ulteriore vantaggio di mantenere il tuo segreto al sicuro se il sistema è compromesso. Ha il rovescio della medaglia che non puoi mostrare di nuovo il segreto all'utente.
- Utilizzare una singola chiave , mostrarla una volta all'utente, eseguirne l'hashing, quindi eseguire una normale ricerca della chiave crittografata o hash. Questo utilizza una singola chiave, ma non può essere mostrato di nuovo all'utente. Ha il vantaggio di mantenere le chiavi al sicuro se il sistema è compromesso.
- Utilizzare una singola chiave , mostrarla una volta all'utente, crittografarla ed eseguire una normale ricerca del segreto crittografato. Può essere mostrato di nuovo all'utente, ma a costo di avere chiavi vulnerabili se il loro sistema è compromesso.
Di questi, penso che 3 sia il miglior equilibrio tra sicurezza e convenienza. L'ho visto implementato su molti siti Web quando si ottengono le chiavi emesse.
Inoltre, invito tutti i veri esperti di sicurezza a criticare questa risposta. Volevo solo che questo fosse un altro punto di discussione.