Uzyskiwanie sumy kontrolnej MD5 pliku w Javie

509

Chcę użyć Java, aby uzyskać sumę kontrolną MD5 pliku. Byłem naprawdę zaskoczony, ale nie byłem w stanie znaleźć niczego, co pokazuje, jak uzyskać sumę kontrolną MD5 pliku.

Jak to jest zrobione?

Jacek
źródło
Może to pomoże. Możesz także sprawdzić specyfikację, ale zajęłoby to więcej, ponieważ jest to skomplikowane.
waynecolvin,
4
Należy pamiętać, że zgodnie z najnowszymi badaniami „MD5 należy uznać za uszkodzony kryptograficznie i nieodpowiedni do dalszego użycia”. en.wikipedia.org/wiki/MD5
Zakharia Stanley
80
MD5 nie jest już uważany za kryptograficznie bezpieczny, ale nadal wystarcza do sprawdzania spójności plików i jest szybszy niż SHA.
jiggy
2
@ZakhariaStanley To pytanie dotyczy sumy kontrolnej.
iPherian
Kanonicznym zastosowaniem sum kontrolnych MD5 w plikach jest unikanie wrogich zamian plików rozproszonych. Tam jest niepewnie. Ale w scenariuszu, w którym wrogie exploity nie stanowią problemu, jest to całkowicie odpowiednie.
Keith Tyler,

Odpowiedzi:

541

Istnieje dekorator strumienia wejściowego java.security.DigestInputStream, dzięki czemu można obliczyć podsumowanie podczas korzystania ze strumienia wejściowego w normalny sposób, bez konieczności dodatkowego przekazywania danych.

MessageDigest md = MessageDigest.getInstance("MD5");
try (InputStream is = Files.newInputStream(Paths.get("file.txt"));
     DigestInputStream dis = new DigestInputStream(is, md)) 
{
  /* Read decorated stream (dis) to EOF as normal... */
}
byte[] digest = md.digest();
erickson
źródło
4
Zgadzam się, bardzo elegancki sposób obliczania sumy kontrolnej w locie, jeśli już robisz coś z bajtami (tj. Odczytujesz je z połączenia HTTP).
Marc Novakowski,
2
@AlPhaba Czy zadeklarowałeś isjako InputStreama FileInputStream? Wygląda na to, że został użyty FileInputStream, co spowodowałoby ten błąd.
erickson,
1
@barwnikk Działa dobrze w Javie 8. MethodNotFoundnie jest wyjątkiem od standardowej Java; może mówisz o błędzie kompilatora? W każdym razie, jeśli to nie działa, oznacza to lokalny problem z konfiguracją lub problem z innym kodem.
erickson
4
@barwnikk Znowu, to jest twój problem z lokalną konfiguracją. Jest to poprawny kod Java 7 i Java 8. Jeśli utkniesz z narzędziami z 2006 roku, będziesz musiał się dostosować.
erickson
5
@erickson Nie aktualizujesz obiektu MessageDigest o zawartość pliku. Rt? Ten kod wydrukuje zawsze ten sam skrót.
sunil
302

Użyj DigestUtils z biblioteki kodeków Apache Commons :

try (InputStream is = Files.newInputStream(Paths.get("file.zip"))) {
    String md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(is);
}
Leif Gruenwoldt
źródło
1
Nie działa dla mnie w moim kodzie Android Otrzymuję ten błąd ... java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString at org.apache.commons.codec.digest.DigestUtils.md5Hex (DigestUtils.java:215)
JPM
@JPM Zakładasz, że pobrałeś i umieściłeś już commons-codec.jarna ścieżce klasy?
Leif Gruenwoldt
tak tam i wyeksportowałem w moim projekcie Android .. Mogę przejść przez kod, a klasa jest w plikach źródłowych ... dziwne, to musi być problem z Androidem Eclipse.
JPM
1
Miałem ten sam problem, ale został rozwiązany przez ten kod `FileInputStream fis = new FileInputStream (new File (filePath)); bajty danych [] = org.apache.commons.codec.digest.DigestUtils.md5 (fis); char md5Chars [] = Hex.encodeHex (dane); String md5 = String.valueOf (md5Chars); `
Dmitry_L
1
Miły! W przypadku nowych projektów zawsze zastanawiam się dwa razy przed dodaniem nowej zależności, ale w przypadku istniejącego projektu muszę tylko sprawdzić, czy biblioteka już tam jest, aby z niej skorzystać. +1
OscarRyz
164

