86

Amazon S3にアップロードされた5GB未満のファイルには、ファイルのMD5ハッシュであるETagが含まれているため、ローカルファイルがS3に配置したものと同じであるかどうかを簡単に確認できます。

ただし、ファイルが5GBより大きい場合、AmazonはETagの計算方法を変えます。

たとえば、5,970,150,664バイトのファイルを380個に分けてマルチパートアップロードしました。これで、S3はETagが。であることを示します6bcf86bed8807b8e78f0fc6e0a53079d-380。私のローカルファイルのmd5ハッシュは702242d3703818ddefe6bf7da2bed757です。ダッシュの後の数字は、マルチパートアップロードのパーツの数だと思います。

また、新しいETag(ダッシュの前)はまだMD5ハッシュであると思われますが、マルチパートアップロードの途中で何らかのメタデータが含まれています。

Amazon S3と同じアルゴリズムを使用してETagを計算する方法を知っている人はいますか?

4

19 に答える 19

98

サーバー側の暗号化なしで14MBのファイルをバケットにアップロードし、パーツサイズが5MBであるとします。各部分に対応する3つのMD5チェックサム、つまり最初の5MB、2番目の5MB、および最後の4MBのチェックサムを計算します。次に、それらの連結のチェックサムを取ります。MD5チェックサムは、バイナリデータの16進表現として出力されることが多いため、ASCIIまたはUTF-8でエンコードされた連結ではなく、デコードされたバイナリ連結のMD5を使用するようにしてください。それが終わったら、ETagを取得するためにハイフンとパーツの数を追加します。

MacOSXでコンソールから実行するコマンドは次のとおりです。

$ 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ます。それらを連結して16進数をデコードし、ロットのMD5チェックサムを取得するには、次を使用します。

$ xxd -r -p checksums.txt | md5

そして、3つの部分があったので、ETagを取得するために「-3」を追加します。

ノート

  • 経由でaws-cliを使用してアップロードした場合aws s3 cp、チャンクサイズは8MBである可能性があります。ドキュメントによると、これがデフォルトです。
  • バケットでサーバー側暗号化(SSE)がオンになっている場合、ETagはMD5チェックサムにはなりません(APIドキュメントを参照)。ただし、アップロードされたパーツが送信したものと一致することを確認しようとしている場合は、Content-MD5ヘッダーを使用すると、S3がそれを比較します
  • md5macOSではチェックサムを書き出すだけですがmd5sum、Linux/brewではファイル名も出力します。それを取り除く必要がありますが、チェックサムのみを出力するオプションがあると確信しています。あなたは空白を心配する必要はありません原因xxdはそれを無視します。

コードリンク

  • macOS用の動作するスクリプトで書いた要点。
  • s3md5のプロジェクト。
于 2013-11-10T23:59:44.337 に答える
24

ここでの回答に基づいて、マルチパートファイルとシングルパートファイルの両方のETagを正しく計算するPython実装を作成しました。

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は公式ツールで使用される8MBで、2つ以上のチャンクに対してマルチパートアップロードを行います。Python2と3の両方で動作するはずです。

于 2017-05-06T10:17:36.820 に答える
12

bashの実装

Pythonの実装

アルゴリズムは文字通り(Python実装のreadmeからコピーされた):

  1. md5チャンク
  2. md5文字列を一緒にグロブします
  3. グロブをバイナリに変換する
  4. md5グロブされたチャンクのバイナリmd5s
  5. バイナリのmd5文字列の最後に「-Number_of_chunks」を追加します
于 2015-10-08T23:17:07.650 に答える
9

それが役立つかどうかわからない:

現在、バケット内のファイルに変更を適用することで構成される、マルチパートアップロードファイル内の誤ったETagを修正するために、醜い(しかしこれまでのところ便利な)ハッキングを行っています。これにより、Amazonからのmd5再計算がトリガーされ、ETagが実際のmd5署名と一致するように変更されます。

私たちの場合には:

ファイル:bucket / Foo.mpg.gpg

  1. 取得したETag:「3f92dffef0a11d175e60fb8b958b4e6e-2」
  2. ファイルを使って何かをします(名前を変更したり、偽のヘッダーなどのメタデータを追加したりします)
  3. 取得したEtag: "c1d903ca1bb6dc68778ef21e74cc15b0"

アルゴリズムはわかりませんが、ETagを「修正」できるので、心配する必要もありません。

于 2013-09-26T16:19:10.470 に答える
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();
}
于 2014-10-01T07:13:44.180 に答える
6

このクレイジーなAWSチャレンジパズルのもう1つのピースがあります。

FWIW、この回答は、「MD5パーツのMD5」を計算する方法をすでに理解しており、ここですでに提供されている他のすべての回答からAWSマルチパートETagを再構築できることを前提としています。

