对于大于5GB的文件,如何计算Amazon-S3 Etag的算法是什么?


74

上传到Amazon S3且小于5GB的文件具有一个ETag,该ETag只是文件的MD5哈希,这使您很容易检查本地文件是否与S3上的文件相同。

但是,如果您的文件大于5GB,则Amazon对ETag的计算会有所不同。

例如,我对380个部分的5970150664字节文件进行了分段上传。现在S3显示它的ETag为6bcf86bed8807b8e78f0fc6e0a53079d-380。我的本地文件的md5哈希为702242d3703818ddefe6bf7da2bed757。我认为破折号后的数字是分段上传中的部分数量。

我还怀疑新的ETag(破折号之前)仍然是MD5哈希,但是在分段上传的过程中包含了一些元数据。

有谁知道如何使用与Amazon S3相同的算法来计算ETag?


14
只是要澄清一下,问题不在于文件超过5GB时ETag算法会有所改变。对于非分段上传和分段上传,ETag算法是不同的。如果使用一个5MB的一部分和一个1MB的一部分上传了6MB文件的ETag,您会遇到相同的问题。MD5用于非分段上传,上限为5GB。我的答案中的算法用于分段上传,每部分上限为5GB。
Emerson Farrugia 2014年

如果启用了服务器端加密,它也将有所不同。我认为etag可能应被视为实现细节,而不应依赖于客户端。
wim

@wim知道启用SSE时如何计算ETag吗?
阿维胡

1
不会。而且我什至不认为这是有可能的-能够从etag本身推断出有关内容的任何信息,首先都将与加密目标背道而驰,如果已知的有效载荷可以预测地复制相同的etag,那么这将是信息泄漏。
wim

Answers:


89

假设您将一个14MB的文件上传到没有服务器端加密的存储桶中,而部件大小为5MB。计算每个部分对应的3个MD5校验和,即前5MB,后5MB和后4MB的校验和。然后取其串联的校验和。MD5校验和通常以二进制数据的十六进制表示形式打印,因此请确保您采用解码后的二进制级联的MD5,而不是ASCII或UTF-8编码级联的MD5。完成后,添加连字符和零件数量以获取ETag。

以下是从控制台在Mac OS X上执行此操作的命令:

$ dd bs=1m count=5 skip=0 if=someFile | md5 >>checksums.txt
5+0 records in
5+0 records out
5242880 bytes transferred in 0.019611 secs (267345449 bytes/sec)
$ dd bs=1m count=5 skip=5 if=someFile | md5 >>checksums.txt
5+0 records in
5+0 records out
5242880 bytes transferred in 0.019182 secs (273323380 bytes/sec)
$ dd bs=1m count=5 skip=10 if=someFile | md5 >>checksums.txt
2+1 records in
2+1 records out
2599812 bytes transferred in 0.011112 secs (233964895 bytes/sec)

此时,所有校验和都在中checksums.txt。要连接它们并解码十六进制并获得批次的MD5校验和,只需使用

$ xxd -r -p checksums.txt | md5

现在有3个部分,请附加“ -3”以获取ETag。

笔记

  • 如果您通过aws-cli上载,aws s3 cp则很可能具有8MB的块大小。根据docs,这是默认设置。
  • 如果存储桶已开启服务器端加密(SSE),则ETag不会是MD5校验和(请参阅API文档)。但是,如果您只是想验证上传的零件是否与您发送的零件匹配,则可以使用Content-MD5标题,S3会为您进行比较
  • md5在macOS上仅写出校验和,但md5sum在Linux / brew上也输出文件名。您需要将其剥离,但是我敢肯定有一些选项仅输出校验和。您无需担心空格,因为xxd它将忽略它。

代码链接


1
有趣的发现,希望亚马逊不会更改它,因为它没有记录功能
sanyi

好点子。根据HTTP规范,ETag完全由他们决定,唯一的保证是他们不能为更改的资源返回相同的ETag。我猜测虽然更改算法没有太多优势。
Emerson Farrugia

1
有没有一种方法可以从etag中计算出“零件尺寸”?
DavidG 2014年