Jest przykład w Javie Real'a dotyczącej korzystania z klasy MessageDigest .

Sprawdź na tej stronie również przykłady użycia CRC32 i SHA-1.

import java.io.*;
import java.security.MessageDigest;

public class MD5Checksum {

   public static byte[] createChecksum(String filename) throws Exception {
       InputStream fis =  new FileInputStream(filename);

       byte[] buffer = new byte[1024];
       MessageDigest complete = MessageDigest.getInstance("MD5");
       int numRead;

       do {
           numRead = fis.read(buffer);
           if (numRead > 0) {
               complete.update(buffer, 0, numRead);
           }
       } while (numRead != -1);

       fis.close();
       return complete.digest();
   }

   // see this How-to for a faster way to convert
   // a byte array to a HEX string
   public static String getMD5Checksum(String filename) throws Exception {
       byte[] b = createChecksum(filename);
       String result = "";

       for (int i=0; i < b.length; i++) {
           result += Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
       }
       return result;
   }

   public static void main(String args[]) {
       try {
           System.out.println(getMD5Checksum("apache-tomcat-5.5.17.exe"));
           // output :
           //  0bb2827c5eacf570b6064e24e0e6653b
           // ref :
           //  http://www.apache.org/dist/
           //          tomcat/tomcat-5/v5.5.17/bin
           //              /apache-tomcat-5.5.17.exe.MD5
           //  0bb2827c5eacf570b6064e24e0e6653b *apache-tomcat-5.5.17.exe
       }
       catch (Exception e) {
           e.printStackTrace();
       }
   }
}
Bill jaszczurka
źródło
70
Tak ... po 11 latach nadal jest on-line! :-)
RealHowTo
Przykład Java's How-To firmy Real działa idealnie i był prosty do wdrożenia.
bakoyaro
Pętla odczytu jest trochę niezdarna. read()nie zwróci zera, a a do/whilenie jest naprawdę właściwe.
Markiz Lorne
10
@EJP Dziękujemy za informację zwrotną na czas.
Bill the Lizard
bajt [] bufor = nowy bajt [1024]; czy możemy zmienić rozmiar z 1024 na coś bardziej optymalnego?
Jalpesh
90

W com.google.common.hash oferty API:

  • Ujednolicony przyjazny dla użytkownika interfejs API dla wszystkich funkcji skrótu
  • Siewne 32- i 128-bitowe implementacje szmeru3
  • adaptery md5 (), sha1 (), sha256 (), sha512 (), zmieniaj tylko jeden wiersz kodu, aby przełączać się między nimi i szemrać.
  • goodFastHash (int bity), gdy nie obchodzi Cię, jakiego algorytmu używasz
  • Ogólne narzędzia dla instancji HashCode, takie jak CombineOrders / CombineUnordered

Przeczytaj Podręcznik użytkownika ( wyjaśnienie IO , wyjaśnienie skrótu ).

Dla twojego przypadku użycia Files.hash()oblicza i zwraca wartość skrótu dla pliku.

Na przykład obliczanie skrótu (zmień SHA-1 na MD5, aby uzyskać skrót MD5)

HashCode hc = Files.asByteSource(file).hash(Hashing.sha1());
"SHA-1: " + hc.toString();

Zauważ, że jest znacznie szybszy niż , więc użyj jeśli nie potrzebujesz kryptograficznie bezpiecznej sumy kontrolnej. Zauważ też, że nie powinny być używane do przechowywania haseł i tym podobnych, ponieważ łatwo jest użyć siły, do użycia haseł , lub zamiast.

W celu zapewnienia długoterminowej ochrony za pomocą skrótów program sygnatury Merkle zwiększa bezpieczeństwo, a sponsorowana przez Komisję Europejską grupa analityczna ds. Kryptografii post kwantowej zaleciła stosowanie tej kryptografii w celu długoterminowej ochrony przed komputerami kwantowymi ( zob .).

Zauważ, że ma wyższy współczynnik kolizji niż inne.

oliwki
źródło
Jaka część Files.hash, jak wspomniano powyżej, nie obejmuje Files.hash?
oluies
2
Files.hash()Jest oznaczony jako przestarzałe, zalecanym sposobem jest:Files.asByteSource(file).hash(Hashing.sha1())
erkfel
1
I od stycznia 2018 r. Hashing.sha1()Jest oznaczony jako przestarzały. Hashing.sha256()Zamiast tego zalecana jest funkcja . źródło
MagicLegend
59