この回答が対処するのは、元のアップロードパーツのサイズを「推測」または「分割」しなければならないという煩わしさです。

S3へのアップロードにはいくつかの異なるツールを使用していますが、それらはすべてアップロードパーツのサイズが異なるように見えるため、「推測」は実際にはオプションではありませんでした。また、パーツサイズが異なっているように見えたときに歴史的にアップロードされたファイルがたくさんあります。また、内部サーバーコピーを使用してMD5タイプのETagを強制的に作成するという古いトリックも、AWSが内部サーバーコピーをマルチパート(かなり大きなパーツサイズ)も使用するように変更したため、機能しなくなりました。

では...オブジェクトのパーツサイズをどのように把握できますか?

最初にhead_objectリクエストを作成し、ETagがマルチパートタイプのETag(最後に'-<partcount>'を含む)であることを検出した場合、別のhead_objectリクエストを作成できますが、part_number属性は次のようになります。 1(最初の部分)。この後続のhead_objectリクエストは、最初の部分のcontent_lengthを返します。Viola ...使用されたパーツサイズがわかったので、そのサイズを使用して、オブジェクトがアップロードされたときに作成された元のアップロードされたS3ETagと一致するローカルETagを再作成できます。

さらに、正確にしたい場合(おそらく、一部のマルチパートアップロードでは可変パーツサイズを使用する必要があります)、各part_numberを指定してhead_objectリクエストを呼び出し、返されたパーツcontent_lengthから各パーツのMD5を計算し続けることができます。

お役に立てば幸いです...

于 2020-08-05T18:25:33.927 に答える
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)にアクセスできる方法でデータを入力する方法です。誰かがメタデータなしでファイルをアップロードした場合でも失敗します。

于 2015-09-19T04:36:37.763 に答える
5

AWSのドキュメントによると、ETagはマルチパートアップロードまたは暗号化されたオブジェクトのMD5ハッシュではありません:http://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html

PUT Object、POST Object、Copy操作、またはAWS Management Consoleを介して作成され、SSE-S3またはプレーンテキストによって暗号化されたオブジェクトには、オブジェクトデータのMD5ダイジェストであるETagがあります。

PUTオブジェクト、POSTオブジェクト、コピー操作、またはAWS管理コンソールを介して作成され、SSE-CまたはSSE-KMSによって暗号化されたオブジェクトには、オブジェクトデータのMD5ダイジェストではないETagがあります。

オブジェクトがマルチパートアップロードまたはパートコピー操作のいずれかによって作成された場合、暗号化の方法に関係なく、ETagはMD5ダイジェストではありません。

于 2017-05-06T05:33:43.130 に答える
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のShortestHex2BinとS3へのマルチパートアップロードに感謝します...

于 2016-11-12T14:34:21.063 に答える
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;
        }
    }
}
于 2016-03-17T21:47:15.273 に答える
1

簡単な答えは、各部分の128ビットバイナリmd5ダイジェストを取得し、それらをドキュメントに連結して、そのドキュメントをハッシュすることです。この回答で提示されているアルゴリズムは正確です。

注:ブロブを「タッチ」すると(コンテンツを変更しなくても)、ハイフン付きのマルチパートETAGフォームはハイフンなしのフォームに変わります。つまり、完成したマルチパートアップロードオブジェクト(別名PUT-COPY)をコピーするか、インプレースコピーを実行すると、S3は単純なバージョンのアルゴリズムを使用してETAGを再計算します。つまり、宛先オブジェクトにはハイフンなしのetagがあります。

おそらくこれについてはすでに検討しましたが、ファイルが5 GB未満で、MD5をすでに知っていて、アップロードの並列化にはほとんどまたはまったくメリットがありません(たとえば、低速のネットワークからアップロードをストリーミングしている場合や、低速のディスクからアップロードしている場合)。 )、マルチパートPUTの代わりに単純なPUTを使用することを検討し、既知のContent-MD5をリクエストヘッダーに渡すこともできます。一致しない場合、amazonはアップロードに失敗します。UploadPartごとに課金されることに注意してください。

さらに、一部のクライアントでは、PUT操作の入力に既知のMD5を渡すことで、転送中にクライアントがMD5を再計算する必要がなくなります。boto3(python)では、たとえば、 client.put_object()ContentMD5メソッドのパラメーターを使用します。パラメータを省略し、MD5をすでに知っている場合、クライアントは転送前に再度計算するサイクルを浪費することになります。