1
“计算”不,“猜测”。如果ETag以“ -4”结尾,则您知道有四个部分,但最后一个部分的大小可小至1个字节,直至部分大小。因此,将文件大小除以部分数即可得出一个估计值,但是当部分数很小(例如-2)时,就很难猜测了。如果您使用相同的零件尺寸上传了多个文件,则还可以查找相邻的零件计数,例如-4和-5并缩小零件尺寸的范围,例如,在-2处为1.9MB,在-处为2.1MB。 3表示部件大小为2MB正负100KB。
Emerson Farrugia 2014年

4
我认为依靠AWS的内部实现是不明智的,只要它们不会特别将散列算法公开为合同(如果它会影响应用程序的正确性),通常这就是验证数据完整性的情况。
iman

14

根据此处的答案,我编写了一个Python实现,可以正确计算多部分和单部分文件ETag。

def calculate_s3_etag(file_path, chunk_size=8 * 1024 * 1024):
    md5s = []

    with open(file_path, 'rb') as fp:
        while True:
            data = fp.read(chunk_size)
            if not data:
                break
            md5s.append(hashlib.md5(data))

    if len(md5s) < 1:
        return '"{}"'.format(hashlib.md5().hexdigest())

    if len(md5s) == 1:
        return '"{}"'.format(md5s[0].hexdigest())

    digests = b''.join(m.digest() for m in md5s)
    digests_md5 = hashlib.md5(digests)
    return '"{}-{}"'.format(digests_md5.hexdigest(), len(md5s))

官方aws cli工具使用的默认chunk_size为8 MB ,并且它对2个以上的块执行分段上传。它应该在Python 2和3下都可以工作。


看来我使用官方的AWS CLI工具的块大小为16MB,也许他们已对其进行了更新?
SerialEnabler

这对我来说是一个20GB的文件,块大小为8MB。我使用aws cli 2.1.15将深度存档存储类上传到s3。
jtbandes

11

bash实施

python实现

从字面上看,该算法是(从python实现的自述文件中复制的):

  1. md5大块
  2. 将md5字​​符串放在一起
  3. 将glob转换为二进制
  4. md5球形块md5s的二进制文件
  5. 在二进制文件的md5字符串末尾附加“ -Number_of_chunks”

这并不能真正解释算法的工作原理,等等。(不是-1 btw)
Willem Van Onsem 2015年

我在分步列表中添加了实际算法。我一整天都在写关于如何实现它的文章,其中大部分都是不正确或过时的信息。
tlastowka 2015年

2
这似乎不起作用。使用默认的块大小8(MB),我得到的etag与亚马逊告诉我的是正确的不同。
Cory

@Cory我不能说bash脚本,但是python实现存在文件大小小于8MB块大小的问题。但是,有一个请求请求可以解决该问题。
v.tralala '19

永远拿走了我,但是它的python版本对我
有用

9

不确定是否可以帮助您:

我们目前在进行丑陋(但到目前为止非常有用)的修改,修复这些错误的ETag在多上传的文件,其中包括对应用变化桶里的文件; 会触发来自Amazon的md5重新计算,从而将ETag更改为与实际md5签名相匹配。

在我们的情况下:

文件:bucket / Foo.mpg.gpg

  1. 获得ETag:“ 3f92dffef0a11d175e60fb8b958b4e6e-2”
  2. 对文件做一些事情将其重命名,添加诸如假标头的元数据等)
  3. 获得的Etag:“ c1d903ca1bb6dc68778ef21e74cc15b0”

我们不知道该算法,但是由于我们可以“修复” ETag,因此我们也不必担心它。


