Jaki jest algorytm obliczania Etag Amazon-S3 dla pliku większego niż 5GB?

Pliki przesłane do Amazon S3, które są mniejsze niż 5 GB, mają ETag, który jest po prostu skrótem MD5 Pliku, co ułatwia sprawdzenie, czy Twoje lokalne pliki są takie same, jak te, które umieściłeś na S3.

Ale jeśli Twój plik jest większy niż 5GB, Amazon oblicza ETag inaczej.

Na przykład, zrobiłem wieloczęściowy upload pliku 5,970,150,664 bajtów w 380 częściach. Teraz S3 pokazuje, że ma ETag 6bcf86bed8807b8e78f0fc6e0a53079d-380. Mój plik lokalny ma hash md5 702242d3703818ddefe6bf7da2bed757. Myślę, że Liczba po dash jest liczbą części w przesyłaniu wieloczęściowym.

Podejrzewam też, że nowy ETag (przed Dashem) to nadal hash MD5, ale z pewnymi metadanymi dołączonymi po drodze z wieloczęściowego uploadu.

Czy ktoś wie jak obliczyć ETag używając tego samego algorytmu co Amazon S3?

Author: Tripp Kinetics, 2012-08-29

17 answers

Załóżmy, że przesłałeś plik 14MB do wiadra bez szyfrowania po stronie serwera, a twój rozmiar części to 5MB. Oblicz 3 sumy kontrolne MD5 odpowiadające każdej części, tj. sumę kontrolną pierwszego 5MB, drugiego 5MB i ostatniego 4MB. Następnie weź sumę kontrolną ich konkatenacji. Sumy kontrolne MD5 są często drukowane jako szesnastkowe reprezentacje danych binarnych, więc upewnij się, że bierzesz MD5 dekodowanej binarnej konkatenacji, a nie zakodowanej w ASCII lub UTF-8. Gdy to zrobisz, dodaj myślnik i liczbę części, aby uzyskać ETag.

Oto polecenia, aby to zrobić na Mac OS X z konsoli:

$ 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)

W tym momencie wszystkie sumy kontrolne są w checksums.txt. Aby je połączyć, zdekodować hex i uzyskać sumę kontrolną MD5 partii, użyj

$ xxd -r -p checksums.txt | md5

A teraz dodaj "-3", aby uzyskać ETag, ponieważ były 3 części.

Uwagi

  • Jeśli załadowałeś za pomocą aws-CLI przez aws s3 cp to najprawdopodobniej masz rozmiar 8MB. Według docs, to jest domyślne.
  • Jeśli bucket ma włączone szyfrowanie po stronie serwera (SSE), ETag nie będzie sumą kontrolną MD5 (zobacz Dokumentacja API ). Ale jeśli próbujesz tylko zweryfikować, czy przesłana część pasuje do tego, co wysłałeś, możesz użyć nagłówka Content-MD5, a S3 porówna ją dla Ciebie .
  • md5 na macOS tylko wypisuje sumę kontrolną, ale md5sum na Linuksie / brew również wypisuje nazwę pliku. Musisz to rozebrać, ale jestem pewien, że jest jakaś opcja, aby wypisać tylko sumy kontrolne. Nie musisz się martwić o spację, ponieważ xxd ją zignoruje.

Linki Kodowe

 90
Author: Emerson Farrugia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-01 18:18:28

Na podstawie odpowiedzi napisałem implementację Pythona, która poprawnie oblicza zarówno wieloczęściowe, jak i jednoczęściowe Etagi plików.

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))

Domyślnym rozmiarem chunk_size jest 8 MB używane przez oficjalne narzędzie aws cli. Powinien działać zarówno pod Pythonem 2 jak i 3.

 15
Author: hyperknot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-13 22:59:31

Implementacja Bash

Implementacja Pythona

Algorytm dosłownie jest (skopiowany z readme w implementacji Pythona):

  1. md5 kawałki
  2. glob ciągi md5 razem
  3. Konwertuj glob na binarny
  4. md5 binary of the globbed chunk md5s
  5. dołącza "- Number_of_chunks " do końca ciągu md5 binarnego
 12
Author: tlastowka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-09 00:30:56

Nie wiem czy to może pomóc:

