Хэш-файл md5 изменяется при разбиении его на части (для передачи по сети)
Вопрос внизу
Я использую Netty для передачи файла на другой сервер. Я ограничиваю свои файловые блоки 1024*64 байтами (64 КБ) из-за протокола WebSocket. Следующий метод является локальным примером того, что произойдет с файлом:
public static void rechunck(File file1, File file2) {
FileInputStream is = null;
FileOutputStream os = null;
try {
byte[] buf = new byte[1024*64];
is = new FileInputStream(file1);
os = new FileOutputStream(file2);
while(is.read(buf) > 0) {
os.write(buf);
}
} catch (IOException e) {
Controller.handleException(Thread.currentThread(), e);
} finally {
try {
if(is != null && os != null) {
is.close();
os.close();
}
} catch (IOException e) {
Controller.handleException(Thread.currentThread(), e);
}
}
}
Файл загружен InputStream
в ByteBuffer и непосредственно записывается в OutputStream
, Содержимое файла не может измениться во время этого процесса.
Чтобы получить md5-hashes
файла я написал следующий метод:
public static String checksum(File file) {
InputStream is = null;
try {
is = new FileInputStream(file);
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[8192];
int read = 0;
while((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
return new BigInteger(1, digest.digest()).toString(16);
} catch(IOException | NoSuchAlgorithmException e) {
Controller.handleException(Thread.currentThread(), e);
} finally {
try {
is.close();
} catch(IOException e) {
Controller.handleException(Thread.currentThread(), e);
}
}
return null;
}
Итак: просто теоретически он должен возвращать тот же хеш, не так ли? Проблема в том, что он возвращает два разных хеша, которые не отличаются при каждом запуске. Размер файла остается прежним, а содержимое тоже. Когда я запускаю метод один раз для in: file-1
, out: file-2
и снова с in: file-2
а также out: file-3
Хеши файлов 2 и 3 одинаковы! Это означает, что метод будет правильно изменять файл каждый раз одинаково.
1. 58a4a9fbe349a9e0af172f9cf3e6050a
2. 7b3f343fa1b8c4e1160add4c48322373
3. 7b3f343fa1b8c4e1160add4c48322373
Вот небольшой тест, который сравнивает все буферы, если они эквивалентны. Тест положительный. Так что никаких отличий нет.
File file1 = new File("controller/templates/Example.zip");
File file2 = new File("controller/templates2/Example.zip");
try {
byte[] buf1 = new byte[1024*64];
byte[] buf2 = new byte[1024*64];
FileInputStream is1 = new FileInputStream(file1);
FileInputStream is2 = new FileInputStream(file2);
boolean run = true;
while(run) {
int read1 = is1.read(buf1), read2 = is2.read(buf2);
String result1 = Arrays.toString(buf1), result2 = Arrays.toString(buf2);
boolean test = result1.equals(result2);
System.out.println("1: " + result1);
System.out.println("2: " + result2);
System.out.println("--- TEST RESULT: " + test + " ----------------------------------------------------");
if(!(read1 > 0 && read2 > 0) || !test) run = false;
}
} catch (IOException e) {
e.printStackTrace();
}
Вопрос: Можете ли вы помочь мне разбить файл на части без изменения хеша?
2 ответа
while(is.read(buf) > 0) { os.write(buf); }
read()
Метод с аргументом массива вернет количество файлов, прочитанных из потока. Если файл не заканчивается точно как кратное длине байтового массива, это возвращаемое значение будет меньше длины байтового массива, потому что вы достигли конца файла.
Однако ваш os.write(buf);
call запишет весь байтовый массив в поток, включая оставшиеся байты после окончания файла. Это означает, что записанный файл становится больше в конце, поэтому хэш изменился.
Интересно, что вы не ошиблись, обновив дайджест сообщения:
while((read = is.read(buffer)) > 0) { digest.update(buffer, 0, read); }
Вы просто должны сделать то же самое, когда вы "перепланируете" свои файлы.
В вашем методе rechunk есть ошибка. Поскольку у вас есть фиксированный буфер, ваш файл разбит на части ByteArray. но последняя часть файла может быть меньше буфера, поэтому вы пишете слишком много байтов в новый файл. и поэтому у вас больше нет той же контрольной суммы. ошибка может быть исправлена следующим образом:
public static void rechunck(File file1, File file2) {
FileInputStream is = null;
FileOutputStream os = null;
try {
byte[] buf = new byte[1024*64];
is = new FileInputStream(file1);
os = new FileOutputStream(file2);
int length;
while((length = is.read(buf)) > 0) {
os.write(buf, 0, length);
}
} catch (IOException e) {
Controller.handleException(Thread.currentThread(), e);
} finally {
try {
if(is != null)
is.close();
if(os != null)
os.close();
} catch (IOException e) {
Controller.handleException(Thread.currentThread(), e);
}
}
}
Из-за переменной длины метод записи знает, что до байта x байтового массива отключен только файл, в нем остаются старые байты, которые больше не принадлежат файлу.