2012-08-29 131 views
30

上傳到Amazon S3的小於5GB的文件有一個ETag,它只是文件的MD5哈希碼,可以很容易地檢查您的本地文件是否與您在S3上放置的文件相同。計算大於5GB的文件的Amazon-S3 Etag的算法是什麼?

但是,如果您的文件大於5GB,則Amazon會以不同的方式計算ETag。

例如,我做了380部分5970150664字節文件的分段上傳。現在S3顯示它具有6bcf86bed8807b8e78f0fc6e0a53079d-380的ETag。我的本地文件有一個md5散列702242d3703818ddefe6bf7da2bed757。我認爲短劃線之後的數字是分段上傳中的部分數量。

我還懷疑新的ETag(在破折號之前)仍然是MD5哈希值,但是以某種方式從多段上傳的方式中包含了一些元數據。

有誰知道如何使用與Amazon S3相同的算法計算Etag?

+6

只是爲了澄清,問題不在於如果文件超過5GB,ETag算法以某種方式更改。 ETag算法對於非分段上傳和分段上傳是不同的。如果使用一個5MB部分和一個1MB部分上傳,您會遇到同樣的問題,試圖計算6MB文件的ETag。 MD5用於非分段上傳,上限爲5GB。我的答案中的算法用於分段上傳,每部分上限爲5GB。 –

+0

如果啓用了服務器端加密,則情況也不同。我認爲etag應該被視爲實現細節,而不是依賴於客戶端。 – wim

回答

52

剛剛驗證過一個。亞馬遜讓它簡單到可以猜測。

假設您上傳了14MB的文件,並且您的零件大小爲5MB。計算與每個部分對應的3個MD5校驗和,即前5MB,後5MB和後4MB的校驗和。然後取其串聯的校驗和。由於MD5校驗和是二進制數據的十六進制表示,因此確保採用解碼二進制串聯的MD5,而不是ASCII或UTF-8編碼串聯。完成後,添加一個連字符和部分數以獲得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」,以獲得ETag的,因爲有3個部分。

值得一提的是,md5在Mac OS X只是寫出了校驗,但md5sum在Linux上還輸出文件名。你需要刪除它,但我確定有一些選項只輸出校驗和。你不需要擔心空白,因爲xxd會忽略它。

注意:如果通過aws s3 cpaws-cli上傳,那麼你很可能有8MB CHUNKSIZE。根據docs,這是默認值。

更新:有人告訴我在https://github.com/Teachnova/s3md5的這個實現,這不會對OS X上運行這裏有一個要點我用working script for OS X寫道。

+0

有趣的發現,希望亞馬遜不會改變它因爲它沒有記錄的功能 – sanyi

+0

好點根據HTTP規範,ETag完全可以自行決定,唯一的保證就是它們不能爲一個改變的資源返回相同的ETag,我猜測沒有太大的優勢改變算法 –

+1

有沒有一種方法可以計算etag中的「零件大小」? – DavidG

-1

沒有,

到現在爲止還沒有解決方案來匹配正常文件的ETag和多文件的ETag和本地文件的MD5。

7

不知道是否可以幫助:

目前,我們正在做一個醜陋的(但到目前爲止有用)在多上載的文件劈死修復那些錯誤的ETag,其中包括對應用變化存儲桶中的文件;這會觸發Amazon的MD5重新計算,將ETag更改爲與實際的md5簽名相匹配。

在我們的例子:

文件:桶/ Foo.mpg.gpg

  1. ETag的獲得: 「3f92dffef0a11d175e60fb8b958b4e6e-2」
  2. 東西與文件(其重命名爲 ,添加像假頭的元數據等)
  3. Etag獲得:「c1d903ca1bb6dc68778ef21e74cc15b0」

我們不知道算法,但由於我們可以「修復」ETag,我們也不需要擔心它。

+1

真棒找到!謝謝! – d33pika

+1

雖然對於大於5GB的文件並不起作用:(你有解決方法嗎? – d33pika

+0

似乎已停止工作,至少對於我正在檢查的文件。 – phunehehe

7

相同的算法,Java版本: (BaseEncoding,散列器,哈希等來自guava library

/** 
* 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(); 
} 
+0

我的怪胎英雄!!!!!!!!!我花了很多個小時試圖讓二進制編碼正確...我不知道番石榴有這個功能。 – nterry

3

在上面的回答,有人問是否有一種方式來獲得比大文件的MD5 5G

爲了獲得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)的方式。如果有人在沒有元數據的情況下上傳文件,它仍然會失敗。

4

bash implementation

python implementation

該算法字面上是(自述在Python實現複製):

  1. MD5組塊
  2. glob的MD5的字符串一起
  3. 轉換glob轉爲二進制
  4. MD5算法的匹配替換塊的二進制md5s
  5. 追加「-Number_of_chunks」到的二進制
+0

這並不能解釋算法的工作原理等。(沒有-1順便說一句) –

+0

我在逐步列表中添加了實際的算法。我寫了一篇關於如何完成整篇文章的python實現,其中大部分充滿了不正確或過時的信息。 – tlastowka

+0

這似乎不起作用。使用8(MB)的默認塊大小,我從amazon告訴我的不同etag是正確的。 – Cory

1

的MD5字符串的結尾這裏是計算的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; 
     } 
    } 
} 
1

這裏是紅寶石的算法...

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}" 

感謝Shortest Hex2Bin in RubyMultipart Uploads to S3 ...

+0

不錯!我確認這對我有用。微小的變化:最後的「multi_part_hash」應該是「multipart_hash」。我還在主要部分添加了一個「ARGV.each do」循環,並在最後添加了一個打印,使其成爲命令行腳本。 –

1

按照AWS文檔的ETag不是一個MD5哈希多部分上傳或加密對象:http://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html

由PUT Obj創建的對象ect,POST對象或複製操作,或通過AWS管理控制檯進行加密,並通過SSE-S3或明文進行加密,從而使ETags成爲其對象數據的MD5摘要。

由PUT對象,POST對象或複製操作或通過AWS管理控制檯創建並由SSE-C或SSE-KMS加密的對象具有不是其對象數據的MD5摘要的ETags。

如果通過分段上載或部分複製操作創建對象,則不論採用何種加密方法,ETag都不是MD5摘要。

2

根據這裏的答案,我編寫了一個Python實現,它可以正確計算多部分文件和單部分文件ETags。

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(md5s[0].hexdigest()) 

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

默認CHUNK_SIZE是由官方aws cli工具使用8 MB,並且它多用於上傳2+塊。它應該在Python 2和3下工作。

相關問題