【问题标题】:How to get the md5sum of a file on Amazon's S3如何获取亚马逊 S3 上文件的 md5sum
【发布时间】:2010-12-19 01:11:09
【问题描述】:

如果我在 Amazon 的 S3 上有现有文件,那么无需下载文件即可获取其 md5sum 的最简单方法是什么?

【问题讨论】:

  • ETag 标头是 MD5,但不适用于多部分文件。以下是有关如何使用它的更多信息:stackoverflow.com/questions/6591047/…
  • 有没有办法在不检索整个对象并在本地计算的情况下在 S3 对象上计算 MD5?目前,没有一个答案真正解决了这个非常简单的问题,而是纯粹关注 ETag。大多数建议使用 ETag 的答案甚至承认它不是计算 MD5 的合适替代品。

标签: amazon-s3


【解决方案1】:

AWS 的 ETag 文档说:

实体标签是对象的哈希值。 ETag 仅反映对象内容的更改,而不反映其元数据。 ETag 可能是也可能不是对象数据的 MD5 摘要。是否是取决于对象的创建方式和加密方式,如下所述:

  • 由 PUT 对象、POST 对象或复制操作或通过 AWS 管理控制台创建并通过 SSE-S3 或明文加密的对象具有作为其对象数据的 MD5 摘要的 ETag。
  • 由 PUT 对象、POST 对象或复制操作或通过 AWS 管理控制台创建并由 SSE-C 或 SSE-KMS 加密的对象具有不是其对象数据的 MD5 摘要的 ETag。
  • 如果对象是通过多部分上传或部分复制操作创建的,则无论采用何种加密方法,ETag 都不是 MD5 摘要。

参考:http://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html

