Quando si parla di One Time Password (OTP), ovvero di password o pin utilizzabili per una unica sessione di login o transazione, gli standard di riferimento sono due:
- HOTP (HMAC-based One-time Password) pubblicato dalla Internet Engineering Task Force (IETF) nella RFC 4226;
- TOTP (Time-based One-time Password) una estensione di HOTP pubblicato dalla Internet Engineering Task Force (IETF) nella RFC 6238.
HOTP
Come specificato nel nome lo standard HOTP per la generazione di una OTP si basa sull’utilizzo dell’algoritmo HMAC. Gli elementi in gioco quando si definiscono le specifiche per l’utilizzo di tale standard sono quindi le seguenti:
- La funzione crittografica di hashing H utilizzato nell’HMAC che per default è lo SHA-1;
- La chiave crittografica K di lunghezza arbitraria che deve rimanere segreta e nota esclusivamente al server e, a seconda dell’utilizzo che si fa dell’OTP, al client;
- La lunghezza
d
dell’OTP che si vuole ottenere che, considerando che in genere si vuole garantita la human readability della password, può essere compresa tra 6 e 10 caratteri.
L’algoritmo è molto semplice e prevede l’utilizzo di un contatore (di eventi) che viene incrementato ad ogni generazione della password. Tale contatore viene cifrato con HMAC-SHA1 generando una sequenza di 160 bit ovvero 20 byte. La specifica prevede poi una sequenza di operazioni che riducono il numero di byte per ottenere un OTP leggibile nel seguente modo:
- Gli ultimi 4 bit, ovvero quelli che appartengono al byte 19° della sequenza, sono utilizzati come
offset
. - I byte dall’
offset
all’offset + 4
sono estratti dalla sequenza e concatenati. Al primo di questi è rimosso il bit più significativo. Complessivamente si ottengono 31 bit. - Alla sequenza risultante viene applicato l’operazione di modulo con 10d che elimina i bit più significativi ottenendo un OTP di lunghezza
d
.
Il codice java che esegue le operazioni appena descritte è mostrato di seguito. Per chiarezza si ricorda che una variabile di tipo long
in java occupa 8 byte mentre una di tipo int
ne occupa 4.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
public static long hotp(byte[] secret, long counter) throws NoSuchAlgorithmException, InvalidKeyException { // Signing Key SecretKeySpec signKey = new SecretKeySpec(secret, "HmacSHA1"); // Preparing byte array to sign ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); buffer.putLong(counter); // Signing the byte array Mac mac = Mac.getInstance("HmacSHA1"); mac.init(signKey); byte[] hash = mac.doFinal(buffer.array()); // Determina l'offset recuperando l'ultimo byte (il 20-esimo) ed eseguendo un and bit a bit con // 00000000 00000000 00000000 00001111 int offset = hash[19] & 0xF; // Requpero il primo elemento a partire dall'offset ed eseguendo un and bit a bit con // 00000000 00000000 00000000 00000000 00000000 00000000 00000000 01111111 long truncatedHash = hash[offset] & 0x7F; for (int i = 1; i < 4; i++) { // Eseguo lo shift a sinistra di 8 bit truncatedHash <<= 8; // Recupero il successivo elemento della sequenza ed eseguendo un and bit a bit con // 00000000 00000000 00000000 00000000 00000000 00000000 00000000 11111111 truncatedHash |= hash[offset + i] & 0xFF; } // Restituisco i 6 digit meno significativi return (truncatedHash %= 1000000); } |
Caso Pratico
Per comprendere i diversi step dell’algoritmo facciamo un caso pratico e supponiamo che il nostro contatore sia arrivato al valore 1876876 . La sequenza di operazioni è descritta di seguito con l’ausilio della figura seguente.
Iniziamo con l’osservare che il valore 1876876 di tipo long occupa 8 byte, i quali una volta attraversato il processo crittografico HMAC-SHA1, divengono una sequenza di 20 byte. Si noti che si tratta di byte con segno, ovvero rappresentati in complemento a 2.
[54, -122, 120, 91, 76, 15, 68, 11, -64, -35, -67, 20, -87, 69, 87, -109, 32, -49, -69, -8]
Quindi si recuperano gli ultimi 4 bit della sequenza (step 1 della figura) accedendo al 20-esimo byte, ovvero -8 (11111000 in binario in complemento a due), ed eseguendo una operazione di AND bit a bit con 00001111. Il risultato è 8 quindi i byte coinvolti nei successivi step del processo sono quelli che vanno dalla posizione 9 alla 13, ovvero:
[-64, -35, -67, 20]
Complessivamente il bit sono 32 ma l’algoritmo ne richiede 31. Quindi il primo byte -64 è messo in AND con 0x7F (step 2 della figura) mentre gli altri -35, -67 e 20 sono mesis in AND con 0xFF (step 4, 6 e 8 della figura). I risultati di tali operazioni vengono poi concatenati con operazioni di OR e opportune operazioni di SHIFT a sinistra di 8 bit, fino ad ottenere il valore decimale 1088273684.
Ultimo step del processo è l’operazione di modulo per 106 che determina la rimozione delle cifre più significative oltre la 6°. L’OTP generato sarà quindi 273684 .
TOTP
Questo algoritmo si basa HOTP con la differenza che come fattore variabile non viene utilizza un contatore di eventi, bensì il tempo. Nella pratica vengono considerati degli incrementi, chiamati intervalli, che di solito sono di 30 o 60 secondi. Allo scadere di ogni intervallo un contatore è incrementato ed utilizzato per generare una nuova OTP con HOTP. Questo vuol dire che ogni OTP è valida per la durata di tutto l’intervallo al termine del quale scade.
Il codice per la generazione dell’OTP in questo caso è molto semplice, la sola complicazione è ottenere l’intervallo utilizzato come contatore. Considerando 30 secondi come validità dell’OTP il codice sarà:
1 2 3 4 5 6 7 |
public long totp(byte[] secret) throws NoSuchAlgorithmException, InvalidKeyException { return hotp( secret, getTimeIndex() ); } private long getTimeIndex() { return System.currentTimeMillis() / 1000 / 30; } |
1 2 3 4 5 6 7 8 9 |
Fri Jun 14 16:07:25 CEST 2019 -> 394440 Fri Jun 14 16:07:55 CEST 2019 -> 899730 Fri Jun 14 16:08:25 CEST 2019 -> 440681 Fri Jun 14 16:08:55 CEST 2019 -> 797892 Fri Jun 14 16:09:25 CEST 2019 -> 190574 Fri Jun 14 16:09:55 CEST 2019 -> 189302 Fri Jun 14 16:10:25 CEST 2019 -> 267765 Fri Jun 14 16:10:55 CEST 2019 -> 581482 Fri Jun 14 16:11:25 CEST 2019 -> 322229 |
Codie Sorgente
Il codice sorgente del progetto è disponibile qui otp.