Obecnie wykonujemy brzydki (ale jak na razie przydatny) hack, Aby naprawić Te błędne Etagi w przesyłanych plikach wieloczęściowych, co polega na zastosowaniu zmiany w pliku w wiadrze; która uruchamia przeliczanie md5 Z Amazon, które zmienia ETag na dopasowany do rzeczywistej sygnatury md5.

W naszym przypadku:

Plik: wiadro / Foo.mpg.gpg

  1. ETag: "3f92dffef0a11d175e60fb8b958b4e6e-2"
  2. Do coś z plikiem (zmień jego nazwę , Dodaj m.in. meta-dane, takie jak fałszywy nagłówek)
  3. Etag: "c1d903ca1bb6dc68778ef21e74cc15b0"

Nie znamy algorytmu, ale ponieważ możemy "naprawić" ETag, nie musimy się o niego martwić.

 9
Author: juanjocv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-26 16:19:10

Ten sam algorytm, wersja java: (BaseEncoding, Hasher, Hashing, etc pochodzi z 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();
}
 9
Author: petertc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-10 09:22:35

W powyższej odpowiedzi ktoś zapytał, czy istnieje sposób na uzyskanie md5 dla plików większych niż 5G.

Odpowiedzią, którą mógłbym dać za uzyskanie wartości MD5 (dla plików większych niż 5G), byłoby ręczne dodanie jej do metadanych lub użycie programu do przesyłania, który doda informacje.

Na przykład, użyłem s3cmd, aby przesłać plik, i dodał następujące metadane.

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

Nie jest to bezpośrednie rozwiązanie przy użyciu ETag, ale jest to sposób na zaludnienie metadane, które chcesz (MD5) w taki sposób, aby uzyskać do nich dostęp. Nadal nie powiedzie się, jeśli ktoś wyśle plik bez metadanych.

 5
Author: Cinderhaze,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-19 04:36:37

Zgodnie z dokumentacją AWS ETag nie jest skrótem MD5 dla wieloczęściowego uploadu ani dla zaszyfrowanego obiektu: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html

Obiekty utworzone przez operację PUT Object, POST Object lub Copy, lub za pośrednictwem konsoli zarządzania AWS i zaszyfrowane przez SSE-S3 lub zwykły tekst, mają znaczniki ETAG, które są skrótem MD5 ich danych obiektowych.

Obiekty utworzone przez obiekt PUT, POST lub Copy operacje, lub za pośrednictwem konsoli zarządzania AWS i są szyfrowane przez SSE-C lub SSE-KMS, mają znaczniki ETAG, które nie są skrótem MD5 ich danych obiektowych.

Jeśli obiekt jest tworzony przez operację przesyłania wieloczęściowego lub kopiowania części, ETag nie jest skrótem MD5, niezależnie od metody szyfrowania.

 5
Author: Timothy Gonzalez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Oto algorytm w Rubim...

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

Dziękinajkrótszemu Hex2Bin w Ruby i Wieloczęściowemu wgrywaniu do S3 ...

 3
Author: vince,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-23 17:09:42

A oto wersja PHP obliczania ETag:

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);

A oto ulepszona wersja, która może zweryfikować oczekiwany ETag - a nawet odgadnąć rozmiar chunksize, jeśli go nie znasz!

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
Author: TheStoryCoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-22 14:14:04

Oto kolejny element tej szalonej łamigłówki AWS challenge.

FWIW, ta odpowiedź zakłada, że już wiesz, jak obliczyć "MD5 części MD5" i możesz przebudować swój wieloczęściowy ETAG AWS ze wszystkich innych odpowiedzi już podanych tutaj.

Co ta odpowiedź odnosi się do irytacji konieczności "odgadnięcia" lub w inny sposób "boskiego" oryginalnego rozmiaru części przesłanej.

Używamy kilku różnych narzędzi do przesyłania do S3 i wszystkie wydają się mieć inny upload rozmiary części, więc "zgadywanie" naprawdę nie wchodziło w grę. Ponadto mamy wiele plików, które były historycznie przesyłane, gdy rozmiary części wydawały się różne. Również stary trik użycia wewnętrznej kopii serwera, aby wymusić utworzenie ETAG typu MD5, również nie działa, ponieważ AWS zmienił swoje wewnętrzne kopie serwera, aby również używać wieloczęściowych (tylko z dość dużym rozmiarem części).

Więc... Jak możesz określić rozmiar części obiektu?