2
但是,它不能用于大于5GB的文件:(您是否有解决方法?
d33pika 2013年

似乎这样已经停止工作,至少对于我正在检查的文件而言。
phunehehe

我还发现了这个技巧,并且试图理解为什么突然无法通过预期的方式计算通过Web界面上传的文件的Etag。在2019年,这仍在起作用并达到目的。知道为什么会发生这种情况吗?
dletozeun

无论如何,依靠Etag来比较文件似乎不是一个好主意(超出了它的计算时间),因为该算法没有记录在案,它有时会被破坏。实际上,S3系统元数据似乎包含文件MD5(docs.aws.amazon.com/AmazonS3/latest/dev/…),该文件可能会回答原始问题。但我尚未测试过检索此元数据。
dletozeun

9

相同的算法,Java版本:(BaseEncoding,Hasher,Hashing等来自guava库

/**
 * Generate checksum for object came from multipart upload</p>
 * </p>
 * AWS S3 spec: Entity tag that identifies the newly created object's data. Objects with different object data will have different entity tags. The entity tag is an opaque string. The entity tag may or may not be an MD5 digest of the object data. If the entity tag is not an MD5 digest of the object data, it will contain one or more nonhexadecimal characters and/or will consist of less than 32 or more than 32 hexadecimal digits.</p> 
 * Algorithm follows AWS S3 implementation: https://github.com/Teachnova/s3md5</p>
 */
private static String calculateChecksumForMultipartUpload(List<String> md5s) {      
    StringBuilder stringBuilder = new StringBuilder();
    for (String md5:md5s) {
        stringBuilder.append(md5);
    }

    String hex = stringBuilder.toString();
    byte raw[] = BaseEncoding.base16().decode(hex.toUpperCase());
    Hasher hasher = Hashing.md5().newHasher();
    hasher.putBytes(raw);
    String digest = hasher.hash().toString();

    return digest + "-" + md5s.size();
}

我的英雄英雄!!!!!!!!! 我花了很多时间试图正确地获得二进制编码...我不知道番石榴具有此功能。
nterry

非常好,像魅力一样。只是注意:您可以使用onelinerDigestUtils.md5Hex(raw)apache-commons如果需要的话,而不是番石榴散列器的。
Pom12

5

在上面的答案中,有人问是否有办法为大于5G的文件获取md5。

为了获得MD5值(对于大于5G的文件),我可以给出的答案是将其手动添加到元数据中,或者使用程序进行上传以添加信息。

例如,我使用s3cmd上传文件,并添加了以下元数据。

$ aws s3api head-object --bucket xxxxxxx --key noarch/epel-release-6-8.noarch.rpm 
{
  "AcceptRanges": "bytes", 
  "ContentType": "binary/octet-stream", 
  "LastModified": "Sat, 19 Sep 2015 03:27:25 GMT", 
  "ContentLength": 14540, 
  "ETag": "\"2cd0ae668a585a14e07c2ea4f264d79b\"", 
  "Metadata": {
    "s3cmd-attrs": "uid:502/gname:staff/uname:xxxxxx/gid:20/mode:33188/mtime:1352129496/atime:1441758431/md5:2cd0ae668a585a14e07c2ea4f264d79b/ctime:1441385182"
  }
}

它不是使用ETag的直接解决方案,而是一种可以访问所需元数据(MD5)的方式。如果有人上传没有元数据的文件,它仍然会失败。


5

根据AWS文档,ETag既不是分段上传的MD5哈希,也不是加密对象的MD5哈希:http : //docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html

由PUT对象,POST对象或复制操作创建的对象,或者通过AWS管理控制台创建的,并由SSE-S3或纯文本加密的对象,其ETag是其对象数据的MD5摘要。

由PUT对象,POST对象或复制操作创建的对象,或者通过AWS管理控制台创建并由SSE-C或SSE-KMS加密的对象,其ETag不是其对象数据的MD5摘要。

如果通过“分段上传”或“部分复制”操作创建了对象,则无论采用哪种加密方法,ETag都不是MD5摘要。


3

这是红宝石中的算法...

require 'digest'

# PART_SIZE should match the chosen part size of the multipart upload
# Set here as 10MB
PART_SIZE = 1024*1024*10 

class File
  def each_part(part_size = PART_SIZE)
    yield read(part_size) until eof?
  end
end

file = File.new('<path_to_file>')

hashes = []

file.each_part do |part|
  hashes << Digest::MD5.hexdigest(part)
end

multipart_hash = Digest::MD5.hexdigest([hashes.join].pack('H*'))
multipart_etag = "#{multipart_hash}-#{hashes.count}"

多亏了Ruby中最短的Hex2Bin分段上传到S3 ...


真好!我确认这对我有用。较小的更改:最后的“ multi_part_hash”应为“ multipart_hash”。我还在主体部分周围添加了“ ARGV.each do”循环,并在最后添加了打印内容,以使其成为命令行脚本。
威廉·彼得里

2

这是计算ETag的PHP版本:

function calculate_aws_etag($filename, $chunksize) {
    /*
    DESCRIPTION:
    - calculate Amazon AWS ETag used on the S3 service
    INPUT:
    - $filename : path to file to check
    - $chunksize : chunk size in Megabytes
    OUTPUT:
    - ETag (string)
    */
    $chunkbytes = $chunksize*1024*1024;
    if (filesize($filename) < $chunkbytes) {
        return md5_file($filename);
    } else {
        $md5s = array();
        $handle = fopen($filename, 'rb');
        if ($handle === false) {
            return false;
        }
        while (!feof($handle)) {
            $buffer = fread($handle, $chunkbytes);
            $md5s[] = md5($buffer);
            unset($buffer);
        }
        fclose($handle);

        $concat = '';
        foreach ($md5s as $indx => $md5) {
            $concat .= hex2bin($md5);
        }
        return md5($concat) .'-'. count($md5s);
    }
}

$etag = calculate_aws_etag('path/to/myfile.ext', 8);

这是一个增强的版本,可以根据预期的ETag进行验证-如果您不知道,甚至可以猜测块大小!

function calculate_etag($filename, $chunksize, $expected = false) {
    /*
    DESCRIPTION:
    - calculate Amazon AWS ETag used on the S3 service
    INPUT:
    - $filename : path to file to check
    - $chunksize : chunk size in Megabytes
    - $expected : verify calculated etag against this specified etag and return true or false instead
        - if you make chunksize negative (eg. -8 instead of 8) the function will guess the chunksize by checking all possible sizes given the number of parts mentioned in $expected
    OUTPUT:
    - ETag (string)
    - or boolean true|false if $expected is set
    */
    if ($chunksize < 0) {
        $do_guess = true;
        $chunksize = 0 - $chunksize;
    } else {
        $do_guess = false;
    }

    $chunkbytes = $chunksize*1024*1024;
    $filesize = filesize($filename);
    if ($filesize < $chunkbytes && (!$expected || !preg_match("/^\\w{32}-\\w+$/", $expected))) {
        $return = md5_file($filename);
        if ($expected) {
            $expected = strtolower($expected);
            return ($expected === $return ? true : false);
        } else {
            return $return;
        }
    } else {
        $md5s = array();
        $handle = fopen($filename, 'rb');
        if ($handle === false) {
            return false;
        }
        while (!feof($handle)) {
            $buffer = fread($handle, $chunkbytes);
            $md5s[] = md5($buffer);
            unset($buffer);
        }
        fclose($handle);

        $concat = '';
        foreach ($md5s as $indx => $md5) {
            $concat .= hex2bin($md5);
        }
        $return = md5($concat) .'-'. count($md5s);
        if ($expected) {
            $expected = strtolower($expected);
            $matches = ($expected === $return ? true : false);
            if ($matches || $do_guess == false || strlen($expected) == 32) {
                return $matches;
            } else {
                // Guess the chunk size
                preg_match("/-(\\d+)$/", $expected, $match);
                $parts = $match[1];
                $min_chunk = ceil($filesize / $parts /1024/1024);
                $max_chunk =  floor($filesize / ($parts-1) /1024/1024);
                $found_match = false;
                for ($i = $min_chunk; $i <= $max_chunk; $i++) {
                    if (calculate_aws_etag($filename, $i) === $expected) {
                        $found_match = true;
                        break;
                    }
                }
                return $found_match;
            }
        } else {
            return $return;
        }
    }
}

2

这是这个疯狂的AWS挑战难题中的又一块。

FWIW,此答案假设您已经弄清楚了如何计算“ MD5部分的MD5”,并且可以从此处已经提供的所有其他答案中重建AWS Multi-part ETag。

这个答案解决的烦恼是不得不“猜测”或以其他方式“划分”原始的上传零件尺寸。

我们使用几种不同的工具上传到S3,它们似乎都具有不同的上传零件大小,因此“猜测”确实不是一个选择。另外,当零件尺寸似乎不同时,我们会上传许多文件。同样,使用旧的内部服务器副本强制创建MD5类型的ETag的技巧也不再起作用,因为AWS已将其内部服务器副本更改为也使用多部分(只是具有相当大的部分大小)。

所以...您如何找出对象的零件尺寸?

好吧,如果您首先发出一个head_object请求并检测到ETag是多部分类型的ETag(末尾包含'-<partcount>'),那么您可以发出另一个head_object请求,但具有额外的part_number属性1(第一部分)。然后,该后续head_object请求将返回第一部分的content_length。中提琴...现在您知道使用的零件尺寸,可以使用该尺寸重新创建本地ETag,该ETag应该与上载对象时创建的原始上载S3 ETag相匹配。

另外,如果您想精确(也许某些多部分上传使用的是可变的零件大小),则可以继续使用指定的每个part_number调用head_object请求,并根据返回的零件content_length计算每个零件的MD5。

希望有帮助...


1

简短的答案是,您需要获取每个部分的128位二进制md5摘要,将它们串联到一个文档中,然后对该文档进行哈希处理。此答案中提出的算法是准确的。

注意:如果您“触摸”斑点(即使不修改内容),带连字符的多部分ETAG表单将更改为不带连字符的表单。也就是说,如果您复制或对完成的多部分上传对象(也称为PUT-COPY)进行就地复制,则S3将使用该算法的简单版本重新计算ETAG。也就是说,目标对象将具有不带连字符的etag。

您可能已经考虑过这一点,但是如果您的文件小于5GB,并且已经知道它们的MD5,并且上传并行化几乎没有好处(例如,您正在从速度较慢的网络流式传输上传内容,或者从速度较慢的磁盘中上传内容) ),那么您也可以考虑使用简单的PUT而不是多部分的PUT,并在请求标头中传递您已知的Content-MD5 -如果它们不匹配,亚马逊将使上传失败。请记住,您需要为每个UploadPart付费。

此外,在某些客户端中,将已知的MD5传递给PUT操作的输入将避免客户端在传输期间重新计算MD5。例如,在boto3(python)中,您将使用client.put_object()方法的ContentMD5参数。如果您省略该参数,并且您已经知道MD5,那么客户端将在传输之前浪费循环来再次计算它。


1

node.js实现-

const fs = require('fs');
const crypto = require('crypto');

const chunk = 1024 * 1024 * 5; // 5MB

const md5 = data => crypto.createHash('md5').update(data).digest('hex');

const getEtagOfFile = (filePath) => {
  const stream = fs.readFileSync(filePath);
  if (stream.length <= chunk) {
    return md5(stream);
  }
  const md5Chunks = [];
  const chunksNumber = Math.ceil(stream.length / chunk);
  for (let i = 0; i < chunksNumber; i++) {
    const chunkStream = stream.slice(i * chunk, (i + 1) * chunk);
    md5Chunks.push(md5(chunkStream));
  }

  return `${md5(Buffer.from(md5Chunks.join(''), 'hex'))}-${chunksNumber}`;
};


1
当文件大小恰好为一个块的大小时,该算法的行为与S3完全不同。不过,这可能取决于该工具如何完成上传。
bernardn19年

0

我有一个适用于iOS和macOS的解决方案,无需使用dd和xxd这样的外部帮助程序。我刚刚找到它,所以我将其报告为现状,并计划在以后的阶段进行改进。目前,它依赖于Objective-C和Swift代码。首先,在Objective-C中创建此帮助器类:

AWS3MD5Hash.h

AWS3MD5Hash.m

现在创建一个普通的swift文件:

AWS Extensions.swift

import UIKit
import CommonCrypto

extension URL {

func calculateAWSS3MD5Hash(_ numberOfParts: UInt64) -> String? {


    do {

        var fileSize: UInt64!
        var calculatedPartSize: UInt64!

        let attr:NSDictionary? = try FileManager.default.attributesOfItem(atPath: self.path) as NSDictionary
        if let _attr = attr {
            fileSize = _attr.fileSize();
            if numberOfParts != 0 {



                let partSize = Double(fileSize / numberOfParts)

                var partSizeInMegabytes = Double(partSize / (1024.0 * 1024.0))



                partSizeInMegabytes = ceil(partSizeInMegabytes)

                calculatedPartSize = UInt64(partSizeInMegabytes)

                if calculatedPartSize % 2 != 0 {
                    calculatedPartSize += 1
                }

                if numberOfParts == 2 || numberOfParts == 3 { // Very important when there are 2 or 3 parts, in the majority of times
                                                              // the calculatedPartSize is already 8. In the remaining cases we force it.
                    calculatedPartSize = 8
                }


                if mainLogToggling {
                    print("The calculated part size is \(calculatedPartSize!) Megabytes")
                }

            }

        }

        if numberOfParts == 0 {

            let string = self.memoryFriendlyMd5Hash()
            return string

        }




        let hasher = AWS3MD5Hash.init()
        let file = fopen(self.path, "r")
        defer { let result = fclose(file)}


        var index: UInt64 = 0
        var bigString: String! = ""
        var data: Data!

        while autoreleasepool(invoking: {

                if index == (numberOfParts-1) {
                    if mainLogToggling {
                        //print("Siamo all'ultima linea.")
                    }
                }

                data = hasher.data(from: file!, startingOnByte: index * calculatedPartSize * 1024 * 1024, length: calculatedPartSize * 1024 * 1024, filePath: self.path, singlePartSize: UInt(calculatedPartSize))

                bigString = bigString + MD5.get(data: data) + "\n"

                index += 1

                if index == numberOfParts {
                    return false
                }
                return true

        }) {}

        let final = MD5.get(data :hasher.data(fromHexString: bigString)) + "-\(numberOfParts)"

        return final

    } catch {

    }

    return nil
}

   func memoryFriendlyMd5Hash() -> String? {

    let bufferSize = 1024 * 1024

    do {
        // Open file for reading:
        let file = try FileHandle(forReadingFrom: self)
        defer {
            file.closeFile()
        }

        // Create and initialize MD5 context:
        var context = CC_MD5_CTX()
        CC_MD5_Init(&context)

        // Read up to `bufferSize` bytes, until EOF is reached, and update MD5 context:
        while autoreleasepool(invoking: {
            let data = file.readData(ofLength: bufferSize)
            if data.count > 0 {
                data.withUnsafeBytes {
                    _ = CC_MD5_Update(&context, $0, numericCast(data.count))
                }
                return true // Continue
            } else {
                return false // End of file
            }
        }) { }

        // Compute the MD5 digest:
        var digest = Data(count: Int(CC_MD5_DIGEST_LENGTH))
        digest.withUnsafeMutableBytes {
            _ = CC_MD5_Final($0, &context)
        }
        let hexDigest = digest.map { String(format: "%02hhx", $0) }.joined()
        return hexDigest

    } catch {
        print("Cannot open file:", error.localizedDescription)
        return nil
    }
}

struct MD5 {

    static func get(data: Data) -> String {
        var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))

        let _ = data.withUnsafeBytes { bytes in
            CC_MD5(bytes, CC_LONG(data.count), &digest)
        }
        var digestHex = ""
        for index in 0..<Int(CC_MD5_DIGEST_LENGTH) {
            digestHex += String(format: "%02x", digest[index])
        }

        return digestHex
    }
    // The following is a memory friendly version
    static func get2(data: Data) -> String {

    var currentIndex = 0
    let bufferSize = 1024 * 1024
    //var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))

    // Create and initialize MD5 context:
    var context = CC_MD5_CTX()
    CC_MD5_Init(&context)


    while autoreleasepool(invoking: {
        var subData: Data!
        if (currentIndex + bufferSize) < data.count {
            subData = data.subdata(in: Range.init(NSMakeRange(currentIndex, bufferSize))!)
            currentIndex = currentIndex + bufferSize
        } else {
            subData = data.subdata(in: Range.init(NSMakeRange(currentIndex, data.count - currentIndex))!)
            currentIndex = currentIndex + (data.count - currentIndex)
        }
        if subData.count > 0 {
            subData.withUnsafeBytes {
                _ = CC_MD5_Update(&context, $0, numericCast(subData.count))
            }
            return true
        } else {
            return false
        }

    }) { }

    // Compute the MD5 digest:
    var digest = Data(count: Int(CC_MD5_DIGEST_LENGTH))
    digest.withUnsafeMutableBytes {
        _ = CC_MD5_Final($0, &context)
    }

    var digestHex = ""
    for index in 0..<Int(CC_MD5_DIGEST_LENGTH) {
        digestHex += String(format: "%02x", digest[index])
    }

    return digestHex

}
}

