MD5 to cały czas bardzo popularny algorytm do generowania kryptograficznych funkcji skrótu. Java poprzez JDK oraz liczne biblioteki, zawiera dla niego bardzo dobre wsparcie od strony kodu. Zobaczmy więc, jak wygląda MD5 w Java.
Co to jest MD5?
MD5 (ang. Message-Digest algorithm 5) to algorytm kryptograficzny. Jego zadaniem jest wygenerowanie krótkiego skrótu dla dowolnych ciągu danych.
Skrót ten powinien wyglądać na losowy i powinien mieć stałą długość. Dla określonego ciągu danych musi być zawsze taki sam. Ale dla danych minimalnie różnych, np. różniących się o jeden bit, już powinien być zupełnie inny.
Bardzo często na różnych stronach z oprogramowaniem możemy zobaczyć link do danej aplikacji, obok którego zamieszczona jest również informacja o wartościach wybranych skrótach, m. in. MD5.
Po pobraniu pliku możemy samodzielnie wyliczyć daną funkcję skrótu. A następnie porównać ją z tą dostępną na stronie. Dzięki temu możemy zweryfikować, czy plik nie został zmieniony przez kogoś z zewnątrz na serwerze, skąd go pobieraliśmy.
A zatem funkcja skrótu pomaga nam w stwierdzeniu, czy ściągnięty plik jest rzeczywiście tym, który chcieliśmy pobrać.
Warto zwrócić tutaj uwagę, że obecnie MD5 nie jest już uznawane za bezpieczny algorytm do generowania wartości funkcji skrótu. Praktyka oraz wyliczenia matematyczne wskazały, że można dość łatwo (przy obecnych możliwościach sprzętowych) znaleźć dwa różne zbiory danych, których wartość skrótu MD5 będzie taka sama. Co rzutuje w dużym stopniu na bezpieczeństwo.
Nie zmienia to jednak faktu, że MD5 jest cały czas bardzo popularny i stosowany w wielu miejscach.
W dalszej części artykułu skupimy się na sposobach wyliczenia w Java wartości MD5 dla fragmentu danych.
A danymi tymi będzie fragment tekstu:
Litwo, Ojczyzno moja! ty jesteś jak zdrowie;
Pan Tadeusz, Adam Mickiewicz
Ile cię trzeba cenić, ten tylko się dowie,
Kto cię stracił. Dziś piękność twą w całej ozdobie
Widzę i opisuję, bo tęsknię po tobie.
MD5 z punktu widzenia Java
Dla nas, programistów, MD5 to po prostu metoda w Java. Jej kontrakt możemy opisać następująco:
- Argumentem wejściowym jest dowolnych ciąg danych, np. typu String.
- Wyjściem jest natomiast 128 bitowy skrót.
Dla podanego fragmentu tekstu wyliczamy wartości skrótu (np. poprzez stronę https://emn178.github.io/online-tools/). MD5 wygląda w tym przypadku następująco:
- MD5: 4f3298f21261f6440597a7f68c747954.
Wszystkie dane zbieramy i umieszczamy w kodzie programu. W tym przypadku dodany został interfejsie DATA. Posłuży nam jako miejsce do przechowywania stałych, które już znamy.
public interface DATA {
String CONTENT = "Litwo, Ojczyzno moja! ty jesteś jak zdrowie;\n" +
"Ile cię trzeba cenić, ten tylko się dowie,\n" +
"Kto cię stracił. Dziś piękność twą w całej ozdobie\n" +
"Widzę i opisuję, bo tęsknię po tobie.";
String MD5 = "4f3298f21261f6440597a7f68c747954";
}
Guava
Wyliczenie funkcji skrótu MD5 w Java zaczniemy od biblioteki Guava, która gościła już wcześniej na blogu. W tym przypadku korzystamy z klasy Hashing:
public class GuavaTest {
@Test
public void md5() {
String hash = Hashing
.md5()
.hashString(DATA.CONTENT, StandardCharsets.UTF_8)
.toString();
assertEquals(DATA.MD5, hash);
}
}
Autorzy Guavy metodę Hashing.md5() oznaczyli już jako @Deprecated, co dokładnie widać w IDE. Nie zmienia to jednak faktu, że możemy z niej skorzystać. Dlatego w projektach korzystających z Guavy warto to rozważyć, gdy pojawi się temat MD5.
O innych możliwościach biblioteki Guava możesz przeczytać w cyklu artykułów Biblioteki Java, które warto znać.
Apache Commons Codec
Alternatywnym wobec Guavy rozwiązaniem jest pakiet od Apache. Apache Commons to zbiór bibliotek, które programistom Java nie trzeba przedstawiać. Prędzej, czy później, trafi na nie każdy kodujący w tym języku.
Celem Apache Commons jest zebranie w jednym miejscu komponentów wielokrotnego użytku w Javie. Zwykle nie chcemy pisać elementów, które już są dostępne i przetestowane przez tysiące innych programistów.
W kontekście kryptograficznych funkcji skrótu w tym zbiorze znajdziemy również odpowiednie miejsce. A jest nim biblioteka commons-codec. Dla nas najciekawsza będzie klasa DigestUtils:
public class CommonsCodecTest {
@Test
public void md5() {
String hash = DigestUtils.md5Hex(DATA.CONTENT);
assertEquals(DATA.MD5, hash);
}
}
Przykłada pokazuje, że wyliczenie MD5 może być jeszcze prostsze. Commons Codec zapewnia to już w jednej linijce.
Jeśli w Twoim projekcie jest dostępna biblioteka Commons Codec, to będzie to najprostsza ścieżka do skorzystania z MD5.
MD5 bez dodatkowych bibliotek Java
MD5 możemy wyliczać również przy wykorzystaniu biblioteki standardowej Java.
Musimy jednak wziąć tutaj małą poprawkę na API dostępne w JDK. Delikatnie mówiąc, nie jest ono najprzyjemniejsze. Przykład poniżej:
public class JDKTest {
@Test
public void md5() throws NoSuchAlgorithmException {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] digest = md5.digest(DATA.CONTENT.getBytes(StandardCharsets.UTF_8));
String hash = encodeMD5(digest);
assertEquals(DATA.MD5, hash);
}
private String encodeMD5(byte[] bytes) {
return String.format("%032x", new BigInteger(1, bytes));
}
}
Powyżej widzimy dużo więcej linijek kodu niż w przypadku Guavy, czy Commons Codec. Dodatkowo samo API nie jest wygodne.
O ile jeszcze fragmenty z linii 4-5 by uszły, to konieczność pamiętania o właściwym przekształceniu do klasy String poprzez odpowiednie formatowanie nie jest przyjemna (metoda encodeMD5 powyżej).
Kolejnym minusem tego rozwiązania jest brak odporności na wielowątkowość. Gdy korzystać będziemy z wielu wątków, to musimy pamiętać o każdorazowym tworzeniu nowej instancji MessageDigest.
Podsumowanie
MD5, pomimo swoich minusów, cały czas ma się nieźle i jest wykorzystywane w wielu miejscach. A ekosystem Java zapewnia nam bogaty zestaw API, które pozwalają nam pracować z tym algorytmem.
W projektach warto korzystać z gotowych rozwiązań w postaci Guavy i Commons Codec.
Nie polecam stosowania rozwiązania z klasami dostępnymi w standardowym JDK. Ich API nie jest zbyt przyjemne dla programisty i wymagają dodatkowego kodu do obsługi zwracanej wartości funkcji skrótu.
A czy Ty korzystasz czasem z MD5?