Cóż, jeśli najpierw złożysz żądanie head_object i wykryj, że ETag jest wieloczęściowym typem ETag (zawiera '- ' na końcu), wtedy możesz wykonać kolejne żądanie head_object, ale z dodatkowym atrybutem part_number 1 (Pierwsza część). To kolejne żądanie head_object zwróci ci content_length pierwszej części. Viola... Teraz znasz rozmiar części, który został użyty i możesz go użyć do odtworzenia lokalnego ETag, który powinien pasować do oryginalnego przesłanego ETag S3 utworzonego, gdy obiekt był uploaded.

DODATKOWO, jeśli chcesz być dokładny (być może niektóre przesyłanie wieloczęściowe miało używać zmiennych rozmiarów części), możesz nadal wywoływać żądania head_object z każdym podanym numerem części i obliczać MD5 każdej części z zwróconych części content_length.

Mam nadzieję, że to pomoże...

 2
Author: Hans,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-05 18:31:14

Krótka odpowiedź jest taka, że bierzesz 128-bitowy kod binarny md5 każdej części, łączysz je w dokument i mieszasz ten dokument. Algorytm przedstawiony w Ta odpowiedź {[3] } jest dokładna.

Uwaga: wieloczęściowy formularz ETAG z myślnikiem zmieni się na Formularz bez myślnika, jeśli "dotkniesz" obiektu blob (nawet bez modyfikowania zawartości). Oznacza to, że jeśli skopiujesz lub wykonasz kopię na miejscu ukończonego obiektu wieloczęściowego (aka put-COPY), S3 będzie Oblicz ETAG za pomocą prostej wersji algorytmu. tzn. obiekt docelowy będzie miał etag bez myślnika.

Prawdopodobnie już to rozważałeś, ale jeśli Twoje pliki mają mniej niż 5 GB i znasz już ich MD5, a równoległe przesyłanie nie przynosi żadnych korzyści (np. przesyłaj strumieniowo przesyłanie z wolnej sieci lub przesyłanie z wolnego dysku), możesz również rozważyć użycie prostego PUT zamiast wieloczęściowego PUT i przekazać znane Ci pliki. Content-MD5 w nagłówkach żądań -- amazon nie powiedzie się przesyłanie, jeśli nie pasują. Należy pamiętać, że pobierane są opłaty za każdy UploadPart.

Ponadto, w niektórych klientach podanie znanego MD5 na wejście operacji PUT uchroni klienta przed ponownym obliczeniem MD5 podczas transferu. W boto3 (python) użyjesz parametru ContentMD5 klienta .na przykład metoda put_object(). Jeśli pominiesz parametr, a znasz już MD5, wówczas klient będzie marnowanie cykli na ponowne obliczanie go przed transferem.

 1
Author: init_js,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-17 00:10:53

Węzeł.implementacja 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
Author: Elad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-23 19:46:22

Mam rozwiązanie dla iOS i macOS bez korzystania z zewnętrznych helperów, takich jak dd i xxd. Właśnie go znalazłem, więc zgłaszam go w takim stanie, w jakim jest, planując ulepszyć go na późniejszym etapie. Na razie opiera się zarówno na kodzie Objective-C, jak i Swift. Po pierwsze, utwórz tę klasę pomocniczą w Objective-C:

AWS3MD5Hash.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface AWS3MD5Hash : NSObject

- (NSData *)dataFromFile:(FILE *)theFile startingOnByte:(UInt64)startByte length:(UInt64)length filePath:(NSString *)path singlePartSize:(NSUInteger)partSizeInMb;

- (NSData *)dataFromBigData:(NSData *)theData startingOnByte:(UInt64)startByte length:(UInt64)length;

- (NSData *)dataFromHexString:(NSString *)sourceString;

@end

NS_ASSUME_NONNULL_END

AWS3MD5Hash.m

#import "AWS3MD5Hash.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE 256

@implementation AWS3MD5Hash


