Определение Etag изменилось в Amazon S3 - PullRequest
49 голосов
/ 06 июля 2011

Некоторое время я немного использовал Amazon S3 для резервного копирования.Обычно после загрузки файла я проверяю совпадения сумм MD5, чтобы убедиться, что я сделал хорошую резервную копию.S3 имеет заголовок "etag", который использовался для получения этой суммы.

Однако, когда я недавно загрузил большой файл, Etag больше не кажется суммой md5.Он содержит дополнительные цифры и дефис "696df35ad1161afbeb6ea667e5dd5dab-2861".Я не могу найти документацию об этом изменении.Я проверил, используя консоль управления S3 и Cyberduck.

Я не могу найти документацию об этом изменении.Есть указатели?

Ответы [ 15 ]

36 голосов
/ 06 июля 2011

Если какой-либо файл загружается с помощью multipart, вы всегда получите такой тип ETag. Но если вы загрузите весь файл как один файл, вы получите ETag, как и раньше.

Bucket Explorer, обеспечивающий нормальный ETag до 5 Гб загрузки в многочастной операции. Но больше тогда это не обеспечивает.

AWS:

ETag для объекта, созданного с использованием API составной загрузки, будет содержать один или несколько не шестнадцатеричных символов и / или состоять из менее 16 или более 16 шестнадцатеричных цифр.

Ссылка: https://forums.aws.amazon.com/thread.jspa?messageID=203510#203510

28 голосов
/ 10 октября 2013

Amazon S3 вычисляет Etag с помощью другого алгоритма (не MD5 Sum, как обычно) при загрузке файла с использованием multipart.

Этот алгоритм подробно описан здесь: http://permalink.gmane.org/gmane.comp.file-systems.s3.s3tools/583

"Рассчитать хеш MD5 для каждой загруженной части файла, объединить хэши в одну двоичную строку и вычислить MD5 хеш этого результата. "

Я просто разработал инструмент для вычисления в bash, s3md5: https://github.com/Teachnova/s3md5

Например, для вычисления Etag файла foo.bin , который был загружен с использованием multipart с размером фрагмента 15 МБ, затем

# s3md5 15 foo.bin

Теперь вы можете проверить целостность очень большого файла (больше 5 ГБ), потому что вы можете вычислить Etag локального файла и сравнить его с S3 Etag.

20 голосов
/ 05 марта 2015

Также в питоне ...

# Max size in bytes before uploading in parts. 
AWS_UPLOAD_MAX_SIZE = 20 * 1024 * 1024
# Size of parts when uploading in parts
AWS_UPLOAD_PART_SIZE = 6 * 1024 * 1024

#
# Function : md5sum
# Purpose : Get the md5 hash of a file stored in S3
# Returns : Returns the md5 hash that will match the ETag in S3
def md5sum(sourcePath):

    filesize = os.path.getsize(sourcePath)
    hash = hashlib.md5()

    if filesize > AWS_UPLOAD_MAX_SIZE:

        block_count = 0
        md5string = ""
        with open(sourcePath, "rb") as f:
            for block in iter(lambda: f.read(AWS_UPLOAD_PART_SIZE), ""):
                hash = hashlib.md5()
                hash.update(block)
                md5string = md5string + binascii.unhexlify(hash.hexdigest())
                block_count += 1

        hash = hashlib.md5()
        hash.update(md5string)
        return hash.hexdigest() + "-" + str(block_count)

    else:
        with open(sourcePath, "rb") as f:
            for block in iter(lambda: f.read(AWS_UPLOAD_PART_SIZE), ""):
                hash.update(block)
        return hash.hexdigest()
6 голосов
/ 27 июня 2015

Вот пример на Go:

func GetEtag(path string, partSizeMb int) string {
    partSize := partSizeMb * 1024 * 1024
    content, _ := ioutil.ReadFile(path)
    size := len(content)
    contentToHash := content
    parts := 0

    if size > partSize {
        pos := 0
        contentToHash = make([]byte, 0)
        for size > pos {
            endpos := pos + partSize
            if endpos >= size {
                endpos = size
            }
            hash := md5.Sum(content[pos:endpos])
            contentToHash = append(contentToHash, hash[:]...)
            pos += partSize
            parts += 1
        }
    }

    hash := md5.Sum(contentToHash)
    etag := fmt.Sprintf("%x", hash)
    if parts > 0 {
        etag += fmt.Sprintf("-%d", parts)
    }
    return etag
}

Это всего лишь пример, вы должны обрабатывать ошибки и прочее

6 голосов
/ 06 марта 2014

Вот функция powershell для расчета Amazon ETag для файла:

$blocksize = (1024*1024*5)
$startblocks = (1024*1024*16)
function AmazonEtagHashForFile($filename) {
    $lines = 0
    [byte[]] $binHash = @()

    $md5 = [Security.Cryptography.HashAlgorithm]::Create("MD5")
    $reader = [System.IO.File]::Open($filename,"OPEN","READ")

    if ((Get-Item $filename).length -gt $startblocks) {
        $buf = new-object byte[] $blocksize
        while (($read_len = $reader.Read($buf,0,$buf.length)) -ne 0){
            $lines   += 1
            $binHash += $md5.ComputeHash($buf,0,$read_len)
        }
        $binHash=$md5.ComputeHash( $binHash )
    }
    else {
        $lines   = 1
        $binHash += $md5.ComputeHash($reader)
    }

    $reader.Close()

    $hash = [System.BitConverter]::ToString( $binHash )
    $hash = $hash.Replace("-","").ToLower()

    if ($lines -gt 1) {
        $hash = $hash + "-$lines"
    }

    return $hash
}
3 голосов
/ 17 июня 2015

Копирование на s3 с помощью aws s3 cp может использовать многоэтапную загрузку, и итоговый etag не будет md5, как написали другие.

Чтобы загрузить файлы без составных частей, используйте команду нижнего уровня put-object.

aws s3api put-object --bucket bucketname --key remote/file --body local/file
3 голосов
/ 30 марта 2015

Если вы используете многоэтапную загрузку, «etag» не является суммой данных MD5 (см. Каков алгоритм для вычисления Etag Amazon-S3 для файла размером более 5 ГБ? ).Этот случай можно идентифицировать по etag, содержащему тире "-".

Теперь интересный вопрос состоит в том, как получить действительную сумму данных MD5 без загрузки ?Один простой способ - просто «скопировать» объект на себя, для этого не требуется загрузка:

s3cmd cp s3://bucket/key s3://bucket/key

Это заставит S3 пересчитать сумму MD5 и сохранить ее как «etag»только что скопированного объекта.Команда «copy» выполняется непосредственно на S3, т. Е. Никакие объектные данные не передаются на / с S3, поэтому это требует небольшой полосы пропускания!(Примечание: не используйте s3cmd mv; это приведет к удалению ваших данных.)

Основная команда REST:

PUT /key HTTP/1.1
Host: bucket.s3.amazonaws.com
x-amz-copy-source: /buckey/key
x-amz-metadata-directive: COPY
2 голосов
/ 25 марта 2018

Эта страница поддержки AWS - Как обеспечить целостность данных объектов, загруженных или загруженных из Amazon S3? - описывает более надежный способ проверки целостности ваших резервных копий s3.

Сначала определите в кодировке Base64 md5sum файла, который вы хотите загрузить:

$ md5_sum_base64="$( openssl md5 -binary my-file | base64 )"

Затем используйте s3api для загрузки файла:

$ aws s3api put-object --bucket my-bucket --key my-file --body my-file --content-md5 "$md5_sum_base64"

Обратите внимание на использование --content-md5флаг, справка для этого флага гласит:

--content-md5  (string)  The  base64-encoded  128-bit MD5 digest of the part data.

Это мало что говорит о , почему использовать этот флаг, но мы можем найти эту информацию в документации API дляput object :

Чтобы данные не были повреждены при прохождении через сеть, используйте заголовок Content-MD5.Когда вы используете этот заголовок, Amazon S3 проверяет объект по предоставленному значению MD5 и, если они не совпадают, возвращает ошибку.Кроме того, вы можете вычислить MD5 при помещении объекта в Amazon S3 и сравнить возвращенный ETag с вычисленным значением MD5.

При использовании этого флага S3 проверяет, что хеш файла на стороне сервера соответствует указанному значению,Если совпадение хэшей s3 вернет ETag:

{
    "ETag": "\"599393a2c526c680119d84155d90f1e5\""
}

Значение ETag обычно будет шестнадцатеричным значением md5sum (см. этот вопрос для некоторых сценариев, где это может быть не так).

Если хэш не соответствует указанному вами, вы получаете сообщение об ошибке.

A client error (InvalidDigest) occurred when calling the PutObject operation: The Content-MD5 you specified was invalid.

В дополнение к этому вы также можете добавить файл md5sum к метаданным файла в качестве дополнительной проверки:

$ aws s3api put-object --bucket my-bucket --key my-file --body my-file --content-md5 "$md5_sum_base64" --metadata md5chksum="$md5_sum_base64"

После загрузки вы можете выполнить команду head-object для проверки значений.

