- Pubblicato il
Gli OTP sono ovunque: ma come funzionano, esattamente?
- Authors
- Name
- Matteo Vignoli
Negli ultimi anni gli OTP (One-Time Passwords) sono diventati uno dei metodi più diffusi per l'autenticazione sicura; si tratta di password che sono valide per una singola sessione o transazione e che scadono dopo un breve periodo di tempo (di solito 30 secondi), rendendo l’accesso ai sistemi molto più sicuro rispetto alle sole password tradizionali.
Un OTP (One-Time Password) è una sequenza di caratteri alfanumerica temporanea generata in modo casuale oppure tramite un algoritmo e utilizzata per autenticare un utente una sola volta. Dopo il primo utilizzo o dopo che il periodo di validità è scaduto, l'OTP diventa inutile e non può essere riutilizzato.
Esistono due tipi principali di OTP:
- TOTP (Time-based One-Time Password): Generato in base all'ora corrente e ha una durata limitata (ad esempio, 30 secondi).
- HOTP (HMAC-based One-Time Password): Generato in base a un contatore incrementale. L'OTP viene utilizzato una sola volta e il contatore viene incrementato ogni volta che viene generata una nuova password.
In questo post ci concentreremo su come implementare un TOTP, uno degli standard più diffusi, basato su tempo e hash crittografico.
Come Funziona l'Algoritmo TOTP?
L'algoritmo TOTP (che fondamentalmente è un HOTP in cui il contatore è sostituito dal momento temporale) genera un OTP utilizzando tre elementi principali:
- Una secret condiviso tra il client e il server, normalmente una stringa di almeno 16 caratteri formattata in base-32;
- Il periodo temporale corrente, suddiviso in intervalli di tempo (tipicamente 30 secondi);
- Una funzione di HMAC-SHA1 (ma la specifica permette anche altre varianti come SHA-256 o SHA-512) per creare un codice crittografico sicuro;
Il processo consiste nel combinare il tempo corrente con il secret condiviso per produrre una password temporanea, che sarà quindi valida solo per un breve periodo di tempo - di solito 30 secondi, ma è permessa una tolleranza fino a 60 o 90 secondi.
Implementazione dell'Algoritmo TOTP
Per implementare l'algoritmo TOTP possiamo seguire questi passaggi:
1. Calcolo dell'intervallo di tempo
Prima di tutto dobbiamo prendere il tempo corrente e dividerlo per un passo temporale fisso, generalmente di 30 secondi. Questo ci permette di mappare il tempo su un contatore che cambia ogni 30 secondi.
const timeStep = 30;
// Prendo l'intervallo di tempo corrente
const getTimeStep = (): number => {
const currentTime = Math.floor(Date.now() / 1000);
return Math.floor(currentTime / timeStep); // intervallo
};
2. Generazione dell'HMAC
Usiamo ora l'algoritmo HMAC-SHA1 per combinare il secret condiviso con l'intervallo di tempo attuale:
import crypto from 'crypto';
// Funzione per generare HMAC-SHA1
const generateHMAC = (key: string, message: string): Buffer => {
return crypto.createHmac('sha1', key).update(message).digest();
};
3. Troncamento dinamico
L'hash così generato sarebbe un tantino scomodo da digitare per l'utente (sarebbe di 20 byte con SHA-1, ad esempio), soprattutto senza errori e nel tempo limitato di 30 secondi! Il passo successivo, quindi, è prenderne soltanto una parte in un processo chiamato troncamento dinamico, dal quale otterremo poi un numero intero da cui deriveremo l'OTP finale.
Selezione dell'offset: L'ultimo byte dell'HMAC è utilizzato per determinare l'offset di inizio della porzione da estrarre. Gli ultimi 4 bit di questo byte sono presi come valore dell'offset, che sarà quindi un numero compreso tra 0 e 15 (0x0 e 0xF) - l'algoritmo selezionerà uno dei 16 possibili punti di partenza nei 20 byte dell'HMAC.
Estrazione dei 4 byte: A partire dall'offset calcolato, si selezionano i successivi 4 byte dell'HMAC.
Conversione in intero: I 4 byte estratti vengono trattati come un numero intero non negativo a 31 bit. Il bit più significativo (MSB) del primo byte viene azzerato per assicurarsi che il numero risultante sia positivo.
// Funzione per il troncamento dinamico
const dynamicTruncate = (hmac: Buffer): number => {
const offset = hmac[hmac.length - 1] & 0xf; // ultimi 4 bit dell'ultimo byte
const trunc =
((hmac[offset] & 0x7f) << 24) | // Primo byte, maschera il bit più significativo
((hmac[offset + 1] & 0xff) << 16) | // Secondo byte
((hmac[offset + 2] & 0xff) << 8) | // Terzo byte
(hmac[offset + 3] & 0xff); // Quarto byte
return truc; // Ritorno il numero a 31 bit
};
- Modulo: L'intero ottenuto viene infine ridotto tramite un'operazione di modulo 10n dove n è il numero di cifre desiderato per l'OTP finale (ad esempio 6).
const generateTOTP = (trunc: string, digits = 6): string => {
const otp = trunc % Math.pow(10, digits);
return otp.toString().padStart(digits, '0'); // Padding con zeri iniziali se necessario
};
5. Verifica dell'OTP
Per verificare l'OTP il server deve rigenerare l'OTP utilizzando lo stesso secret e intervallo temporale dell'utente. Per migliorare l'usabilità spesso si permette un piccolo margine di tolleranza - verificando l'OTP anche per l'intervallo precedente o successivo, così da compensare eventuali differenze di orologio o ritardi nella digitazione.
const verifyTOTP = (token: string, secret: string, threshold = 1): boolean => {
const currentStep = getTimeStep();
// Verifica nell'intervallo attuale, precedente e successivo
for (let tolerance = -threshold; tolerance <= threshold; tolerance++) {
const timeStepVal = (currentStep + tolerance).toString(16).padStart(16, '0');
const hmac = generateHMAC(secret, timeStepVal);
const truncated = dynamicTruncate(hmac);
const otp = (truncatedVal % Math.pow(10, token.length)).toString().padStart(token.length, '0');
if (otp === token) {
return true; // l'OTP + valido
}
}
return false; // l'OTP non è corretto
};
Conclusione
Sebbene il meccanismo dietro un OTP, alla fine, non sià poi così complicato una volta applicato l'algoritmo corretto, tuttavia non è proprio il caso di usare una versione "fatta in casa" per questo tipo di operazioni! L'autenticazione a 2 fattori (2FA) è estremamente importante nel prevenire attacchi e furti di credenziali, meglio lasciar fare a librerie testate e verificate, o meglio ancora a provider esterni qualificati.