现在添加:

到您的Objective-C桥接标头。您应该可以使用此设置。

用法示例

要测试此设置,您可以在负责处理AWS连接的对象内部调用以下方法:

func getMd5HashForFile() {


    let credentialProvider = AWSCognitoCredentialsProvider(regionType: AWSRegionType.USEast2, identityPoolId: "<INSERT_POOL_ID>")
    let configuration = AWSServiceConfiguration(region: AWSRegionType.APSoutheast2, credentialsProvider: credentialProvider)
    configuration?.timeoutIntervalForRequest = 3.0
    configuration?.timeoutIntervalForResource = 3.0

    AWSServiceManager.default().defaultServiceConfiguration = configuration

    AWSS3.register(with: configuration!, forKey: "defaultKey")
    let s3 = AWSS3.s3(forKey: "defaultKey")


    let headObjectRequest = AWSS3HeadObjectRequest()!
    headObjectRequest.bucket = "<NAME_OF_YOUR_BUCKET>"
    headObjectRequest.key = self.latestMapOnServer.key




    let _: AWSTask? = s3.headObject(headObjectRequest).continueOnSuccessWith { (awstask) -> Any? in

        let headObjectOutput: AWSS3HeadObjectOutput? = awstask.result

        var ETag = headObjectOutput?.eTag!
        // Here you should parse the returned Etag and extract the number of parts to provide to the helper function. Etags end with a "-" followed by the number of parts. If you don't see this format, then pass 0 as the number of parts.
        ETag = ETag!.replacingOccurrences(of: "\"", with: "")

        print("headObjectOutput.ETag \(ETag!)")

        let mapOnDiskUrl = self.getMapsDirectory().appendingPathComponent(self.latestMapOnDisk!)

        let hash = mapOnDiskUrl.calculateAWSS3MD5Hash(<Take the number of parts from the ETag returned by the server>)

        if hash == ETag {
            print("They are the same.")
        }

        print ("\(hash!)")

        return nil
    }



}