$ aws s3api head-object --bucket my-bucket --key my-file
{
    "AcceptRanges": "bytes",
    "ContentType": "binary/octet-stream",
    "LastModified": "Thu, 31 Mar 2016 16:37:18 GMT",
    "ContentLength": 605,
    "ETag": "\"599393a2c526c680119d84155d90f1e5\"",
    "Metadata": {    
        "md5chksum": "WZOTosUmxoARnYQVXZDx5Q=="    
    }    
}

Вот скрипт bash, который использует контент md5 и добавляет метаданные, а затем проверяет, чтозначения, возвращаемые S3, соответствуют локальным хешам:

#!/bin/bash

set -euf -o pipefail

# assumes you have aws cli, jq installed

# change these if required
tmp_dir="$HOME/tmp"
s3_dir="foo"
s3_bucket="stack-overflow-example"
aws_region="ap-southeast-2"
aws_profile="my-profile"

test_dir="$tmp_dir/s3-md5sum-test"
file_name="MailHog_linux_amd64"
test_file_url="https://github.com/mailhog/MailHog/releases/download/v1.0.0/MailHog_linux_amd64"
s3_key="$s3_dir/$file_name"
return_dir="$( pwd )"

cd "$tmp_dir" || exit
mkdir "$test_dir"
cd "$test_dir" || exit

wget "$test_file_url"

md5_sum_hex="$( md5sum $file_name | awk '{ print $1 }' )"
md5_sum_base64="$( openssl md5 -binary $file_name | base64 )"

echo "$file_name hex    = $md5_sum_hex"
echo "$file_name base64 = $md5_sum_base64"

echo "Uploading $file_name to s3://$s3_bucket/$s3_dir/$file_name"
aws \
--profile "$aws_profile" \
--region "$aws_region" \
s3api put-object \
--bucket "$s3_bucket" \
--key "$s3_key" \
--body "$file_name" \
--metadata md5chksum="$md5_sum_base64" \
--content-md5 "$md5_sum_base64"

echo "Verifying sums match"

s3_md5_sum_hex=$( aws --profile "$aws_profile"  --region "$aws_region" s3api head-object --bucket "$s3_bucket" --key "$s3_key" | jq -r '.ETag' | sed 's/"//'g )
s3_md5_sum_base64=$( aws --profile "$aws_profile"  --region "$aws_region" s3api head-object --bucket "$s3_bucket" --key "$s3_key" | jq -r '.Metadata.md5chksum' )

if [ "$md5_sum_hex" == "$s3_md5_sum_hex" ] && [ "$md5_sum_base64" == "$s3_md5_sum_base64" ]; then
    echo "checksums match"
else
    echo "something is wrong checksums do not match:"

    cat <<EOM | column -t -s ' '
$file_name file hex:    $md5_sum_hex    s3 hex:    $s3_md5_sum_hex
$file_name file base64: $md5_sum_base64 s3 base64: $s3_md5_sum_base64
EOM

fi

echo "Cleaning up"
cd "$return_dir"
rm -rf "$test_dir"
aws \
--profile "$aws_profile" \
--region "$aws_region" \
s3api delete-object \
--bucket "$s3_bucket" \
--key "$s3_key"
2 голосов
/ 06 мая 2017

Основываясь на ответах, я написал реализацию 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(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 по умолчанию составляет 8 МБ, используемый официальным инструментом aws cli, и он выполняет многоэтапную загрузку для 2+ блоков. Должно работать под Python 2 и 3.

1 голос
/ 13 марта 2017

Чтобы сделать один шаг за пределы вопроса ОП. Скорее всего, эти кусочки ETag усложняют вашу жизнь, пытаясь сравнить их на стороне клиента.

Если вы публикуете свои артефакты на S3 с помощью *Для команд 1003 * (cp, sync и т. Д.) Пороговое значение по умолчанию, при котором, как представляется, используется многоэтапная загрузка, составляет 10 МБ.Последние версии awscli позволяют настроить этот порог, поэтому вы можете отключить multipart и получить простой в использовании MD5 ETag:

aws configure set default.s3.multipart_threshold 64MB

Полная документация здесь: http://docs.aws.amazon.com/cli/latest/topic/s3-config.html

Последствиеиз этого может быть понижена производительность загрузки (я, честно говоря, не заметил).Но в результате все файлы, размер которых меньше настроенного вами порогового значения, теперь будут иметь обычные хэш-теги MD5, что значительно упрощает их деление на стороне клиента.

Для этого требуется несколько более поздняя установка awscli.Моя предыдущая версия (1.2.9) не поддерживала эту опцию, поэтому мне пришлось обновить ее до 1.10.x.

Мне удалось успешно установить порог до 1024 МБ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...