于 2018-11-18T05:21:53.560 に答える
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}`;
};

于 2019-05-30T16:38:00.977 に答える
1

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

于 2019-12-22T18:33:51.810 に答える
0

ddやxxdなどの外部ヘルパーを使用せずにiOSとmacOS用のソリューションがあります。見つけたばかりなので、そのまま報告し、後日改善する予定です。今のところ、Objective-CとSwiftの両方のコードに依存しています。まず、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

次に、プレーンな迅速なファイルを作成します。

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

}
}

ここで追加します:

#import "AWS3MD5Hash.h"

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を渡してAWSS3MD5Hashを計算します。問題が発生した場合はコメントしてください。私は迅速な解決策に取り組んでいます。終了したらすぐにこの回答を更新します。ありがとう

于 2019-04-08T06:25:51.793 に答える
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)

パーツは丸める必要があります

于 2020-11-12T12:09:01.980 に答える
0

AWS S3コンソールの「アップロード」が17,179,870の異常なパーツ(チャンク)サイズを使用していることを確認しました-少なくとも大きなファイルの場合。

そのパーツサイズを使用すると、前述の方法を使用して正しいETagハッシュが得られました。phpバージョンの@TheStoryCoderに感謝します。

各パーツの実際のサイズを確認するためにhead-objectを使用するという彼のアイデアを提供してくれた@hansに感謝します。

AWS S3コンソール(2020年11月28日)を使用して、190MBから2.3GBまでのサイズの約50個のファイルをアップロードしましたが、それらはすべて同じパーツサイズの17,179,870でした。

于 2020-11-29T18:23:45.797 に答える
0

Node.js(TypeScript)に実装された作業アルゴリズム。

/**
 * Generate an S3 ETAG for multipart uploads in Node.js 
 * An implementation of this algorithm: https://stackoverflow.com/a/19896823/492325
 * Author: Richard Willis <willis.rh@gmail.com>
 */
import fs from 'node:fs';
import crypto, { BinaryLike } from 'node:crypto';

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

function md5(contents: string | BinaryLike): string {
  return crypto.createHash('md5').update(contents).digest('hex');
}

export function getS3Etag(
  filePath: string,
  partSizeInBytes = defaultPartSizeInBytes
): string {
  const { size: fileSizeInBytes } = fs.statSync(filePath);
  let parts = Math.floor(fileSizeInBytes / partSizeInBytes);
  if (fileSizeInBytes % partSizeInBytes > 0) {
    parts += 1;
  }
  const fileDescriptor = fs.openSync(filePath, 'r');
  let totalMd5 = '';

  for (let part = 0; part < parts; part++) {
    const skipBytes = partSizeInBytes * part;
    const totalBytesLeft = fileSizeInBytes - skipBytes;
    const bytesToRead = Math.min(totalBytesLeft, partSizeInBytes);
    const buffer = Buffer.alloc(bytesToRead);
    fs.readSync(fileDescriptor, buffer, 0, bytesToRead, skipBytes);
    totalMd5 += md5(buffer);
  }

  const combinedHash = md5(Buffer.from(totalMd5, 'hex'));
  const etag = `${combinedHash}-${parts}`;
  return etag;
}

これをnpmに公開しました

npm install s3-etag
import { generateETag } from 's3-etag';

const etag = generateETag(absoluteFilePath, partSizeInBytes);

ここでプロジェクトを表示:https ://github.com/badsyntax/s3-etag

于 2021-12-16T08:05:10.670 に答える
0

私は上記のエマーソンの主要な答え(特にそのxxd部分)が好きでしたが、私は怠惰すぎて使用できddなかったのでsplit、アップロードしたので8Mのチャンクサイズを推測しましたaws s3 cp

$ split -b 8M large.iso XXX
$ md5sum XXX* > checksums.txt
$ sed -i 's/ .*$//' checksums.txt 
$ xxd -r -p checksums.txt | md5sum
99a090df013d375783f0f0be89288529  -
$ wc -l checksums.txt 
80 checksums.txt
$ 

S3etagの両方の部分がファイルの計算されたetagと一致していることがすぐにわかりました。

アップデート:

これはうまく機能しています:

$ ll large.iso
-rw-rw-r-- 1 user   user   669134848 Apr 12  2021 large.iso
$ 
$ etag large.iso
99a090df013d375783f0f0be89288529-80
$ 
$ type etag
etag is a function
etag () 
{ 
    split -b 8M --filter=md5sum $1 | cut -d' ' -f1 | pee "xxd -r -p | md5sum | cut -d' ' -f1" "wc -l" | paste -d'-' - -
}
$ 
于 2022-02-15T08:07:45.553 に答える
-4

いいえ、

これまで、通常のファイルETagとマルチパートファイルETagおよびローカルファイルのMD5を一致させるソリューションはありません。

于 2012-08-31T06:11:14.787 に答える