Obtener la suma de comprobación MD5 de un archivo en Java

510

Estoy buscando utilizar Java para obtener la suma de comprobación MD5 de un archivo. Me sorprendió mucho, pero no he podido encontrar nada que muestre cómo obtener la suma de comprobación MD5 de un archivo.

Como se hace

Jack
fuente
Quizás esto ayude. También puede buscar la especificación, pero eso requeriría más trabajo ya que es complicado.
waynecolvin
44
Tenga en cuenta que según la investigación reciente "MD5 debe considerarse criptográficamente roto y no apto para su uso posterior". en.wikipedia.org/wiki/MD5
Zakharia Stanley
80
MD5 ya no se considera criptográficamente seguro, pero sigue siendo suficiente para validar la consistencia del archivo y es más rápido que SHA.
jiggy
2
@ZakhariaStanley Esta es una pregunta sobre la suma de comprobación.
iPherian
El uso canónico para las sumas de verificación MD5 en los archivos es evitar reemplazos hostiles de archivos distribuidos. Ahí es donde no es seguro. Pero en un escenario donde las hazañas hostiles no son una preocupación, es perfectamente adecuado.
Keith Tyler

Respuestas:

541

Hay un decorador de flujo de entrada java.security.DigestInputStream, para que pueda calcular el resumen mientras usa el flujo de entrada como lo haría normalmente, en lugar de tener que hacer un pase adicional sobre los datos.

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
fuente
44
Estoy de acuerdo, es una forma muy elegante de calcular la suma de verificación sobre la marcha si ya está haciendo algo con los bytes (es decir, leerlos desde una conexión HTTP).
Marc Novakowski,
2
@AlPhaba ¿Declaraste el iscomo un InputStreamo un FileInputStream? Suena como lo usaste FileInputStream, lo que causaría este error.
erickson
1
@barwnikk Funciona bien en Java 8. MethodNotFoundno es una excepción de Java estándar; tal vez estás hablando de un error del compilador? En cualquier caso, si no funciona para usted, es un problema de configuración local o un problema con otro código.
erickson
44
@barwnikk Nuevamente, ese es su problema de configuración local. Este es un código válido de Java 7 y Java 8. Si está atrapado con herramientas de 2006, tendrá que adaptarse.
erickson
55
@erickson No está actualizando el objeto MessageDigest con el contenido del archivo. Rt? Este código imprimirá siempre un mismo resumen.
sunil
302

Utilice DigestUtils de la biblioteca de códecs de Apache Commons :

try (InputStream is = Files.newInputStream(Paths.get("file.zip"))) {
    String md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(is);
}
Leif Gruenwoldt
fuente
1
No funciona para mí en mi código de Android Obtengo este error ... java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString en org.apache.commons.codec.digest.DigestUtils.md5Hex (DigestUtils.java:215)
JPM
@JPM ¿Presumiste que descargaste y pusiste el commons-codec.jaren tu classpath ya?
Leif Gruenwoldt
Sí, y exporté en mi proyecto de Android ... Puedo recorrer el código y la clase está allí en los archivos de origen ... raro, debe ser un problema de Android Eclipse.
JPM
1
Tuve el mismo problema, pero lo solucionó con este código `FileInputStream fis = new FileInputStream (nuevo archivo (filePath)); datos de bytes [] = org.apache.commons.codec.digest.DigestUtils.md5 (fis); char md5Chars [] = Hex.encodeHex (datos); String md5 = String.valueOf (md5Chars); `
Dmitry_L
1
¡Agradable! Para proyectos nuevos, siempre lo pienso dos veces antes de agregar una nueva dependencia, pero para proyectos existentes solo tengo que verificar si la biblioteca ya está allí para usarla. +1
OscarRyz
164

Hay un ejemplo en Real-Java-How-to usando el MessageDigest clase .

Consulte esa página para ver ejemplos de uso de CRC32 y SHA-1 también.

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 el lagarto
fuente
70
Sí ... todavía en línea después de 11 años! :-)
RealHowTo
El ejemplo de Real-Java-How-To funciona perfectamente y fue fácil de implementar.
bakoyaro
El ciclo de lectura es un poco torpe. read()no devolverá cero, y a do/whileno es realmente apropiado.
Marqués de Lorne
10
@EJP Gracias por sus comentarios oportunos.
Bill the Lizard
byte [] buffer = nuevo byte [1024]; ¿Podemos cambiar el tamaño de 1024 a algo más óptimo?
Jalpesh
90

