tl; dr: di solito non lo consiglio, ma potrebbe essere utile in circostanze specifiche
Vantaggi di SCRAM
- La password non viene mai inviata al server. Le uniche cose inviate sul filo sono:
- Il nonce
- Il sale
- Il conteggio delle iterazioni
-
HMAC(PBKDF2(password), "Client Key") XOR HMAC(H(HMAC(PBKDF2(password), "Client Key")), AuthMessage)
(seriamente, non lo sto inventando!)
-
HMAC(HMAC(PBKDF2(password), "Server Key"), AuthMessage)
- Il server non conosce mai la password e quindi non può impersonare l'utente su altri server (a condizione che il sale su quei server sia diverso da quello di questo server)
Svantaggi di SCRAM
- Richiede l'uso di PBKDF2. Mentre PBKDF2 è significativamente migliore di un hash veloce, bcrypt è migliore (e al giorno d'oggi dovresti considerare anche Argon2).
- Richiede che tutta la complessità computazionale sia gestita dal client
Il primo punto è sfortunato, ma il secondo punto è il vero killer. Con smartphone e tablet ovunque ora non puoi semplicemente supporre che il client sarà in grado di calcolare rapidamente PBKDF2 con iterazioni sufficienti per essere sicuro.
Quando sarebbe utile SCRAM?
L'uso che vedo per SCRAM è dove il client è affidabile ma il server no. Questo non è mai vero per un'applicazione web (poiché il "client" è il codice JavaScript inviato da ... il server) e solo a volte è vero per le applicazioni locali. I migliori esempi a cui riesco a pensare sono quelli Wikipedia : SMTP, IMAP e XMPP, in cui ti fidi della tua email o chat client ma si stanno autenticando su un server di terze parti potenzialmente dannoso.
Se il client impone un conteggio iterato elevato e sale unico, può ridurre il rischio di dirottamento DNS combinato con una CA canaglia, ma l'hai già fatto con il blocco dei certificati. L'unica cosa che rimane da difendere è il tuo server malevolo, che può essere utile in alcuni casi, ma solo se i guadagni superano gli svantaggi di SCRAM.
SCRAM potrebbe essere utile per impedire al server di ottenere la password se si sa che verrà utilizzata una password complessa, ma che la password verrà riutilizzata altrove (se non viene riutilizzata, perché ti importa se il server potrebbe saperlo?). Sfortunatamente questo non può essere applicato, e l'ideale sarebbe comunque utilizzare una password univoca.
Come funziona SCRAM?
In SCRAM, il server memorizza:
- Il sale
- Il conteggio delle iterazioni
-
ServerKey = HMAC(PBKDF2(password), "Server Key")
(dove "Server Key"
è una chiave HMAC costante nota) *
-
StoredKey = H(HMAC(PBKDF2(password), "Client Key"))
(idem per "Client Key"
)
Per autenticare:
- Il client invia lo username e un nonce casuale
- Il server aggiunge il proprio nonce casuale al client e risponde con il conteggio nonce, salt e iterativo per l'utente
- Il client calcola quanto segue:
-
ClientKey = HMAC(PBKDF2(password), "Client Key")
-
StoredKey = H(ClientKey)
-
ClientSignature = HMAC(StoredKey, AuthMessage)
(dove AuthMessage
è la concatenazione di tutti i messaggi precedentemente scambiati e il nonce, delimitato da virgole)
-
ClientProof = ClientKey XOR ClientSignature
- Il client invia il nonce e
ClientProof
al server
- Il server calcola quanto segue:
-
ClientSignature = HMAC(StoredKey, AuthMessage)
-
ClientKey = ClientProof XOR ClientSignature
-
ServerSignature = HMAC(ServerKey, AuthMessage)
- Il server verifica
H(ClientKey) == StoredKey
, dimostrando che il client conosce la password (o almeno ClientKey
)
- Il server risponde con
ServerSignature
- Il client calcola
ServerSignature
e lo confronta con il valore restituito dal server, verificando che il server conosca ServerKey
Questo è piuttosto semplificato, in quanto esclude alcune cose come la selezione hash, la codifica, il formato dei messaggi e il binding dei canali, ma dovrebbe dare una buona idea di come funziona.
* Tutti gli usi di PBKDF2 includono ovviamente anche il conteggio di sale e iterazione, SCRAM imposta dkLen
sulla lunghezza di uscita dell'hash selezionato