为什么两次压缩相同的内容会产生两个不同SHA1的文件?

时间:2012-03-15 04:48:04

标签: git ant zip gzip sha

我遇到了一个git和zip文件的奇怪问题。我的构建脚本需要一堆文档html页面并将它们压缩成docs.zip然后我将此文件检入git。

我遇到的问题是,每次重新运行构建脚本并获取新的zip文件时,新的zip文件都具有与上一次运行不同的SHA1。我的构建脚本正在调用ant zip任务。然而,如果我将同一目录压缩两次,那么从Mac OS X shell手动调用macOSX zip会给我一个不同的sha1。

运行1:

zip foo.zip *
openssl sha1 foo.zip 
rm foo.zip 

运行2:

zip foo.zip *
openssl sha1 foo.zip

运行1和run2给出不同的SHA1,即使内容在运行之间没有变化。在这两种情况下,zip都会打印出完全相同的压缩文件,但并不表示任何特定于操作系统的文件(如.DS_Store)都包含在zip文件中。

zip算法是否具有确定性?如果在相同的内容上运行它会产生完全相同的位吗?如果不是为什么不呢?

我有哪些选择以确定的方式压缩文件?压缩文件中有数千个,我不希望这些文件发生太大变化。我知道git会压缩你检查的任何文件,但拉链它们的动机就是保持它们的质量不受影响。

4 个答案:

答案 0 :(得分:10)

根据维基百科http://en.wikipedia.org/wiki/Zip_(file_format),似乎zip文件有标题 文件上次修改时间和文件上次修改日期,因此如果从同一内容重建zip,则任何检入git的zip文件将显示为git已更改。并且似乎没有标志告诉它不设置这些标题。

我只是使用tar,如果多次运行,它似乎为同一输入产生相同的字节。

答案 1 :(得分:9)

默认情况下,gzip保存文件名和时间戳

%> gzip -help 2>&1 | grep -e '-n'
 -N --name            save or restore original file name and time stamp
 -n --no-name         don't save original file name or time stamp

%> gzip -V
Apple gzip 272

使用-n选项:

%> tar cv foo/ | gzip -n > foo.tgz; shasum foo.tgz # sha256sum on Ubuntu

你将始终获得相同的哈希值。

在没有-n的情况下尝试上面,每次都应该看到不同的哈希值。

答案 2 :(得分:2)

我使用-X的{​​{1}}(--no-extra)标记创建了具有相同SHA1的文件。

我创建了一个文件夹和几个要压缩的文件来测试它,正如预期的那样,每次都会得到不同的SHA1哈希值:

zip

但是,使用$ mkdir stuff $ echo "Stuff 1" > stuff/stuff1.txt $ echo "Stuff 2" > stuff/stuff2.txt $ zip -r stuff.zip stuff/ adding: stuff/ (stored 0%) adding: stuff/stuff1.txt (stored 0%) adding: stuff/stuff2.txt (stored 0%) $ shasum stuff.zip 1c8be43ac859bb57603be1243da14022710d22bd stuff.zip $ shasum stuff.zip 1c8be43ac859bb57603be1243da14022710d22bd stuff.zip $ zip -r stuff.zip stuff/ updating: stuff/ (stored 0%) updating: stuff/stuff1.txt (stored 0%) updating: stuff/stuff2.txt (stored 0%) $ shasum stuff.zip 73920362d0f7de74d87286502e03e7126fdc0a6a stuff.zip 在连续压缩后会得到相同的哈希值:

-X

我没有时间深入挖掘并找出在第一种情况下哪些额外信息导致弹出窗口的差异,但这可能对尝试解决它的人有所帮助。也只在macOS 10.12.6上测试过。

答案 3 :(得分:0)

使用以下脚本创建确定性的zip或jar文件

#!/bin/bash

usage() {
    echo "Usage : ./createDeterministicArtifact.sh <zip/jar file name>"
    exit 1
}

info() {
    echo "$1"
}

strip_artifact() {
    if [ -z ${file} ]; then
        usage
    fi
    if [ -f ${file} -a -s ${file} ]; then
        mkdir -p ${file}.tmp
        unzip -oq -d ${file}.tmp ${file}
        find ${file}.tmp -follow -exec touch -a -m -t 201912010000.00 {} \+
        if [ "$UNAME" == "Linux" ] ; then
            find ${file}.tmp -follow -exec chattr -a {} \+
        elif [[ "$UNAME" == CYGWIN* || "$UNAME" == MINGW* ]] ; then
            find ${file}.tmp -follow -exec attrib -A {} \+
        fi
        cd ${file}.tmp
        zip -rq -D -X -9 -A --compression-method deflate  ../${file}.new . 
        cd -
        rm -rf ${file}.tmp
        info "Recreated deterministic artifact: ${file}.new"
    else 
        info "Input file is empty. Please validate the file and try again"
    fi
}

file=$1
strip_artifact