Allora, un po' di questioni, vediamo di andare con ordine:
Il getInstance() riceve una stringa con il nome dell'algoritmo. Il nome potrebbe essere "sballato" (es. "MDD5", se fosse un input preso da qualche parte) oppure potrebbe essere sensato ma non supportato su una certa versione della piattaforma Java. Il getInstance segnala la cosa con un NoSuchAlgorithmException, che purtroppo è una eccezione "checked", quindi il programmatore non la può ignorare.
Nel mio esempio "basilare" ho fatto semplicemente uscire NoSuchAlgorithmException dal md5Hex. Ma ci sono diverse varianti che possono aver senso:
1) Nel md5Hex() far uscire NoSuchAlgorithmException (come ho fatto)
2) Nel md5Hex() catturare NoSuchAlgorithmException per fare del "logging" e poi rilanciare fuori dal md5Hex il NoSuchAlgorithmException così come è.
3) Nel md5Hex() catturare NoSuchAlgorithmException (con/senza logging) e lanciare fuori un'altra eccezione differente magari "unchecked" in modo che il programmatore possa non doversene preoccupare subito.
Ma catturare NoSuchAlgorithmException e fare un return null, no, ha davvero poco senso.
E' una stringa di "formato" utilizzata dalla funzionalità di "formattazione delle stringhe" che è stata introdotta in Java 5. Documentati perché è una cosa molto utile in generale.
La documentazione di BigInteger è chiara:
Il digest lo passiamo al BigInteger che lo tratterà come un numero di tot byte (bit x 8) ma con il solo scopo poi di formattarlo in hex (non ci interessa farci calcoli!). Ma il digest NON è un numero (in senso stretto, specifico), è solo un tot di byte "crudi". Quindi con 1 indichiamo al BigInteger di trattarlo come numero positivo, perché non ci interessa altro modo.BigInteger(int signum, byte[] magnitude)
signum - signum of the number (-1 for negative, 0 for zero, 1 for positive).
magnitude - big-endian binary representation of the magnitude of the number.
Il risultato di un algoritmo di hash è un tot di bit "crudi", che non hanno alcun significato particolare, a parte appunto rappresentare una sorta di "firma" di un messaggio di tot byte.
Quindi il MessageDigest dà il risultato come array di byte. Non offre alcun supporto alla "formattazione". Come formattarlo in stringa (tipicamente in hex ma in rari casi in Base64 o altro) è compito nostro.
Prima hai detto, quoto:
Se vuoi fare un file (o DB) con delle password NON in chiaro, allora un hash è ok. Ma l'algoritmo di hashing lo devi stabilire TU a priori. Non è una cosa che puoi far scegliere ad una macchina, non ha senso!
La API della security (package java.security) permette di scoprire dinamicamente tutti i servizi (compresi quelli di message digest) di un certo provider. Quindi puoi prendere l'elenco dei Provider, per ciascuno l'elenco dei service e filtrarli per trovare tutti gli algoritmi di digest disponibili. Se vuoi del codice, non c'è problema. Per ciascun MessageDigest puoi anche sapere la lunghezza del digest (metodo getDigestLength() ).
Ma il punto è che per fare quello che hai detto, l'algoritmo lo devi scegliere e imporre TU, non lo può fare una macchina. Non puoi determinare programmaticamente quale algoritmo è il "migliore". La conoscenza anche basilare sulle caratteristiche degli algoritmi la devi avere tu ... non la macchina!