如何在亚马逊S3上获取文件的md5sum

118

如果我在亚马逊的 S3 上有现有文件,最简单的方法是获取它们的 md5sum 而不必下载这些文件?


2
ETag头部是MD5,但不适用于多部分文件。这里有更多关于如何使用它的信息:https://dev59.com/RGw15IYBdhLWcg3wd7pj#31086810 - roeland
5
有没有办法在不检索整个对象并在本地计算的情况下计算 S3 对象的 MD5 值?目前,没有答案实际上回答了这个非常简单的问题,而是纯粹关注 ETag。大多数提出使用 ETag 的答案甚至承认它并不适合替代计算出的 MD5。 - bsplosion
14个回答

54
AWS的文档(截至2023年11月17日)关于ETag的说明如下:
实体标签(ETag)代表对象的特定版本。ETag仅反映对象内容的更改,而不包括元数据。ETag可能是对象数据的MD5摘要,也可能不是,这取决于对象的创建方式和加密方式,具体如下:
通过AWS管理控制台、PUT Object、POST Object或Copy操作创建的对象: - 明文对象或使用Amazon S3托管密钥(SSE-S3)进行服务器端加密的对象具有MD5摘要作为ETag。 - 使用客户提供的密钥(SSE-C)或AWS密钥管理服务(SSE-KMS)进行服务器端加密的对象的ETag不是MD5摘要。
通过分段上传或上传部分复制操作创建的对象的ETag不是MD5摘要,无论加密方法如何。
类型:字符串
参考资料:http://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html

实体标签是对象的哈希值。ETag仅反映对象内容的更改,而不包括元数据。但如果修改了元数据,ETag将会发生变化。 - undefined

27

据Gael Fraiteur的评论所述,分段上传的ETag似乎不是MD5。在这种情况下,它包含一个减号和一个数字的后缀。然而,即使在减号之前的部分似乎也不是MD5,尽管它与MD5的长度相同。可能后缀是上传的部分数量?


4
这个后缀似乎只在文件很大(大于5GB)时出现。通过检查一些我拥有的大文件,似乎该后缀代表已上传的部分数目。然而,第一个部分似乎没有与原始文件相同的md5哈希值。当计算这个哈希值时,亚马逊必须为每个部分添加了一些额外的数据。我想知道算法,以便检查我的一些文件。 - broc.seib
2
哈希算法在这里描述:https://dev59.com/RGw15IYBdhLWcg3wd7pj - Nakedible
@broc.seib 我发现一些文件的后缀要小得多,比如一个只有18.3MB的文件。我想知道这是否取决于上传文件所使用的工具;我正在使用 aws s3 cp ... - Mark
@Mark 这里发布的答案有更多细节:https://dev59.com/Cmct5IYBdhLWcg3wULzo#19896823 - broc.seib