如果服务器返回的ETag在ETag的末尾没有“-”,则只需将0传递给calculateAWSS3MD5Hash。如果您遇到任何问题,请发表评论。我正在研究一种快速解决方案,一旦完成,我将立即更新此答案。谢谢


0

Rust中的版本:

use crypto::digest::Digest;
use crypto::md5::Md5;
use std::fs::File;
use std::io::prelude::*;
use std::iter::repeat;

fn calculate_etag_from_read(f: &mut dyn Read, chunk_size: usize) -> Result<String> {
    let mut md5 = Md5::new();
    let mut concat_md5 = Md5::new();
    let mut input_buffer = vec![0u8; chunk_size];
    let mut chunk_count = 0;
    let mut current_md5: Vec<u8> = repeat(0).take((md5.output_bits() + 7) / 8).collect();

    let md5_result = loop {
        let amount_read = f.read(&mut input_buffer)?;
        if amount_read > 0 {
            md5.reset();
            md5.input(&input_buffer[0..amount_read]);
            chunk_count += 1;
            md5.result(&mut current_md5);
            concat_md5.input(&current_md5);
        } else {
            if chunk_count > 1 {
                break format!("{}-{}", concat_md5.result_str(), chunk_count);
            } else {
                break md5.result_str();
            }
        }
    };
    Ok(md5_result)
}