Korzystanie z nio2 (Java 7+) i bez bibliotek zewnętrznych:

byte[] b = Files.readAllBytes(Paths.get("/path/to/file"));
byte[] hash = MessageDigest.getInstance("MD5").digest(b);

Aby porównać wynik z oczekiwaną sumą kontrolną:

String expected = "2252290BC44BEAD16AA1BF89948472E8";
String actual = DatatypeConverter.printHexBinary(hash);
System.out.println(expected.equalsIgnoreCase(actual) ? "MATCH" : "NO MATCH");
assylias
źródło
@Arash tak absolutnie - dzięki. Pomieszałem klasę JDK Files i klasę Guava.
assylias
I jakby tego rozwiązania więcej niż Ericksona, ponieważ może być owinięte opcjonalne użycie programowania czystym stylu funkcjonalna
Gabriel Hernandez
2
W przypadku dużego pliku zajmie to dużo pamięci, ponieważ cały plik jest odczytywany, a następnie podawany do podsumowania zamiast odczytywania fragmentów i „trawienia” ich podczas odczytywania.
bernie,
39

Guava udostępnia teraz nowy, spójny interfejs API mieszania, który jest o wiele bardziej przyjazny dla użytkownika niż różne interfejsy API mieszania dostępne w JDK. Zobacz Wyjaśnienie skrótu . W przypadku pliku można łatwo uzyskać sumę MD5, CRC32 (z wersją 14.0+) lub wiele innych skrótów:

HashCode md5 = Files.hash(file, Hashing.md5());
byte[] md5Bytes = md5.asBytes();
String md5Hex = md5.toString();

HashCode crc32 = Files.hash(file, Hashing.crc32());
int crc32Int = crc32.asInt();

// the Checksum API returns a long, but it's padded with 0s for 32-bit CRC
// this is the value you would get if using that API directly
long checksumResult = crc32.padToLong();
ColinD
źródło
32

Ok. Musiałem dodać. Jednowierszowa implementacja dla tych, którzy mają już zależność Spring i Apache Commons lub planują ją dodać:

DigestUtils.md5DigestAsHex(FileUtils.readFileToByteArray(file))

Dla i tylko wspólne opcje Apache (kredyt @duleshi):

DigestUtils.md5Hex(FileUtils.readFileToByteArray(file))

Mam nadzieję, że to komuś pomoże.

MickJ
źródło
1
ToDigestUtils.md5Hex(FileUtils.readFileToByteArray(file))
duleshi,
Rozwiązanie oparte na wspólnym systemie David Onter jest lepsze, ponieważ nie odczytuje całego pliku do pamięci.
Fran Marzoa,
Przynajmniej Spring 5 musisz DigestUtils.md5Digest(InputStream inputStream)obliczyć skrót MD5 i DigestUtils.md5DigestAsHex(InputStream inputStream)szesnastkową reprezentację ciągu metod skrótu MD5 bez wczytywania całego pliku do pamięci.
Mike Shauneu,
24

Proste podejście bez bibliotek stron trzecich korzystających z Java 7

String path = "your complete file path";
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(Files.readAllBytes(Paths.get(path)));
byte[] digest = md.digest();

Jeśli musisz wydrukować tę tablicę bajtów. Użyj jak poniżej

System.out.println(Arrays.toString(digest));

Jeśli potrzebujesz ciągu szesnastkowego z tego skrótu. Użyj jak poniżej

String digestInHex = DatatypeConverter.printHexBinary(digest).toUpperCase();
System.out.println(digestInHex);

gdzie DatatypeConverter to javax.xml.bind.DatatypeConverter

sunil
źródło
Dlaczego toUpperCase?
EdgeCaseBerg,
@edgecaseberg tylko dla szesnastkowego łańcucha wygląda dobrze podczas drukowania go na konsolę
sunil
Odkryłem, że muszę użyć toLowerCase () zamiast toUpperCase ().
Splendor
13

Niedawno musiałem to zrobić tylko dla dynamicznego ciągu, MessageDigestmoże reprezentować skrót na wiele sposobów. Aby uzyskać podpis pliku, tak jak w przypadku polecenia md5sum , musiałem zrobić coś takiego:

try {
   String s = "TEST STRING";
   MessageDigest md5 = MessageDigest.getInstance("MD5");
   md5.update(s.getBytes(),0,s.length());
   String signature = new BigInteger(1,md5.digest()).toString(16);
   System.out.println("Signature: "+signature);

} catch (final NoSuchAlgorithmException e) {
   e.printStackTrace();
}

To oczywiście nie odpowiada na twoje pytanie, jak to zrobić specjalnie dla pliku, powyższa odpowiedź ładnie radzi sobie z tym spokojem. Spędziłem dużo czasu, aby suma wyglądała tak, jakby wyświetlała ją większość aplikacji, i pomyślałem, że możesz mieć takie same problemy.

Brian Gianforcearo
źródło
Podpis jest skrótem w formacie szesnastkowym. Ja też znalazłem reprezentację szesnastkową do pracy tam, gdzie, jak mówisz, inne reprezentacje nie działają. Dziękuję za postawienie tego.
amit
11
public static void main(String[] args) throws Exception {
    MessageDigest md = MessageDigest.getInstance("MD5");
    FileInputStream fis = new FileInputStream("c:\\apache\\cxf.jar");

    byte[] dataBytes = new byte[1024];

    int nread = 0;
    while ((nread = fis.read(dataBytes)) != -1) {
        md.update(dataBytes, 0, nread);
    };
    byte[] mdbytes = md.digest();
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < mdbytes.length; i++) {
        sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
    }
    System.out.println("Digest(in hex format):: " + sb.toString());
}

Lub możesz uzyskać więcej informacji http://www.asjava.com/core-java/java-md5-example/

Dżem
źródło
9
String checksum = DigestUtils.md5Hex(new FileInputStream(filePath));
Ravikiran kalal
źródło
9

Używaliśmy kodu, który przypomina kod powyżej w poprzednim poście przy użyciu

...
String signature = new BigInteger(1,md5.digest()).toString(16);
...

Uważaj jednak na użycie BigInteger.toString()tutaj, ponieważ spowoduje to obcięcie wiodących zer ... (na przykład spróbuj s = "27", suma kontrolna powinna wynosić"02e74f10e0327ad868d138f2b4fdd6f0" )

Popieram sugestię użycia kodeka Apache Commons, zastąpiłem tym kodem.

użytkownik552999
źródło
1
Wow, szukałem problemu, w którym rzeczy z MD5 działały idealnie we wszystkim, z wyjątkiem tego, że plik dawał nam tylko 31 cyfr szesnastkowych i nie sprawdzał sum kontrolnych md5. obcinanie wiodących zer to ogromny ból ... Dziękuję za twoją notatkę.
Mike
8
public static String MD5Hash(String toHash) throws RuntimeException {
   try{
       return String.format("%032x", // produces lower case 32 char wide hexa left-padded with 0
      new BigInteger(1, // handles large POSITIVE numbers 
           MessageDigest.getInstance("MD5").digest(toHash.getBytes())));
   }
   catch (NoSuchAlgorithmException e) {
      // do whatever seems relevant
   }
}
FX
źródło
8

Bardzo szybka i czysta metoda Java, która nie polega na bibliotekach zewnętrznych:

(Po prostu zamień MD5 na SHA-1, SHA-256, SHA-384 lub SHA-512, jeśli chcesz)

public String calcMD5() throws Exception{
        byte[] buffer = new byte[8192];
        MessageDigest md = MessageDigest.getInstance("MD5");

        DigestInputStream dis = new DigestInputStream(new FileInputStream(new File("Path to file")), md);
        try {
            while (dis.read(buffer) != -1);
        }finally{
            dis.close();
        }

        byte[] bytes = md.digest();

        // bytesToHex-method
        char[] hexChars = new char[bytes.length * 2];
        for ( int j = 0; j < bytes.length; j++ ) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }

        return new String(hexChars);
}
David
źródło
6

Standardowy sposób Java Runtime Environment :

public String checksum(File file) {
  try {
    InputStream fin = new FileInputStream(file);
    java.security.MessageDigest md5er =
        MessageDigest.getInstance("MD5");
    byte[] buffer = new byte[1024];
    int read;
    do {
      read = fin.read(buffer);
      if (read > 0)
        md5er.update(buffer, 0, read);
    } while (read != -1);
    fin.close();
    byte[] digest = md5er.digest();
    if (digest == null)
      return null;
    String strDigest = "0x";
    for (int i = 0; i < digest.length; i++) {
      strDigest += Integer.toString((digest[i] & 0xff) 
                + 0x100, 16).substring(1).toUpperCase();
    }
    return strDigest;
  } catch (Exception e) {
    return null;
  }
}

