Я хочу использовать Java для получения контрольной суммы файла MD5. Я был очень удивлен, но мне не удалось найти ничего, что показало бы, как получить контрольную сумму MD5 файла.
Как это делается?
Имейте в виду, что согласно недавнему исследованию «MD5 следует считать криптографически взломанным и непригодным для дальнейшего использования». en.wikipedia.org/wiki/MD5
MD5 больше не считается криптографически безопасным, но его по-прежнему достаточно для проверки целостности файлов и он быстрее, чем SHA.
@ZakhariaStanley Это вопрос о контрольной сумме.
Каноническое использование контрольных сумм MD5 для файлов - избежать враждебной замены распределенных файлов. Вот где это небезопасно. Но в сценарии, где враждебные эксплойты не вызывают беспокойства, это идеально подходит.




В Практические инструкции Real по Java есть пример с использованием класса Дайджест сообщения.
На этой странице также есть примеры с использованием CRC32 и 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();
}
}
}
Ага ... все еще в сети спустя 11 лет! :-)
Пример из Real Java-How-To работает отлично, и его легко реализовать.
Цикл чтения немного корявый. read() не вернет ноль, а do/while не совсем подходит.
@EJP Спасибо за своевременный отзыв.
байт [] буфер = новый байт [1024]; можем ли мы изменить размер с 1024 на что-нибудь более оптимальное?
Он работает нормально, но я бы рекомендовал подумать о правильном закрытии InputStream при его внедрении в свой проект.
Почему вы используете имя файла, а не каплю? Как вы можете гарантировать, что изображение такое же, если вы не рассчитываете контрольную сумму на основе большого двоичного объекта?
@DimitriKopriwa Это не использует имя файла для вычисления хэша, оно использует содержимое буфера при чтении файла.
Есть декоратор входного потока, java.security.DigestInputStream, так что вы можете вычислять дайджест, используя входной поток, как обычно, вместо того, чтобы делать дополнительный проход по данным.
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();
Я согласен, очень элегантный способ вычисления контрольной суммы на лету, если вы уже что-то делаете с байтами (т.е. считываете их из HTTP-соединения).
+1 за потоковую реализацию. @Marc не обязательно должен быть просто HTTP-потоком, потоки java io могут иметь все, что только можно придумать; то есть этот метод будет удобен в самых разных сценариях.
Пробовал, но возникает эта ошибка Несоответствие типов: невозможно преобразовать из DigestInputStream в FileInputStream, почему это не работает на моей стороне? Я работаю на java 1.6
@AlPhaba Вы объявляли is как InputStream или FileInputStream? Похоже, вы использовали FileInputStream, что могло вызвать эту ошибку.
Можно ли использовать этот подход для нескольких типов дайджестов? Например, если мне нужны и MD5, и SHA1?
@carlspring В сочетании с входным потоком «тройник» вы могли бы. Написать его самостоятельно или использовать Apache Commons не так уж и сложно. Вот такой вопрос об этом.
@erickson, я вообще-то тут задал вопрос: stackoverflow.com/questions/19300774/…. Если у вас есть время, может, вы могли бы проиллюстрировать это для меня?
@barwnikk Он отлично работает в Java 8. MethodNotFound не является исключением из стандартной Java; возможно вы говорите об ошибке компилятора? В любом случае, если это не сработает для вас, это проблема локальной конфигурации или проблема с другим кодом.
@barwnikk Опять же, это проблема вашей локальной конфигурации. Это действительный код Java 7 и Java 8. Если вы застряли с инструментами из 2006 года, вам придется адаптироваться.
@erickson Вы не обновляете объект MessageDigest содержимым файла. Rt? Этот код всегда будет печатать один и тот же дайджест.
@sunil Нет. MessageDigest обновляет объект DigestInputStream со всеми данными в файле. Он вернет тот же дайджест, только если содержимое файла идентично (или есть конфликт хешей, что практически невозможно, если кто-то намеренно не воспользуется слабостью MD5).
@erickson Ты был прав, Эриксон. Я забыл прочитать содержимое, чтобы обновить объект MessageDigest. Я обновил ваш ответ этим изменением.
@erickson Мне было интересно, почему эта функция продолжает возвращать один и тот же хеш, но похоже, что ваше последнее изменение (3 ноября) снова нарушило код. Часть «while (dis.read ()! = -1); dis.close ();» должен быть выполнен, чтобы вернуть правильный md5.
@SymenTimmermans Спасибо, что заметили это. Моя редакция должна это исправить.
IMO, в этом случае нет необходимости закрывать is, так как закрытие dis автоматически закрывает dis. Итак, вы можете переместить InputStream is = Files.newInputStream(Paths.get("file.txt")) из try (...) (т.е. вот так: MessageDigest md = MessageDigest.getInstance("MD5"); InputStream is = Files.newInputStream(Paths.get("file.txt")); try (DigestInputStream dis = new DigestInputStream(is, md)) {...}). Хотя я не уверен насчет стиля кодирования (возможно, некоторые рекомендации по стилю требуют, чтобы и is, и dis находились внутри try (...), но это не требует логики программы как таковой).
Или вот так: MessageDigest md = MessageDigest.getInstance("MD5"); try (DigestInputStream is = new DigestInputStream(Files.newInputStream(Paths.get("file.txt")), md)) {...} byte[] digest = md.digest();.
@Sasha Что делать, если при создании потока дайджеста возникает исключение? Тогда поток ввода файла не будет своевременно закрыт. С другой стороны, какой вред может быть в явном принудительном закрытии входного потока базового файла при выходе из блока try? Я не вижу никакой ценности в этой микрооптимизации.
@erickson, "что, если создание потока дайджеста вызовет исключение" - AFAIK, не может. «Я не вижу никакой ценности в этой микрооптимизации» - вы правы, в этом нет никакой ценности, это просто вопрос стиля кода. (Я просто хотел указать, что закрывать базовый поток не обязательно, поскольку он автоматически закрывается потоком-оболочкой - это может быть неочевидно для новичков в Java.)
Но какая переменная в итоге содержит хеш MD-5?
@gstackoverflow Переменная digest в конце.
Он всегда возвращает один и тот же результат.
@AnmolSinghJaggi Фактически вы должны прочитать весь поток внутри блока try. Если ваш код не работает, вы можете открыть свой вопрос с помощью минимальный воспроизводимый пример, чтобы узнать, где находится ваша ошибка.
Ааа теперь вижу .. Пропустил, так как очень торопился. Вероятно, вам также следует заполнить этот код.
@AnmolSinghJaggi Что ж, идея шаблона Decorator заключается в том, что любой код, который работает с InputStream, будет работать с этим расширением и вычислять дайджест как побочный эффект любой обработки, которую вы обычно выполняете (например, загрузка файла, его анализ, так далее.). Я не знаю, какую обработку будут использовать люди, поэтому я не могу выполнять их (всю) работу за них.
Использует ли этот метод много памяти при вычислении MD5 для большого файла? Это рекомендуется, если у моего приложения уже серьезные проблемы с памятью?
@WayneWei Нет. MD5-хеш - это алгоритм с постоянным пространством. Он хранит только 16 байтов состояния для вычислений. Реализация может использовать внутри 64-байтовый буфер блока.
Недавно мне пришлось сделать это только для динамической строки, MessageDigest может представлять хеш разными способами. Чтобы получить подпись файла, как при использовании команды md5sum, мне пришлось сделать что-то вроде этого:
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();
}
Это, очевидно, не отвечает на ваш вопрос о том, как это сделать специально для файла, приведенный выше ответ прекрасно справляется с этим. Я просто потратил много времени на то, чтобы сумма выглядела так, как ее отображает большинство приложений, и подумал, что вы можете столкнуться с той же проблемой.
Подпись представляет собой дайджест в шестнадцатеричном формате. Я тоже обнаружил, что шестнадцатеричное представление работает там, где, как вы говорите, другие представления не работают. Спасибо, что разместили это.
Это хорошо, но .toString(16) отбрасывает ведущие нули. String.format("%032x", ...) может быть лучше.
Используйте DigestUtils из библиотеки Кодек Apache Commons:
try (InputStream is = Files.newInputStream(Paths.get("file.zip"))) {
String md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(is);
}
У меня не работает в моем коде Android, я получаю эту ошибку ... java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString в org.apache.commons.codec.digest.DigestUtils.md5Hex (DigestUti ls.java:215)
@JPM Предположим, вы уже загрузили и поместили commons-codec.jar в свой путь к классам?
да, там, и я экспортировал в свой проект Android .. Я могу пройти через код, и класс есть в исходных файлах ... странно, должно быть какая-то проблема с Android Eclipse.
У меня была такая же проблема, но она исправлена этим кодом `FileInputStream fis = new FileInputStream (new File (filePath)); байтовые данные [] = org.apache.commons.codec.digest.DigestUtils.md5 (fis); char md5Chars [] = Hex.encodeHex (данные); Строка md5 = String.valueOf (md5Chars); `
Хороший! Для новых проектов я всегда дважды думаю, прежде чем добавлять новую зависимость, но для существующего проекта мне просто нужно проверить, существует ли уже библиотека, чтобы использовать ее. +1
Очень хорошо! Также работает в MATLAB ... Просто прочтите файл 'fis' и используйте 'md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex (fis);'
Работает хорошо и должен быть принятым ответом, поскольку принятый ответ не является полным.
Если вы используете ANT для сборки, это очень просто. Добавьте в свой build.xml следующее:
<checksum file = "${jarFile}" todir = "${toDir}"/>
Где jarFile - это JAR, для которого вы хотите сгенерировать MD5, а toDir - это каталог, в который вы хотите поместить файл MD5.
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
}
}
Мы использовали код, похожий на код, приведенный выше в предыдущем сообщении, используя
...
String signature = new BigInteger(1,md5.digest()).toString(16);
...
Однако будьте осторожны при использовании здесь BigInteger.toString(), так как он обрезает ведущие нули ...
(например, попробуйте s = "27", контрольная сумма должна быть "02e74f10e0327ad868d138f2b4fdd6f0")
Я поддерживаю предложение использовать кодек Apache Commons, я заменил на него наш собственный код.
Вау, я изучал проблему, при которой материал MD5 работал отлично для всего, за исключением того, что файл давал нам только 31 шестнадцатеричный вывод и не давал md5checksums. усечение ведущих нулей - огромная боль ... Спасибо за вашу заметку.
API com.google.common.hash предлагает:
Прочтите руководство пользователя (Объяснение ввода-вывода, Объяснение хеширования).
Для вашего варианта использования Files.hash() вычисляет и возвращает значение дайджеста для файла.
Например, расчет дайджеста ша-1 (измените SHA-1 на MD5, чтобы получить дайджест MD5)
HashCode hc = Files.asByteSource(file).hash(Hashing.sha1());
"SHA-1: " + hc.toString();
Обратите внимание, что crc32 намного быстрее, чем мкр5, поэтому используйте crc32, если вам не нужна криптографически безопасная контрольная сумма. Также обратите внимание, что мкр5 не следует использовать для хранения паролей и тому подобного, поскольку его легко перебрать, для паролей вместо этого используйте bcrypt, зашифровать или ша-256.
Для долгосрочной защиты с помощью хэшей Схема подписи Меркла добавляет к безопасности, и Исследовательская группа Post Quantum Cryptography, спонсируемая Европейской Комиссией, рекомендовала использовать эту криптографию для долгосрочной защиты от квантовых компьютеров (ссылка).
Обратите внимание, что crc32 имеет более высокую частоту конфликтов, чем другие.
Какая часть Files.hash, как указано выше, не охватывает Files.hash?
Files.hash() отмечен как устаревший, рекомендуемый способ: Files.asByteSource(file).hash(Hashing.sha1())
А по состоянию на январь 2018 года Hashing.sha1() помечен как устаревший. Вместо этого рекомендуется использовать функцию Hashing.sha256(). источник
Другая реализация: Быстрая реализация MD5 на Java
String hash = MD5.asHex(MD5.getHash(new File(filename)));
Я не могу найти метод MD5.asHex() в JDK 1.8.0 242.
Гуава теперь предоставляет новый согласованный API хеширования, который намного удобнее для пользователя, чем различные API хеширования, представленные в JDK. См. Объяснение хеширования. Для файла вы можете легко получить сумму MD5, CRC32 (с версией 14.0+) или многие другие хэши:
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();
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());
}
Или вы можете получить дополнительную информацию http://www.asjava.com/core-java/java-md5-example/
ЭТОТ ОТВЕТ хорош, я потратил 12 часов, прежде чем использовать этот ответ, чтобы достичь того, чего я хочу огромное спасибо :)
В порядке. Пришлось добавить. Однострочная реализация для тех, кто уже имеет зависимость Spring и Apache Commons или планирует ее добавить:
DigestUtils.md5DigestAsHex(FileUtils.readFileToByteArray(file))
Вариант только для общего пользования и Apache (кредит @duleshi):
DigestUtils.md5Hex(FileUtils.readFileToByteArray(file))
Надеюсь, это кому-то поможет.
Это DigestUtils.md5Hex(FileUtils.readFileToByteArray(file))
Решение, основанное на сообществе Дэвида Онтера, лучше, потому что оно не считывает весь файл в память.
По крайней мере, для Spring 5 у вас есть DigestUtils.md5Digest(InputStream inputStream) для вычисления дайджеста MD5 и DigestUtils.md5DigestAsHex(InputStream inputStream) для шестнадцатеричного строкового представления методов дайджеста MD5 без чтения всего файла в память.
Стандартный способ среды выполнения Java:
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;
}
}
Результат аналогичен утилите linux md5sum.
Google guava предоставляет новый API. Найдите тот, что ниже:
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
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();
}
Использование nio2 (Java 7+) и без внешних библиотек:
byte[] b = Files.readAllBytes(Paths.get("/path/to/file"));
byte[] hash = MessageDigest.getInstance("MD5").digest(b);
Чтобы сравнить результат с ожидаемой контрольной суммой:
String expected = "2252290BC44BEAD16AA1BF89948472E8";
String actual = DatatypeConverter.printHexBinary(hash);
System.out.println(expected.equalsIgnoreCase(actual) ? "MATCH" : "NO MATCH");
@Arash да абсолютно - спасибо. Я перепутал класс JDK Files и класс Guava.
Мне это решение нравится больше, чем решение Эриксона, так как оно может быть обернуто опциями для использования программирования в чисто функциональном стиле.
Для большого файла это будет использовать много памяти, так как весь файл читается и затем передается в дайджест вместо чтения фрагментов и «переваривания» их по мере чтения.
Простой подход без сторонних библиотек с использованием Java 7
String path = "your complete file path";
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(Files.readAllBytes(Paths.get(path)));
byte[] digest = md.digest();
Если вам нужно распечатать этот массив байтов. Используйте как показано ниже
System.out.println(Arrays.toString(digest));
Если вам нужна шестнадцатеричная строка из этого дайджеста. Используйте как показано ниже
String digestInHex = DatatypeConverter.printHexBinary(digest).toUpperCase();
System.out.println(digestInHex);
где DatatypeConverter - это javax.xml.bind.DatatypeConverter
Почему именно toUpperCase?
@edgecaseberg только для шестнадцатеричной строки выглядит хорошо при печати на консоли
Я обнаружил, что мне нужно использовать toLowerCase () вместо toUpperCase ().
Очень быстрый и чистый Java-метод, не использующий внешние библиотеки:
(Просто замените MD5 на SHA-1, SHA-256, SHA-384 или SHA-512, если они вам нужны)
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);
}
String checksum = DigestUtils.md5Hex(new FileInputStream(filePath));
Вот простая функция, которая обтекает код Сунила и принимает в качестве параметра файл. Функция не требует внешних библиотек, но требует 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);
}
}
Пример вывода:
hex=B117DD0C3CBBD009AC4EF65B6D75C97B
Вот удобный вариант, который использует InputStream.transferTo() из Java 9 и OutputStream.nullOutputStream() из Java 11. Он не требует внешних библиотек и не требует загрузки всего файла в память.
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()));
}
и
hashFile("SHA-512", Path.of("src", "test", "resources", "some.txt").toFile());
возвращается
"e30fa2784ba15be37833d569280e2163c6f106506dfb9b07dde67a24bfb90da65c661110cf2c5c6f71185754ee5ae3fd83a5465c92f72abd888b03187229da29"
Может, это поможет. Вы также можете посмотреть спецификацию, но это потребует больше усилий, поскольку это сложно.