fn calculate_etag(file: &String, chunk_size: usize) -> Result<String> {
    let mut f = File::open(file)?;
    calculate_etag_from_read(&mut f, chunk_size)
}

查看带有简单实现的仓库:https : //github.com/bn3t/calculate-etag/tree/master


0

关于块大小,我注意到它似乎取决于零件数。作为AWS文档,最大零件数为10000。

因此,从默认的8MB开始并知道文件大小,块大小和部分可以按以下方式计算:

chunk_size=8*1024*1024
flsz=os.path.getsize(fl)

while flsz/chunk_size>10000:
  chunk_size*=2

parts=math.ceil(flsz/chunk_size)

零件必须四舍五入


0

我刚刚看到,AWS S3控制台的“上传”使用了一个不寻常的部分(块)大小为17,179,870-至少对于较大的文件而言。

使用该零件尺寸,可以使用前面所述的方法为我提供正确的ETag哈希。感谢@TheStoryCoder的php版本。

感谢@hans提出的使用头对象查看每个零件的实际尺寸的想法。

我使用AWS S3控制台(2020年11月28日)上传了大约50个文件,大小从190MB到2.3GB不等,所有文件的部分大小都为17,179,870。


-3

没有,

到目前为止,还没有解决方案来匹配普通文件ETag和本地文件的Multipart文件ETag和MD5。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.