【讨论】:

    【解决方案2】:

    ETag 似乎不是用于分段上传的 MD5(根据 Gael Fraiteur 的评论)。在这些情况下,它包含一个减号和一个数字的后缀。但是,即使减号之前的位似乎也不是 MD5,即使它与 MD5 的长度相同。可能后缀是上传的部分数量?

    【讨论】:

    • 这个后缀似乎只在文件较大(大于 5GB)时出现。通过检查我拥有的几个大文件,后缀似乎代表了上传的部分数量。但是,第一部分的 md5 哈希值似乎与原始文件不同。在计算这个哈希时,亚马逊必须为每个部分折叠一些额外的数据。我想知道算法,以便检查我的一些文件。
    • 哈希算法在这里描述:stackoverflow.com/questions/6591047/…
    • @broc.seib 我看到文件的后缀要小得多,例如 18.3MB。我想知道这是否取决于上传文件的内容;我正在使用aws s3 cp ...
    • @Mark 这里发布的答案有更多细节:stackoverflow.com/a/19896823/516910
    【解决方案3】:

    这是一个非常古老的问题,但我很难找到下面的信息,这是我能找到的第一批地方之一,所以我想详细说明一下,以防有人需要。

    ETag 是一个 MD5。但是对于 Multipart 上传的文件,MD5 是从每个上传的部分的 MD5 的串联中计算出来的。 所以你不需要在服务器中计算 MD5。只需获取 ETag 即可。

    正如@EmersonFarrugia 在this 中所说的回答:

    假设您上传了一个 14MB 的文件,而您的部分大小为 5MB。计算每个部分对应的3个MD5校验和,即前5MB、后5MB、后4MB的校验和。然后取它们连接的校验和。由于 MD5 校验和是二进制数据的十六进制表示,只需确保您采用解码后的二进制连接的 MD5,而不是 ASCII 或 UTF-8 编码的连接。完成后,添加连字符和零件数以获取 ETag。

    因此,您唯一需要的其他东西就是 ETag 和上传部分大小。但是 ETag 有一个 -NumberOfParts 后缀。因此,您可以将尺寸除以后缀并获得零件尺寸。 5Mb 是最小部件大小和默认值。零件尺寸必须是整数,因此您不能得到每个零件尺寸 7,25Mb 之类的东西。所以应该很容易获得零件尺寸信息。

    这是一个在 osx 中制作的脚本,在 cmets 中有一个 Linux 版本:https://gist.github.com/emersonf/7413337

    我会把这两个脚本都留在这里,以防以后无法访问上面的页面:

    Linux 版本:

    #!/bin/bash
    set -euo pipefail
    if [ $# -ne 2 ]; then
        echo "Usage: $0 file partSizeInMb";
        exit 0;
    fi
    file=$1
    if [ ! -f "$file" ]; then
        echo "Error: $file not found." 
        exit 1;
    fi
    partSizeInMb=$2
    fileSizeInMb=$(du -m "$file" | cut -f 1)
    parts=$((fileSizeInMb / partSizeInMb))
    if [[ $((fileSizeInMb % partSizeInMb)) -gt 0 ]]; then
        parts=$((parts + 1));
    fi
    checksumFile=$(mktemp -t s3md5.XXXXXXXXXXXXX)
    for (( part=0; part<$parts; part++ ))
    do
        skip=$((partSizeInMb * part))
        $(dd bs=1M count=$partSizeInMb skip=$skip if="$file" 2> /dev/null | md5sum >> $checksumFile)
    done
    etag=$(echo $(xxd -r -p $checksumFile | md5sum)-$parts | sed 's/ --/-/')
    echo -e "${1}\t${etag}"
    rm $checksumFile
    

    OSX 版本:

    #!/bin/bash
    
    if [ $# -ne 2 ]; then
        echo "Usage: $0 file partSizeInMb";
        exit 0;
    fi
    
    file=$1
    
    if [ ! -f "$file" ]; then
        echo "Error: $file not found." 
        exit 1;
    fi
    
    partSizeInMb=$2
    fileSizeInMb=$(du -m "$file" | cut -f 1)
    parts=$((fileSizeInMb / partSizeInMb))
    if [[ $((fileSizeInMb % partSizeInMb)) -gt 0 ]]; then
        parts=$((parts + 1));
    fi
    
    checksumFile=$(mktemp -t s3md5)
    
    for (( part=0; part<$parts; part++ ))
    do
        skip=$((partSizeInMb * part))
        $(dd bs=1m count=$partSizeInMb skip=$skip if="$file" 2>/dev/null | md5 >>$checksumFile)
    done
    
    echo $(xxd -r -p $checksumFile | md5)-$parts
    rm $checksumFile
    

    【讨论】:

      【解决方案4】:

      下面是我将本地文件校验和与 s3 etag 进行比较的工作。 我用 Python

      def md5_checksum(filename):
          m = hashlib.md5()
          with open(filename, 'rb') as f:
              for data in iter(lambda: f.read(1024 * 1024), b''):
                  m.update(data)
         
          return m.hexdigest()
      
      
      def etag_checksum(filename, chunk_size=8 * 1024 * 1024):
          md5s = []
          with open(filename, 'rb') as f:
              for data in iter(lambda: f.read(chunk_size), b''):
                  md5s.append(hashlib.md5(data).digest())
          m = hashlib.md5(b"".join(md5s))
          print('{}-{}'.format(m.hexdigest(), len(md5s)))
          return '{}-{}'.format(m.hexdigest(), len(md5s))
      
      def etag_compare(filename, etag):
          et = etag[1:-1] # strip quotes
          print('et',et)
          if '-' in et and et == etag_checksum(filename):
              return True
          if '-' not in et and et == md5_checksum(filename):
              return True
          return False
      
      
      def main():   
          session = boto3.Session(
              aws_access_key_id=s3_accesskey,
              aws_secret_access_key=s3_secret
          )
          s3 = session.client('s3')
          obj_dict = s3.get_object(Bucket=bucket_name, Key=your_key)
      
          etag = (obj_dict['ETag'])
          print('etag', etag)
          
          validation = etag_compare(filename,etag)
          print(validation)
          etag_checksum(filename, chunk_size=8 * 1024 * 1024)
          return validation

      【讨论】:

      • your_key 是否与filename 相同?
      • 有不同的。
      • 在使用etag_checksum 函数时,我遇到了文件小于chunkt_size 的问题。一个简单的文件大小测试 (os.path.getsize(filename) &lt; chunk_size) 解决了这个问题,以防其他人也有这个问题。在这种情况下,哈希是hashlib.md5(f.read())
      【解决方案5】:

      对于任何花时间四处搜索以找出为什么 md5 与 S3 中的 ETag 不同的人。

      ETag 将针对数据的chuck进行计算,并将所有md5hash连接起来,使md5再次hash,并在最后保留chunk的数量。

      这里是生成哈希的C#版本

          string etag = HashOf("file.txt",8);
      

      源代码

          private string HashOf(string filename,int chunkSizeInMb)
          {
              string returnMD5 = string.Empty;
              int chunkSize = chunkSizeInMb * 1024 * 1024;
      
              using (var crypto = new MD5CryptoServiceProvider())
              {
                  int hashLength = crypto.HashSize/8;
      
                  using (var stream = File.OpenRead(filename))
                  {
                      if (stream.Length > chunkSize)
                      {
                          int chunkCount = (int)Math.Ceiling((double)stream.Length/(double)chunkSize);
      
                          byte[] hash = new byte[chunkCount*hashLength];
                          Stream hashStream = new MemoryStream(hash);
      
                          long nByteLeftToRead = stream.Length;
                          while (nByteLeftToRead > 0)
                          {
                              int nByteCurrentRead = (int)Math.Min(nByteLeftToRead, chunkSize);
                              byte[] buffer = new byte[nByteCurrentRead];
                              nByteLeftToRead -= stream.Read(buffer, 0, nByteCurrentRead);
      
                              byte[] tmpHash = crypto.ComputeHash(buffer);
      
                              hashStream.Write(tmpHash, 0, hashLength);
      
                          }
      
                          returnMD5 = BitConverter.ToString(crypto.ComputeHash(hash)).Replace("-", string.Empty).ToLower()+"-"+ chunkCount;
                      }
                      else {
                          returnMD5 = BitConverter.ToString(crypto.ComputeHash(stream)).Replace("-", string.Empty).ToLower();
      
                      }
                      stream.Close();
                  }
              }
              return returnMD5;
          }
      

      【讨论】:

      • 此代码适用于我的小文件。大文件给了我不同的哈希
      • 文件有多大?
      • 如何获取s3 multi-part object key的chunk size?
      • 取决于上传软件。您可以在通过 AWS CLI 上传时设置块大小。(默认为:8MB)参考:docs.aws.amazon.com/cli/latest/topic/s3-config.html
      【解决方案6】:

      最简单的方法是在将这些文件上传到存储桶之前自己将校验和设置为元数据:

      ObjectMetadata md = new ObjectMetadata();
      md.setContentMD5("foobar");
      PutObjectRequest req = new PutObjectRequest(BUCKET, KEY, new File("/path/to/file")).withMetadata(md);
      tm.upload(req).waitForUploadResult();
      

      现在您无需下载文件即可访问这些元数据:

      ObjectMetadata md2 = s3Client.getObjectMetadata(BUCKET, KEY);
      System.out.println(md2.getContentMD5());
      

      来源:https://github.com/aws/aws-sdk-java/issues/1711

      【讨论】:

        【解决方案7】:

        我发现 s3cmd 有一个 --list-md5 选项可以与 ls 命令一起使用,例如

        s3cmd ls --list-md5 s3://bucket_of_mine/
        

        希望这会有所帮助。

        【讨论】:

        • 这很方便,但正如其他几个答案中提到的,在某些文件上,这不是实际的 MD5 总和,而是其他类型的哈希。
        • 我检查了 s3cmd 源代码,它在上传时将 md5 存储在元数据中。所以这个命令只会打印使用 s3cmd 上传的对象或单块上传的对象的 md5
        【解决方案8】:

        我已经针对上传文件的 MD5sum 交叉检查了 jets3t 和管理控制台,并且 ETag 似乎等于 MD5sum。您可以在 AWS 管理控制台中查看文件的属性:

        https://console.aws.amazon.com/s3/home

        【讨论】:

          【解决方案9】:

          这对我有用。 在 PHP 中,您可以使用以下方法比较本地文件和亚马逊文件之间的校验和:

          
          
              // get localfile md5
              $checksum_local_file = md5_file ( '/home/file' );
          
              // compare checksum between localfile and s3file    
              public function compareChecksumFile($file_s3, $checksum_local_file) {
          
                  $Connection = new AmazonS3 ();
                  $bucket = amazon_bucket;
                  $header = $Connection->get_object_headers( $bucket, $file_s3 );
          
                  // get header
                  if (empty ( $header ) || ! is_object ( $header )) {
                      throw new RuntimeException('checksum error');
                  }
                  $head = $header->header;
                  if (empty ( $head ) || !is_array($head)) {
                      throw new RuntimeException('checksum error');
                  }
                  // get etag (md5 amazon)
                  $etag = $head['etag'];
                  if (empty ( $etag )) {
                      throw new RuntimeException('checksum error');
                  }
                  // remove quotes
                  $checksumS3 = str_replace('"', '', $etag);
          
                  // compare md5
                  if ($checksum_local_file === $checksumS3) {
                      return TRUE;
                  } else {
                      return FALSE;
                  }
              }
          
          

          【讨论】:

          • 这不适用于分段上传,如上所述,如果etag 不是整个文件的md5,而是块的md5s 的md5,那么你在比较不同的东西。考虑在 laravel 中比较 S3 对象 etag 和本地文件的 md5 的这个例子:dump(trim(Storage::disk('s3')-&gt;getMetadata($path)['etag'], '"'), md5_file(Storage::disk('local')-&gt;path($path))) 返回:["7243d808aaca5466cee4ebef3ed6cbdf-4", "3680d3d6da5d90ccf2d758b90682f64c",]
          【解决方案10】:

          这是在 PowerShell 中为从 c# 转换的对象获取 S3 ETag 的代码。

          function Get-ETag {
            [CmdletBinding()]
            param(
              [Parameter(Mandatory=$true)]
              [string]$Path,
              [Parameter(Mandatory=$true)]
              [int]$ChunkSizeInMb
            )
          
            $returnMD5 = [string]::Empty
            [int]$chunkSize = $ChunkSizeInMb * [Math]::Pow(2, 20)
          
            $crypto = New-Object System.Security.Cryptography.MD5CryptoServiceProvider
            [int]$hashLength = $crypto.HashSize / 8
          
            $stream = [System.IO.File]::OpenRead($Path)
          
            if($stream.Length -gt $chunkSize) {
              $chunkCount = [int][Math]::Ceiling([double]$stream.Length / [double]$chunkSize)
              [byte[]]$hash = New-Object byte[]($chunkCount * $hashLength)
              $hashStream = New-Object System.IO.MemoryStream(,$hash)
              [long]$numBytesLeftToRead = $stream.Length
              while($numBytesLeftToRead -gt 0) {
                $numBytesCurrentRead = [int][Math]::Min($numBytesLeftToRead, $chunkSize)
                $buffer = New-Object byte[] $numBytesCurrentRead
                $numBytesLeftToRead -= $stream.Read($buffer, 0, $numBytesCurrentRead)
                $tmpHash = $crypto.ComputeHash($buffer)
                $hashStream.Write($tmpHash, 0, $hashLength)
              }
              $returnMD5 = [System.BitConverter]::ToString($crypto.ComputeHash($hash)).Replace("-", "").ToLower() + "-" + $chunkCount
            }
            else {
              $returnMD5 = [System.BitConverter]::ToString($crypto.ComputeHash($stream)).Replace("-", "").ToLower()
            }
          
            $stream.Close()  
            $returnMD5
          }
          

          【讨论】:

            【解决方案11】:

            这是根据 2017 年获取 MD5 哈希的代码

            import java.security.MessageDigest;
            import java.security.NoSuchAlgorithmException;
            import org.apache.commons.codec.binary.Base64;
            public class GenerateMD5 {
            public static void main(String args[]) throws Exception{
                String s = "<CORSConfiguration> <CORSRule> <AllowedOrigin>http://www.example.com</AllowedOrigin> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <AllowedMethod>DELETE</AllowedMethod> <AllowedHeader>*</AllowedHeader> <MaxAgeSeconds>3000</MaxAgeSeconds> </CORSRule> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedHeader>*</AllowedHeader> <MaxAgeSeconds>3000</MaxAgeSeconds> </CORSRule> </CORSConfiguration>";
            
                    MessageDigest md = MessageDigest.getInstance("MD5");
                    md.update(s.getBytes());
                    byte[] digest = md.digest();
                    StringBuffer sb = new StringBuffer();
                    /*for (byte b : digest) {
                        sb.append(String.format("%02x", b & 0xff));
                    }*/
                    System.out.println(sb.toString());
                    StringBuffer sbi = new StringBuffer();
                    byte [] bytes = Base64.encodeBase64(digest);
                    String finalString = new String(bytes);
                    System.out.println(finalString);
                }
            }
            

            注释代码是大多数人错误地将其更改为十六进制的地方

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-12-05
              • 1970-01-01
              • 2018-01-22
              • 2015-02-03
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多