La API com.google.common.hash ofrece:

  • Una API unificada y fácil de usar para todas las funciones hash
  • Implementaciones de 32 y 128 bits de murmur3 visibles
  • Los adaptadores md5 (), sha1 (), sha256 (), sha512 (), cambian solo una línea de código para cambiar entre estos y murmuran.
  • goodFastHash (int bits), para cuando no te importa qué algoritmo uses
  • Utilidades generales para instancias de HashCode, como combineOrdered / combineUnordered

Lea la Guía del usuario ( IO explicado , Hash explicado ).

Para su caso de uso Files.hash() calcula y devuelve el valor de resumen para un archivo.

Por ejemplo un Cálculo del resumen (cambie SHA-1 a MD5 para obtener el resumen MD5)

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

Tenga en cuenta que es mucho más rápido que , entonces usa si no necesita una suma de verificación criptográficamente segura. Tenga en cuenta también que no debe usarse para almacenar contraseñas y similares, ya que es fácil de aplicar fuerza bruta, para el uso de contraseñas , o en lugar.

Para la protección a largo plazo con hashes, un esquema de firma Merkle se suma a la seguridad y el Grupo de Estudio de Criptografía Post Quantum patrocinado por la Comisión Europea ha recomendado el uso de esta criptografía para la protección a largo plazo contra las computadoras cuánticas ( ref ).

Tenga en cuenta que tiene una tasa de colisión más alta que las demás.

oluies
fuente
¿Qué parte de Files.hash como se indicó anteriormente no cubre Files.hash?
Oluies
2
El Files.hash()está marcado como obsoleto, la forma recomendada es:Files.asByteSource(file).hash(Hashing.sha1())
erkfel
1
Y a partir de enero de 2018 Hashing.sha1()está marcado como obsoleto. La función Hashing.sha256()se recomienda en su lugar. fuente
MagicLegend
60

Usando nio2 (Java 7+) y sin bibliotecas externas:

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

Para comparar el resultado con una suma de verificación esperada:

String expected = "2252290BC44BEAD16AA1BF89948472E8";
String actual = DatatypeConverter.printHexBinary(hash);
System.out.println(expected.equalsIgnoreCase(actual) ? "MATCH" : "NO MATCH");
asilias
fuente
@Arash sí, absolutamente, gracias. Mezclé la clase de archivos JDK y la de Guava.
Assylias
Esta solución me gusta más que la de Erickson, ya que puede envolverse con opciones para usar programación de estilo funcional puro
Gabriel Hernández
2
Para un archivo grande, esto usará mucha memoria ya que se lee todo el archivo y luego se alimenta al resumen en lugar de leer fragmentos y "digerirlos" a medida que se leen.
bernie
39

Guava ahora proporciona una nueva API de hashing coherente que es mucho más fácil de usar que las diversas API de hashing proporcionadas en el JDK. Ver Hashing explicado . Para un archivo, puede obtener fácilmente la suma MD5, CRC32 (con la versión 14.0+) o muchos otros hashes:

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
fuente
32

Okay. Tuve que agregar. Implementación de una línea para aquellos que ya tienen dependencia de Spring y Apache Commons o planean agregarla:

DigestUtils.md5DigestAsHex(FileUtils.readFileToByteArray(file))

Opción para los comunes comunes de Apache (crédito @duleshi):

DigestUtils.md5Hex(FileUtils.readFileToByteArray(file))

Espero que esto ayude a alguien.

MickJ
fuente
1
EsDigestUtils.md5Hex(FileUtils.readFileToByteArray(file))
duleshi
La solución basada en los comunes de David Onter es mejor porque no lee un archivo completo en la memoria.
Fran Marzoa
Al menos Spring 5 , DigestUtils.md5Digest(InputStream inputStream)debe calcular el resumen MD5 y la DigestUtils.md5DigestAsHex(InputStream inputStream)representación de cadena hexadecimal de los métodos de resumen MD5 sin leer un archivo completo en la memoria.
Mike Shauneu
24

