
The hmac-bcrypt password hashing function
Questo repository contiene implementazioni di riferimento della funzione di hashing delle password hmac-bcrypt in diversi linguaggi. Ogni implementazione di riferimento cerca di essere una conversione 1:1 dell'implementazione originale in C e Perl creata da @epixoip dove possibile, e sono pienamente compatibili tra loro (cioè producono e validano gli stessi valori di hash).
Ogni implementazione di riferimento definisce due funzioni procedurali con i seguenti pseudo-prototipi:
string hmac_bcrypt_hash(password, settings?, pepper?)
boolean hmac_bcrypt_verify(password, expected, pepper?)
Fare riferimento ai casi di test forniti con ciascuna implementazione di riferimento per capire come integrare e utilizzare queste funzioni nel proprio progetto. È stata scelta un'interfaccia procedurale per semplicità, ma siete liberi di incorporare queste funzioni in classi o oggetti come preferite.
Il parametro settings in questo contesto si riferisce a una stringa di impostazioni bcrypt standard contenente l'identificatore dell'hash (2a), il costo log2 (es., 13) e un valore di sale opzionale di 22 byte codificato in radix64 (es., LhayLxezLhK1LhWvKxCyLO). Questi valori sono concatenati in una stringa delimitata da dollari; es., $2a$13$LhayLxezLhK1LhWvKxCyLO.
Il parametro settings è opzionale; nella maggior parte dei casi, dovrebbe essere lasciato nullo/vuoto per utilizzare il costo predefinito di 13 e un sale generato. Al massimo, se si desidera utilizzare un valore di costo diverso da 13, si può fornire solo l'id + il valore di costo (es., $2a$10$). Non è consigliato creare e fornire i propri valori di sale!
Il parametro pepper definisce un segreto condiviso globale ed è anch'esso opzionale; se è nullo/vuoto, viene utilizzato il valore predefinito di hmac_bcrypt. Questo serve principalmente come difesa contro gli attacchi shucking, ma può anche essere usato per aumentare la sicurezza, la difficoltà e il costo di cracking (specialmente se usato in combinazione con un HSM).
La funzione di hashing delle password hmac-bcrypt impiega bcrypt con pre-hashing e post-hashing appropriati, combinati con un pepper opzionale. In pseudo-codice, questo è abbastanza semplice:
pre_hash = hmac_sha512_base64(password, pepper)
mid_hash = bcrypt(pre_hash, settings)
post_hash = hmac_sha512_base64(mid_hash, pepper)
return settings + post_hash
Il pre-hashing viene impiegato per consentire lunghezze di input maggiori del massimo di bcrypt di 72 byte. È stata scelta SHA-512 per la sua dimensione di parola a 64 bit, che è favorevole ai difensori su CPU ma ostacola gli attaccanti su GPU. Tuttavia, un valore SHA-512 grezzo non può essere utilizzato per diverse ragioni:
Per mitigare gli attacchi shucking, il pre-hash deve essere salato -- o in questo caso, pepato -- e HMAC fornisce un veicolo comodo per la chiave di un hash. Il valore HMAC risultante viene quindi codificato con base64 per produrre input puliti, ASCII inferiore, che mitigano i problemi con byte nulli e dati binari.
Il lettore attento noterà che hmac_sha512_base64 produce 88 byte di dati, mentre bcrypt ha una dimensione massima di input di 72 byte. Questo non è un problema, anzi è preferibile rispetto all'utilizzo di un algoritmo di hash che produce meno dati di input come sha256. Vogliamo riempire tutti i 72 byte, e non si perde sicurezza troncando sha512 a 432 bit (questo è maggiore dei 384 bit forniti da sha384).
Il post-hashing viene impiegato in gran parte per differenziare gli hash hmac-bcrypt dagli hash bcrypt -- cioè, le lunghezze differiranno -- ma anche per aggiungere un ulteriore livello di protezione grazie al pepper. Il passo del post-hashing potrebbe persino essere eseguito con il valore del pepper memorizzato in un HSM (altamente raccomandato!) per una protezione ulteriore.
Sebbene la memoria dura (memory hardness) sia stata un esperimento interessante, il percorso corretto per ottenere resistenza all'accelerazione è chiaramente la cache dura (cache hardness). Le velocità e la larghezza di banda della memoria continuano ad aumentare, mentre la RAM diventa più grande, più economica e più densa. Ma le dimensioni della cache, le velocità della cache e i costi della cache sono relativamente statici. Anche le istruzioni hardware di scatter/gather non hanno avuto l'impatto drammatico che un tempo si prevedeva potessero avere sugli algoritmi cache-hard.
I migliori algoritmi memory-hard -- Argon2 e scrypt -- sono in realtà meno resistenti all'accelerazione rispetto agli algoritmi cache-hard per tempi di esecuzione target inferiori a 1000ms, il che li rende ottimi KDF ma non ottimi per l'autenticazione in tempo reale.
Idealmente, si dovrebbe usare una funzione di hashing delle password intenzionalmente cache-hard, come pufferfish o bscrypt. Tuttavia, queste funzioni sono più recenti, meno studiate e hanno poche librerie disponibili. bcrypt, invece -- pur essendo cache-hard involontariamente -- è prontamente disponibile praticamente per ogni linguaggio e framework. Degli algoritmi che abbiamo prontamente disponibili, bcrypt fornisce la maggiore resistenza all'accelerazione per l'autenticazione interattiva in tempo reale (runtime target < 1000ms), quindi la risposta ovvia è sfruttare il bcrypt che abbiamo a disposizione.
Tuttavia, bcrypt ha alcune limitazioni notevoli, come i suoi critici molto vocali non mancano di sottolineare:
hmac-bcrypt affronta entrambi questi problemi, e altro ancora.