- (NSData *)dataFromFile:(FILE *)theFile startingOnByte:(UInt64)startByte length:(UInt64)length filePath:(NSString *)path singlePartSize:(NSUInteger)partSizeInMb {


   char *buffer = malloc(length);


   NSURL *fileURL = [NSURL fileURLWithPath:path];
   NSNumber *fileSizeValue = nil;
   NSError *fileSizeError = nil;
   [fileURL getResourceValue:&fileSizeValue
                           forKey:NSURLFileSizeKey
                            error:&fileSizeError];

   NSInteger __unused result = fseek(theFile,startByte,SEEK_SET);

   if (result != 0) {
      free(buffer);
      return nil;
   }

   NSInteger result2 = fread(buffer, length, 1, theFile);

   NSUInteger difference = fileSizeValue.integerValue - startByte;

   NSData *toReturn;

   if (result2 == 0) {
       toReturn = [NSData dataWithBytes:buffer length:difference];
    } else {
       toReturn = [NSData dataWithBytes:buffer length:result2 * length];
    }

     free(buffer);

     return toReturn;
 }

 - (NSData *)dataFromBigData:(NSData *)theData startingOnByte:  (UInt64)startByte length:(UInt64)length {

   NSUInteger fileSizeValue = theData.length;
   NSData *subData;

   if (startByte + length > fileSizeValue) {
        subData = [theData subdataWithRange:NSMakeRange(startByte, fileSizeValue - startByte)];
    } else {
       subData = [theData subdataWithRange:NSMakeRange(startByte, length)];
    }

        return subData;
    }

- (NSData *)dataFromHexString:(NSString *)string {
    string = [string lowercaseString];
    NSMutableData *data= [NSMutableData new];
    unsigned char whole_byte;
    char byte_chars[3] = {'\0','\0','\0'};
    NSInteger i = 0;
    NSInteger length = string.length;
    while (i < length-1) {
       char c = [string characterAtIndex:i++];
       if (c < '0' || (c > '9' && c < 'a') || c > 'f')
           continue;
       byte_chars[0] = c;
       byte_chars[1] = [string characterAtIndex:i++];
       whole_byte = strtol(byte_chars, NULL, 16);
       [data appendBytes:&whole_byte length:1];
    }

        return data;
}


@end

Teraz Utwórz zwykły plik swift:

Rozszerzenia AWS.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

}
}

Teraz dodaj:

#import "AWS3MD5Hash.h"

Do nagłówka mostkowego Objective-C. Powinieneś być w porządku z tą konfiguracją.

Przykładowe użycie

Aby przetestować tę konfigurację, można wywołać następującą metodę wewnątrz obiektu, która jest odpowiedzialna za obsługę połączeń 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
    }



}

Jeśli ETag zwrócony przez serwer nie ma " - " na końcu ETag, po prostu przekaż 0 do calculateAWSS3MD5Hash. Proszę o komentarz, jeśli napotkasz jakiekolwiek problemy. Pracuję nad szybkim tylko rozwiązaniem, będę zaktualizuj tę odpowiedź, jak tylko skończę. Dzięki

 0
Author: Alfonso Tesauro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-04-12 00:59:49

Wersja w 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)
}

Zobacz repo z prostą implementacją: https://github.com/bn3t/calculate-etag/tree/master

 0
Author: bernardn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-14 01:14:45

Jeśli chodzi o rozmiar kawałka, zauważyłem, że zależy to od liczby części. Maksymalna liczba części wynosi 10000 jako dokumenty AWS.

Więc zaczynając od domyślnej wielkości 8MB i znając rozmiar pliku, Rozmiar kawałka i części można obliczyć w następujący sposób:

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

while flsz/chunk_size>10000:
  chunk_size*=2

parts=math.ceil(flsz/chunk_size)

Części muszą być zaokrąglone w górę

 0
Author: Salva.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-12 14:52:53

Właśnie zobaczyłem ,że konsola AWS S3 "upload" używa nietypowego rozmiaru części (fragmentu) 17,179,870 - przynajmniej dla większych plików.

Użycie tego rozmiaru części dało mi poprawny hash ETag przy użyciu metod opisanych wcześniej. Dzięki @TheStoryCoder dla wersji php.

Podziękowania dla @ hans za jego pomysł, aby użyć head-object, aby zobaczyć rzeczywiste rozmiary każdej części.

Użyłem konsoli AWS S3 (na Nov28 2020), aby przesłać około 50 plików o rozmiarze od 190mb do 2.3 GB i wszystkie z nich miał ten sam rozmiar części 17,179,870.

 0
Author: macq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-29 18:23:45

Nie,

Do tej pory nie ma rozwiązania pasującego do zwykłego pliku ETag oraz Pliku wieloczęściowego ETag i MD5 pliku lokalnego.

 -3
Author: Tej Kiran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-31 06:11:14