boto 获取 S3 文件的 MD5

18 投票
4 回答
22420 浏览
提问于 2025-04-28 01:14

我有一个情况,就是我需要把几百个文件上传到我的S3存储桶,使用的是分段上传。在每次上传后,我需要确保上传的文件没有损坏(也就是检查数据的完整性)。目前,我的做法是上传文件后,再把它下载回来,计算一下内容的md5值,然后和本地文件的md5值进行比较。大致就是这样:

conn = S3Connection('access key', 'secretkey')
bucket = conn.get_bucket('bucket_name')
source_path = 'file_to_upload'
source_size = os.stat(source_path).st_size

mp = bucket.initiate_multipart_upload(os.path.basename(source_path))
chunk_size = 52428800
chunk_count = int(math.ceil(source_size / chunk_size))

for i in range(chunk_count + 1):
   offset = chunk_size * i
   bytes = min(chunk_size, source_size - offset)
   with FileChunkIO(source_path, 'r', offset=offset, bytes=bytes) as fp:
       mp.upload_part_from_file(fp, part_num=i + 1, md5=k.compute_md5(fp, bytes))
mp.complete_upload()
    
obj_key = bucket.get_key('file_name')
print(obj_key.md5) #prints None
print(obj_key.base64md5) #prints None

content = bucket.get_key('file_name').get_contents_as_string()
# compute the md5 on content

这种方法有点浪费,因为它会消耗两倍的带宽。我试过

bucket.get_key('file_name').md5 
bucket.get_key('file_name').base64md5 

但两个都返回了None。

有没有其他方法可以在不下载整个文件的情况下计算md5呢?

暂无标签

4 个回答

1

自2016年以来,最简单的方法是在进行PutObject请求时,提供一个叫做--content-md5的参数。这样,AWS会检查你提供的MD5值是否和他们计算出来的MD5值一致。这种方法也适用于分片上传和大于5GB的文件。

下面是来自知识中心的一个示例调用:

aws s3api put-object --bucket awsexamplebucket --key awsexampleobject.txt --body awsexampleobjectpath --content-md5 examplemd5value1234567== --metadata md5checksum=examplemd5value1234567==

https://aws.amazon.com/premiumsupport/knowledge-center/data-integrity-s3/

3

你可以通过 e_tag 属性来恢复 md5,而不需要下载文件,方法如下:

boto3.resource('s3').Object(<BUCKET_NAME>, file_path).e_tag[1 :-1]

然后使用这个函数来比较经典的 S3 文件:

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

或者使用这个函数来处理多部分文件:

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

最后,使用这个函数在两者之间进行选择:

def md5_compare(file_path, s3_file_md5):
    if '-' in s3_file_md5 and s3_file_md5 == etag_checksum(file_path):
        return True
    if '-' not in s3_file_md5 and s3_file_md5 == md5_checksum(file_path):
        return True
    print("MD5 not equals for file " + file_path)
    return False

感谢来源: https://zihao.me/post/calculating-etag-for-aws-s3-objects/

11

我使用boto3这个库里的head_object方法来获取ETag。

import boto3
import botocore

def s3_md5sum(bucket_name, resource_name):
    try:
        md5sum = boto3.client('s3').head_object(
            Bucket=bucket_name,
            Key=resource_name
        )['ETag'][1:-1]
    except botocore.exceptions.ClientError:
        md5sum = None
        pass
    return md5sum
26

没错,
你可以用 bucket.get_key('file_name').etag[1 :-1] 这个方法。
这样就能获取文件的MD5值,而不用下载文件的内容。

撰写回答