Wynik jest równy narzędziu Linux md5sum.

gotozero
źródło
6

Oto prosta funkcja, która owija się wokół kodu Sunila, dzięki czemu przyjmuje parametr File jako parametr. Ta funkcja nie wymaga żadnych zewnętrznych bibliotek, ale wymaga Java 7.

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.xml.bind.DatatypeConverter;

public class Checksum {

    /**
     * Generates an MD5 checksum as a String.
     * @param file The file that is being checksummed.
     * @return Hex string of the checksum value.
     * @throws NoSuchAlgorithmException
     * @throws IOException
     */
    public static String generate(File file) throws NoSuchAlgorithmException,IOException {

        MessageDigest messageDigest = MessageDigest.getInstance("MD5");
        messageDigest.update(Files.readAllBytes(file.toPath()));
        byte[] hash = messageDigest.digest();

        return DatatypeConverter.printHexBinary(hash).toUpperCase();
    }

    public static void main(String argv[]) throws NoSuchAlgorithmException, IOException {
        File file = new File("/Users/foo.bar/Documents/file.jar");          
        String hex = Checksum.generate(file);
        System.out.printf("hex=%s\n", hex);            
    }


}

Przykładowe dane wyjściowe:

hex=B117DD0C3CBBD009AC4EF65B6D75C97B
stackoverflowuser2010
źródło
3

Jeśli używasz ANT do budowania, jest to bardzo proste. Dodaj następujące elementy do pliku build.xml:

<checksum file="${jarFile}" todir="${toDir}"/>

Gdzie jarFile to plik JAR, dla którego chcesz wygenerować MD5, a toDir to katalog, w którym chcesz umieścić plik MD5.

Więcej informacji tutaj.

Matt Brock
źródło
3

Google guava zapewnia nowy interfejs API. Znajdź ten poniżej:

public static HashCode hash(File file,
            HashFunction hashFunction)
                     throws IOException

Computes the hash code of the file using hashFunction.

Parameters:
    file - the file to read
    hashFunction - the hash function to use to hash the data
Returns:
    the HashCode of all of the bytes in the file
Throws:
    IOException - if an I/O error occurs
Since:
    12.0
Balaji Boggaram Ramanarayan
źródło
3

Oto przydatna odmiana, która korzysta InputStream.transferTo()z Java 9 i OutputStream.nullOutputStream()Java 11. Nie wymaga zewnętrznych bibliotek i nie musi ładować całego pliku do pamięci.

public static String hashFile(String algorithm, File f) throws IOException, NoSuchAlgorithmException {
    MessageDigest md = MessageDigest.getInstance(algorithm);

    try(BufferedInputStream in = new BufferedInputStream((new FileInputStream(f)));
        DigestOutputStream out = new DigestOutputStream(OutputStream.nullOutputStream(), md)) {
        in.transferTo(out);
    }

    String fx = "%0" + (md.getDigestLength()*2) + "x";
    return String.format(fx, new BigInteger(1, md.digest()));
}

i

hashFile("SHA-512", Path.of("src", "test", "resources", "some.txt").toFile());

zwroty

"e30fa2784ba15be37833d569280e2163c6f106506dfb9b07dde67a24bfb90da65c661110cf2c5c6f71185754ee5ae3fd83a5465c92f72abd888b03187229da29"
Rachunek
źródło
2
public static String getMd5OfFile(String filePath)
{
    String returnVal = "";
    try 
    {
        InputStream   input   = new FileInputStream(filePath); 
        byte[]        buffer  = new byte[1024];
        MessageDigest md5Hash = MessageDigest.getInstance("MD5");
        int           numRead = 0;
        while (numRead != -1)
        {
            numRead = input.read(buffer);
            if (numRead > 0)
            {
                md5Hash.update(buffer, 0, numRead);
            }
        }
        input.close();

        byte [] md5Bytes = md5Hash.digest();
        for (int i=0; i < md5Bytes.length; i++)
        {
            returnVal += Integer.toString( ( md5Bytes[i] & 0xff ) + 0x100, 16).substring( 1 );
        }
    } 
    catch(Throwable t) {t.printStackTrace();}
    return returnVal.toUpperCase();
}
XXX
źródło