Почему один и тот же JAR файл имеет разные хэширования каждый раз, когда я его создаю?

Я думал о проверке хеш файла jar файла, чтобы определить, изменилось ли оно или нет, но, как оказалось, у того же самого файла jar есть разные хэши каждый раз, когда я его создаю (экспортируем как файл jar из eclipse или создаем он использует maven). Я удалил значения дат файла файла и прочее, но он все еще отличается. Есть ли что-то в генерации байт-кода, которое включает временную метку или что-то еще?

Ответ 1

Файл JAR является ZIP файлом и содержит последнюю измененную дату в локальных заголовках файлов и главном файле каталога. Это приведет к разным хэшам ваших сборников.

Если вы запустите команду JAR в том же наборе файлов (с такими же датами файла) и пропустите создание файла манифеста, он должен предоставить вам тот же JAR файл (если порядок файлов внутри ZIP не изменяется).

Ответ 2

У меня была такая же проблема со сборками Gradle. В моем случае мой файл .war содержал много встроенных файлов .jar.

В Gradle задачи Jar и War по сути являются вариантами задачи Zip, которая имеет свойство preserveFileTimestamps (https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling. Zip.html # org.gradle.api.tasks.bundling.Zip: preserveFileTimestamps) Чтобы сделать SHA одинаковыми, используйте это свойство для задач jar и war, например, где-то в build.gradle:

plugins.withType(WarPlugin).whenPluginAdded {
    war {
        preserveFileTimestamps = false
    }
}
jar {
    preserveFileTimestamps = false
}

Также интересное замечание: если вы собираете на MacOS, убедитесь, что файлы .DS_Store не попадают во встроенный архив, так как это также вызовет различные SHA.

Чтобы отключить на MacOS, запустите это в терминале:

defaults write com.apple.desktopservices DSDontWriteNetworkStores true

Затем перезагрузите его. Вам все равно придется удалить существующие файлы .DS_Store, поэтому из папки вашего проекта запустите:

find . -name '.DS_Store' -exec rm {} \;

Если вы хотите сделать SHA одинаковыми даже после сборки в разных операционных системах, установите для свойства reproducibleFileOrder значение true для задач war и jar и убедитесь, что значение umask одинаково в обеих создаваемых вами системах (очевидно, gradle включает атрибуты файла внутри файлов war/jar, и у меня были разные SHA, когда эти атрибуты были разными).

Наконец, я смог получить те же SHA артефактов, где бы я ни построил.

ура

Ответ 3

Получение воспроизводимых сборок с помощью Java, т.е. сборки, которые всегда выдают один и тот же двоичный вывод, требуют некоторых настроек, поскольку Java не воспроизводится с самого начала: jar файлы с порядком файлов и метками времени являются первым естественным источником изменений. В дополнение к проблемам, вызванным Java, некоторые плагины Maven вызывают дополнительные изменения: см. вики-страницу Maven Reproducible/Verifiable Builds https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=74682318

Вы можете использовать reproducible-build-maven-plugin: https://zlika.github.io/reproducible-build-maven-plugin  для инструмента сборки Apache Maven, популярного в проектах Java или Плагин sbt-reproducible-builds https://github.com/raboof/sbt-reproducible-builds для инструмента сборки sbt, популярный в проектах Scala. Для инструмента Gradle: https://docs.gradle.org/current/userguide/working_with_files.html#sec:reproducible_archives

Для получения общей информации о "Воспроизводимых сборках" см. https://reproducible-builds.org

Ответ 4

Лучшее решение для меня было следующим в моем файле gradle (обратите внимание, что я также удаляю дату манифеста, которая может быть изменена некоторыми задачами):

// Prevent manifest from changing every build
project.tasks.withType(Jar) {
    manifest.attributes Date: ''
}

// Prevent timestamps from appearing in JAR and use reproducible file order
tasks.withType(AbstractArchiveTask) {
    preserveFileTimestamps = false
    reproducibleFileOrder = true
}

По мотивам: https://dzone.com/articles/reproducible-builds-in-java