17
这是一个很老的问题,但我花了很长时间才找到下面的信息,这是我能找到的第一个地方之一,所以我想详细说明一下,以防有人需要。
ETag是MD5。但对于多部分上传文件,MD5是从每个上传部分的MD5串联计算出来的。 因此,您不需要在服务器上计算MD5。只需获取ETag即可。
正如@EmersonFarrugia在这个答案中所说:
说你上传了一个14MB的文件,你的部分大小为5MB。计算对应于每个部分的3个MD5校验和,即第一个5MB、第二个5MB和最后4MB的校验和。然后取它们的串联的校验和。由于MD5校验和是二进制数据的十六进制表示,所以确保你取解码二进制串联的MD5,而不是ASCII或UTF-8编码的串联。完成后,在其后加上连字符和部分数,就可以得到ETag。
所以你唯一需要的是ETag和上传部分大小。但ETag有一个-NumberOfParts后缀。所以你可以将大小除以后缀并获得部分大小。5Mb是最小的部分大小和默认值。部分大小必须是整数,所以你不能得到像7.25Mb这样的部分大小。因此,获取部分大小信息应该很容易。
这是一个在osx中实现它的脚本,有一个Linux版本在注释中:https://gist.github.com/emersonf/7413337 我会把两个脚本都放在这里,以防将来无法访问上面的页面:
Linux版本:
#!/bin/bash
set -euo pipefail
if [ $# -ne 2 ]; then
    echo "Usage: $0 file partSizeInMb";
    exit 0;
fi
file=$1
if [ ! -f "$file" ]; then
    echo "Error: $file not found." 
    exit 1;
fi
partSizeInMb=$2
fileSizeInMb=$(du -m "$file" | cut -f 1)
parts=$((fileSizeInMb / partSizeInMb))
if [[ $((fileSizeInMb % partSizeInMb)) -gt 0 ]]; then
    parts=$((parts + 1));
fi
checksumFile=$(mktemp -t s3md5.XXXXXXXXXXXXX)
for (( part=0; part<$parts; part++ ))
do
    skip=$((partSizeInMb * part))
    $(dd bs=1M count=$partSizeInMb skip=$skip if="$file" 2> /dev/null | md5sum >> $checksumFile)
done
etag=$(echo $(xxd -r -p $checksumFile | md5sum)-$parts | sed 's/ --/-/')
echo -e "${1}\t${etag}"
rm $checksumFile

操作系统版本:

#!/bin/bash

if [ $# -ne 2 ]; then
    echo "Usage: $0 file partSizeInMb";
    exit 0;
fi

file=$1

if [ ! -f "$file" ]; then
    echo "Error: $file not found." 
    exit 1;
fi

partSizeInMb=$2
fileSizeInMb=$(du -m "$file" | cut -f 1)
parts=$((fileSizeInMb / partSizeInMb))
if [[ $((fileSizeInMb % partSizeInMb)) -gt 0 ]]; then
    parts=$((parts + 1));
fi

checksumFile=$(mktemp -t s3md5)

for (( part=0; part<$parts; part++ ))
do
    skip=$((partSizeInMb * part))
    $(dd bs=1m count=$partSizeInMb skip=$skip if="$file" 2>/dev/null | md5 >>$checksumFile)
done

echo $(xxd -r -p $checksumFile | md5)-$parts
rm $checksumFile

14

以下是我用Python编写的比较本地文件校验和与S3 ETag的代码。

def md5_checksum(filename):
    m = hashlib.md5()
    with open(filename, 'rb') as f:
        for data in iter(lambda: f.read(1024 * 1024), b''):
            m.update(data)
   
    return m.hexdigest()


def etag_checksum(filename, chunk_size=8 * 1024 * 1024):
    md5s = []
    with open(filename, 'rb') as f:
        for data in iter(lambda: f.read(chunk_size), b''):
            md5s.append(hashlib.md5(data).digest())
    m = hashlib.md5(b"".join(md5s))
    print('{}-{}'.format(m.hexdigest(), len(md5s)))
    return '{}-{}'.format(m.hexdigest(), len(md5s))

def etag_compare(filename, etag):
    et = etag[1:-1] # strip quotes
    print('et',et)
    if '-' in et and et == etag_checksum(filename):
        return True
    if '-' not in et and et == md5_checksum(filename):
        return True
    return False


def main():   
    session = boto3.Session(
        aws_access_key_id=s3_accesskey,
        aws_secret_access_key=s3_secret
    )
    s3 = session.client('s3')
    obj_dict = s3.get_object(Bucket=bucket_name, Key=your_key)

    etag = (obj_dict['ETag'])
    print('etag', etag)
    
    validation = etag_compare(filename,etag)
    print(validation)
    etag_checksum(filename, chunk_size=8 * 1024 * 1024)
    return validation


你的“key”和“文件名”是一样的吗? - Jason Liu
有所不同。 - li Anna
当使用 etag_checksum 函数时,我遇到了文件大小小于 chunk_size 的问题。一个简单的文件大小测试 (os.path.getsize(filename) < chunk_size) 可以解决这个问题,如果其他人也遇到了这个问题,可以尝试这种方法。在这种情况下,哈希值是 hashlib.md5(f.read()) - KingBugAndTheCodeWizard

7
截至2022年2月25日,S3功能新增了一个Checksum检索功能GetObjectAttributes

新功能 - Amazon S3的附加Checksum算法 | AWS新闻博客

Checksum检索 - 新的GetObjectAttributes函数返回对象的校验和以及(如果适用)每个部分的校验和。

该函数支持SHA-1、SHA-256、CRC-32和CRC-32C来检查传输的完整性。

更新:看起来,虽然这种GetObjectAttributes方法在许多情况下都有效,但在某些情况下,如控制台上传,校验和是基于16 MB块计算的。请参见例如检查对象完整性

当您使用AWS管理控制台执行某些操作时,如果对象的大小超过16MB,Amazon S3将使用分段上传。在这种情况下,校验和不是整个对象的直接校验和,而是基于每个单独部分的校验和值进行计算的。
例如,假设您以单一部分直接上传的方式上传了一个100MB大小的对象,使用REST API。在这种情况下,校验和是整个对象的校验和。如果您稍后使用控制台对该对象进行重命名、复制、更改存储类别或编辑元数据,Amazon S3将使用分段上传功能来更新对象。因此,Amazon S3会为对象创建一个新的校验和值,该值是基于各个部分的校验和值计算得出的。
看起来MD5实际上不是新功能的选项,所以这可能无法解决您最初的问题,但是由于MD5已经被弃用了很多原因,如果您可以使用替代的校验和,那么这可能是您要找的东西。

有没有 AWS 的文档宣布 MD5 已被弃用? - Rauf Aghayev
@RaufAghayev 亚马逊不在“GetObjectAttributes”返回的校验和集合中包含MD5的事实表明他们认识到它存在问题。有关MD5的许多这样的问题都有记录,例如,请参见https://en.wikipedia.org/wiki/MD5,它已正式被废弃用于许多目的,例如https://www.rfc-editor.org/rfc/rfc9155.pdf,但是,在某些安全意识较低的应用程序中可能存在使用它的风险较低的情况。 - nealmcb
1
这对于大于16MB的文件(分块上传)仍然不起作用,根据文档 - https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html#large-object-checksums - orogers
谢谢,@orogers - 看起来确实可能会有分块的复杂情况。不过根据我阅读文档的内容,这只是控制台上传时的问题。我已更新我的答案。 - nealmcb

6

对于那些花费时间搜索为什么在S3中md5与ETag不同的人来说。

ETag将根据数据块计算并连接所有md5哈希以再次生成md5哈希,并在末尾保留数据块数。

这里是生成哈希的C#版本。

    string etag = HashOf("file.txt",8);

源代码
    private string HashOf(string filename,int chunkSizeInMb)
    {
        string returnMD5 = string.Empty;
        int chunkSize = chunkSizeInMb * 1024 * 1024;

        using (var crypto = new MD5CryptoServiceProvider())
        {
            int hashLength = crypto.HashSize/8;

            using (var stream = File.OpenRead(filename))
            {
                if (stream.Length > chunkSize)
                {
                    int chunkCount = (int)Math.Ceiling((double)stream.Length/(double)chunkSize);

                    byte[] hash = new byte[chunkCount*hashLength];
                    Stream hashStream = new MemoryStream(hash);

                    long nByteLeftToRead = stream.Length;
                    while (nByteLeftToRead > 0)
                    {
                        int nByteCurrentRead = (int)Math.Min(nByteLeftToRead, chunkSize);
                        byte[] buffer = new byte[nByteCurrentRead];
                        nByteLeftToRead -= stream.Read(buffer, 0, nByteCurrentRead);

                        byte[] tmpHash = crypto.ComputeHash(buffer);

                        hashStream.Write(tmpHash, 0, hashLength);

                    }

                    returnMD5 = BitConverter.ToString(crypto.ComputeHash(hash)).Replace("-", string.Empty).ToLower()+"-"+ chunkCount;
                }
                else {
                    returnMD5 = BitConverter.ToString(crypto.ComputeHash(stream)).Replace("-", string.Empty).ToLower();

                }
                stream.Close();
            }
        }
        return returnMD5;
    }

这段代码对于小文件可以工作,但是对于大文件会给我一个不同的哈希值。 - Tono Nam
文件的大小是多少? - Pitipong Guntawong
如何获取S3多部分对象键的块大小? - Daniel
根据上传软件而定。您可以通过AWS CLI在上传时设置块大小。(默认值为:8MB) 参考:https://docs.aws.amazon.com/cli/latest/topic/s3-config.html - Pitipong Guntawong
这个话题,我只想提供一些建议...我需要验证使用S3 SDK传输工具下载的文件。如果文件已经存在,我会使用以下代码来计算块大小:var kSize = ETag.Split('-'); var tSize = double.TryParse(kSize.LastOrDefault(), out var idd) ? idd : 5; var tq = (int)Math.Floor(AmazonS3Object.Size / Math.Floor(tSize) / (1024*1024)); - Adrian Hum

3

最简单的方法是在上传这些文件到你的存储桶之前,将校验和自己设置为元数据:

ObjectMetadata md = new ObjectMetadata();
md.setContentMD5("foobar");
PutObjectRequest req = new PutObjectRequest(BUCKET, KEY, new File("/path/to/file")).withMetadata(md);
tm.upload(req).waitForUploadResult();

现在你可以在不下载文件的情况下访问这些元数据:
ObjectMetadata md2 = s3Client.getObjectMetadata(BUCKET, KEY);
System.out.println(md2.getContentMD5());

来源: https://github.com/aws/aws-sdk-java/issues/1711

这是一个有关AWS SDK for Java的问题。该问题解决了S3客户端在上传大文件时可能会崩溃的问题。此外,还修复了一些Javadoc错误和其他小问题。建议用户升级到最新版本以获取更好的性能和稳定性。


1

MD5是一个已弃用的算法,不受AWS S3支持,但您可以通过使用--checksum-algorithm上传文件来获取SHA256校验和,如下所示:

aws s3api put-object --bucket picostat --key nasdaq.csv --body nasdaq.csv --checksum-algorithm SHA256

这将返回类似于以下的输出:

{
    "ETag": "\"25f798aae1c15d44a556366b24d85b6d\"",
    "ChecksumSHA256": "TEqQVO6ZsOR9FEDv3ofP8KDKbtR02P6foLKEQYFd+MI=",
    "ServerSideEncryption": "AES256"
}

然后在原始文件上运行这个base64算法进行比较:
shasum -a 256 nasdaq.csv | cut -f1 -d\ | xxd -r -p | base64 

将CSV文件的引用替换为您自己的引用,并将桶名称更改为您自己的名称。

每当您想要检索校验和时,可以运行:

aws s3api get-object-attributes --bucket picostat --key nasdaq.csv --object-attributes "Checksum"

AWS不再支持MD5,这在哪里写明了? - Rauf Aghayev
1
正如我之前提到的,服务器端不支持MD5,但支持SHA256(S3不会为您计算MD5哈希值,但如果您自己计算,可以在创建对象时将MD5哈希值发送到S3)。 - pmagunia
谢谢您的回答,但这在AWS方面有文档记录吗?再次感谢您的答复。 - Rauf Aghayev
您可以在此处查看支持的校验和:https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html - pmagunia
他们不是只提到了额外的校验和吗?据我理解,他们通过头部支持MD5,但这些是额外的校验和,他们没有使用MD5。 - Rauf Aghayev
1
所以,我向AWS的解决方案架构师再次核实了一下。显然,MD5仍然得到支持。 - Rauf Aghayev

1
我发现s3cmd有一个--list-md5选项,可以与ls命令一起使用,例如:
最初的回答。
s3cmd ls --list-md5 s3://bucket_of_mine/

希望这可以帮到您。

1
这很方便,但正如其他几个答案中提到的那样,在某些文件上,这将不是实际的MD5校验和,而是其他类型的哈希值。 - Ian Greenleaf Young
3
我已经检查了s3cmd的源代码,它在上传时将md5存储在元数据中。因此,这个命令只会打印使用s3cmd上传或以单个块上传的对象的md5。 - ZAB

-1

我已经成功地使用了以下方法。这里我提供一个带注释的Python片段。

假设我们想要获取存储在S3中的对象的MD5校验和,并且该对象是使用分块上传过程加载的。在S3中与对象一起存储的ETag值不是我们想要的MD5校验和。可以使用以下Python命令来流式传输对象的二进制数据,而无需下载或打开对象文件,以计算所需的MD5校验和。请注意,此方法假定已建立到包含对象的S3帐户的连接,并且已导入boto3hashlib模块:

#
# specify the S3 object...
#
bucket_name = "raw-data"
object_key = "/date/study-name/sample-name/file-name"
s3_object = s3.Object(bucket_name, object_key)

#
# compute the MD5 checksum for the specified object...
#
s3_object_md5 = hashlib.md5(s3_object.get()['Body'].read()).hexdigest()

这种方法适用于存储在S3中的所有对象(即,使用或不使用分段上传过程加载的对象)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接