Un enfoque simple sin bibliotecas de terceros que utilizan Java 7

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

Si necesita imprimir esta matriz de bytes. Use como a continuación

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

Si necesita una cadena hexadecimal fuera de este resumen. Use como a continuación

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

donde DatatypeConverter es javax.xml.bind.DatatypeConverter

sunil
fuente
¿Por qué el toUpperCase?
EdgeCaseBerg
@edgecaseberg solo para que la cadena hexadecimal se vea bien mientras se imprime en la consola
sunil
Descubrí que necesitaba usar toLowerCase () en lugar de toUpperCase ().
Esplendor
14

Recientemente tuve que hacer esto solo para una cadena dinámica, MessageDigestpuede representar el hash de numerosas maneras. Para obtener la firma del archivo como lo haría con el comando md5sum , tuve que hacer algo como esto:

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();
}

Obviamente, esto no responde a su pregunta sobre cómo hacerlo específicamente para un archivo, la respuesta anterior se ocupa muy bien de eso. Acabo de pasar mucho tiempo haciendo que la suma se vea como la mayoría de las aplicaciones lo muestran, y pensé que podría tener el mismo problema.

Brian Gianforcaro
fuente
La firma es el resumen en formato hexadecimal. También encontré la representación hexadecimal para trabajar donde, como usted dice, otras representaciones no funcionan. Gracias por colgar esto.
amit
Esto es bueno, pero .toString(16)arrojará ceros a la izquierda. String.format("%032x", ...)quizás mejor.
Harold hace
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());
}

O puede obtener más información http://www.asjava.com/core-java/java-md5-example/

Mermelada
fuente
9
String checksum = DigestUtils.md5Hex(new FileInputStream(filePath));
Ravikiran kalal
fuente
9

Estábamos usando un código que se parece al código anterior en una publicación anterior usando

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

Sin embargo, BigInteger.toString()tenga cuidado al usar aquí, ya que truncará los ceros a la izquierda ... (por ejemplo, intente s = "27", la suma de verificación debería ser "02e74f10e0327ad868d138f2b4fdd6f0")

Secundo la sugerencia de usar Apache Commons Codec, reemplacé nuestro propio código con eso.

usuario552999
fuente
1
Wow, estaba investigando un problema en el que el material MD5 funcionaba perfectamente para todo, excepto que un archivo solo nos daba una salida de 31 dígitos hexadecimales y fallaba las sumas de md5. que truncar los ceros iniciales es un gran dolor ... Gracias por su nota.
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
fuente
8

Método Java muy rápido y limpio que no se basa en bibliotecas externas:

(Simplemente reemplace MD5 con SHA-1, SHA-256, SHA-384 o SHA-512 si lo desea)

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
fuente
6

Forma estándar de 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;
  }
}

El resultado es igual a la utilidad linux md5sum.

gotozero
fuente
6

Aquí hay una función simple que envuelve el código de Sunil para que tome un archivo como parámetro. La función no necesita ninguna biblioteca externa, pero sí requiere 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);            
    }


}

Salida de ejemplo:

hex=B117DD0C3CBBD009AC4EF65B6D75C97B
stackoverflowuser2010
fuente
3

Si está utilizando ANT para construir, esto es muy simple. Agregue lo siguiente a su build.xml:

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

Donde jarFile es el JAR en el que desea generar el MD5 y toDir es el directorio donde desea colocar el archivo MD5.

Más información aquí.

Matt Brock
fuente
3

Google guava ofrece una nueva API. Encuentra el siguiente:

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
fuente
3

Aquí hay una variación práctica que utiliza InputStream.transferTo()Java 9 y OutputStream.nullOutputStream()Java 11. No requiere bibliotecas externas y no necesita cargar todo el archivo en la memoria.

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()));
}

y

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

devoluciones

"e30fa2784ba15be37833d569280e2163c6f106506dfb9b07dde67a24bfb90da65c661110cf2c5c6f71185754ee5ae3fd83a5465c92f72abd888b03187229da29"
Cuenta
fuente
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
fuente