Dal punto di vista matematico le funzioni hash sono funzioni non iniettive, ovvero non invertibili, che mappano una stringa di lunghezza arbitraria in una di lunghezza predefinita detta impronta o message digest. Esistono diversi algoritmi che realizzano funzioni hash (es. MD5 e SHA-1) con diverse proprietà e caratteristiche che ne determinano l’uso più appropriato.
La funzione di hash ideale deve avere tre proprietà fondamentali:
- essere estremamente semplice da calcolare a partire da un qualunque tipo di dato;
- essere estremamente difficile o quasi impossibile risalire al testo che ha portato ad un dato hash;
- essere estremamente improbabile che due messaggi differenti, anche simili, abbiano lo stesso hash.
In informatica tali funzioni sono utilizzate in diversi ambiti, ad esempio:
- come funzioni crittografiche per la generazione dell’impronta di un documento che deve essere firmato digitalmente;
- nelle comunicazioni tra agenti software dove l’hash del messaggio viene trasmesso insieme allo stesso, in modo che il ricevente possa ricalcolarlo e verificare l’integrità del messaggio;
- nella programmazione per l’implementazione delle tabelle di hash o dizionari, che sono strutture dati che risultano molto efficienti nelle le operazioni di ricerca.
L’applicazione più semplice delle funzioni di hash è sicuramente la conservazione della password utente in una applicazione che richiede credenziali per l’accesso. In generale in applicazioni enterprise l’autenticazione è demandata a sistemi esterni come Active Directory, LDAP o più complessi Identity Management System. Applicazioni meno importanti, però, hanno la necessità di implementare autonomamente il proprio sistema di autenticazione e devono quindi conservare le password utente nella propria base dati. Questo può avvenire o utilizzando un base dati che supporta la crittografazione delle informazioni, oppure utilizzando funzioni di hash, ottenendo, a mio avviso, con poco effort un sistema comunque efficace, economico e soprattutto sicuro.
L’idea alla base dell’algoritmo è mostrato nella figura seguente. In fase di registrazione la password utente subisce il processo di hashing e l’impronta viene conservato nella base dati. In fase di autenticazione la password inserita subisce la medesima funzione di hashing e l’impronta generata è confrontato con quanto memorizzato in base dati. La sicurezza dell’algoritmo è garantita dal fatto che il sistema non conosce la password utente ma solamente la sua impronta che, per sua natura, non è invertibile.
La realizzazione di quanto detto è molto semplice in quanto java mette a disposizione un intero package in cui sono implementate diverse funzioni per la sicurezza, che toccano aree quali: crittografia, infrastruttura a chiave pubblica, comunicazione sicura, autenticazione e controllo degli accessi. In particolare la classe che a noi interessa è java.security.MessageDigest
che fornisce una implementazione di diverse funzioni di hash. L’algoritmo di hashing che utilizzeremo è lo SHA-1 e la funzione di scramble della password e di autenticazione sono mostrati nel codice seguente.
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class PasswordHasher { private static final String ALGORITHM = "SHA-1"; //--------------------------------------------------------------------------------- // SRCAMBLE PASSWORD //--------------------------------------------------------------------------------- public static String scramble(String password) { try { MessageDigest md = MessageDigest.getInstance( ALGORITHM ); md.update( password.getBytes("UTF-8") ); return toHex( md.digest() ); } catch (UnsupportedEncodingException e) { return null; } catch (NoSuchAlgorithmException e) { return null; } } //--------------------------------------------------------------------------------- // AUTHENTICATE PASSWORD //--------------------------------------------------------------------------------- public static boolean authenticate( String password, String hash ) { return hash.equals( scramble( password ) ); } //--------------------------------------------------------------------------------- // PRIVATE METHODS //--------------------------------------------------------------------------------- private static String toHex(byte[] data) { StringBuffer sb = new StringBuffer(); for (byte b : data) { String digit = Integer.toString(b & 0xFF, 16); if (digit.length() == 1) { sb.append("0"); } sb.append(digit); } return sb.toString(); } } |
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 |
public class TestHash { public static void main(String[] args) throws IOException { // Stored password String hash = PasswordHasher.scramble( "massimo0001" ); String password = null; Boolean authenticated = false; do { // Input new password System.out.print( "Enter password:" ); BufferedReader reader = new BufferedReader( new InputStreamReader( System.in ) ); password = reader.readLine(); // Check password authenticated = PasswordHasher.authenticate( password, hash ); // Exit if authenticated if ( !authenticated ) { System.out.println( "Wrong password ... try again." ); } else { System.out.println( "Password correct" ); } } while ( !authenticated ); } } |
1 2 3 4 5 6 |
Enter password:test Wrong password ... try again. Enter password:test0001 Wrong password ... try again. Enter password:massimo0001 Password correct |