在 Amazon S3 中检查对象完整性 - Amazon Simple Storage Service

在 Amazon S3 中检查对象完整性

Amazon S3 使用校验和值来验证您上传或下载的数据的完整性。此外,您可以请求为存储在 Amazon S3 中的任何对象计算另一个校验和值。您可以选择在上传、复制或批量复制数据时要使用的校验和算法。

当您上传数据时,Amazon S3 使用您选择的算法在服务器端计算校验和,并使用提供的值对其进行验证,然后再存储对象并将校验和作为对象元数据的一部分存储。对于单分段上传和多分段上传,这种验证在加密模式、对象大小和存储类方面是一致的。但是,当您复制或批量复制数据时,Amazon S3 会计算源对象的校验和并将其移动到目标对象。

注意

当您执行单分段上传或多分段上传时,您可以选择将预先计算的校验和作为请求的一部分包含在内,并使用完整对象校验和类型。要将预先计算的值用于多个对象,请使用 AWS CLI 或 AWS SDK。

使用支持的校验和算法

使用 Amazon S3,您可以选择要在上传期间验证数据的校验和算法。然后,指定的校验和算法将与对象一起存储,并可用于在下载期间验证数据完整性。您可以选择以下安全哈希算法(SHA)或循环冗余校验(CRC)校验和算法之一来计算校验和值:

  • CRC-64/NVME (CRC64NVME)

  • CRC-32 (CRC32)

  • CRC-32C (CRC32C)

  • SHA-1 (SHA1)

  • SHA-256 (SHA256)

此外,您可以使用 Content-MD5 标头为每个请求提供校验和。

上传对象时,请指定要使用的算法:

  • 使用 AWS Management Console时,请选择要使用的校验和算法。您可以选择指定对象的校验和值。当 Amazon S3 收到对象时,它使用您指定的算法计算校验和。如果这两个校验和值不匹配,Amazon S3 会生成错误。

  • 使用 SDK 时,请注意以下事项:

    • ChecksumAlgorithm 参数设置为您希望 Amazon S3 使用的算法。如果您已经有了预先计算的校验和,则可以将校验和值传递给 AWS SDK,然后此 SDK 会在请求中包含该值。如果您未传递校验和值或未指定校验和算法,SDK 会自动为您计算校验和值,并将其包含在请求中以提供完整性保护。如果单个校验和值与校验和算法的设置值不匹配,Amazon S3 会使请求失败并出现 BadDigest 错误。

    • 如果您使用的是升级的 AWS SDK,此 SDK 会为您选择校验和算法。但是,您可以覆盖此校验和算法。

    • 如果您未指定校验和算法,且 SDK 也没有为您计算校验和,则 S3 会自动选择 CRC-64/NVME (CRC64NVME) 校验和算法。

  • 使用 REST API 时,不使用 x-amz-sdk-checksum-algorithm 参数。而是使用特定于算法的标头之一(例如 x-amz-checksum-crc32)。

要将这些校验和值中的任何一个应用于已上传到 Amazon S3 的对象,您可以复制该对象,并指定是要使用现有的校验和算法,还是使用新的算法。如果您未指定算法,S3 将使用现有算法。如果源对象没有指定的校验和算法或校验和值,Amazon S3 会使用 CRC-64/NVME 算法来计算目标对象的校验和值。还可以在使用 S3 批量操作复制对象时指定校验和算法。

重要

如果您将分段上传与校验和结合使用来获得复合(或分段级)校验和,则分段上传的分段编号必须是连续的并从 1 开始。如果您尝试使用非连续分段编号来完成分段上传请求,Amazon S3 会生成 HTTP 500 Internal Server 错误。

完整对象和复合校验和类型

在 Amazon S3 中,支持两种类型的校验和:

  • 完整对象校验和:完整对象校验和是根据分段上传的所有内容计算的,涵盖从第一分段的第一个字节到最后分段的最后一个字节的所有数据。

    注意

    所有 PUT 请求都需要完整对象校验和类型。

  • 复合校验和:复合校验和是根据分段上传中每个分段的各个校验和计算得出的。这种方法不是根据所有数据内容计算校验和,而是聚合分段级的校验和(从第一个分段到最后一个分段),以便为完整对象生成单个组合校验和。

    注意

    当对象作为分段上传进行上传时,该对象的实体标签(ETag)不是整个对象的 MD5 摘要。相反,Amazon S3 会在上传时计算每个分段的 MD5 摘要。MD5 摘要用于确定最终对象的 ETag。Amazon S3 将 MD5 摘要的字节串联在一起,然后计算这些串联值的 MD5 摘要。在最终创建 ETag 的步骤中,Amazon S3 将在末尾添加一个短横线以及分段总数。

Amazon S3 支持以下完整对象和复合校验和算法类型:

  • CRC-64/NVME (CRC64NVME):仅支持完整对象算法类型。

  • CRC-32 (CRC32):支持完整对象和复合算法类型。

  • CRC-32C (CRC32C):支持完整对象和复合算法类型。

  • SHA-1 (SHA1):支持完整对象和复合算法类型。

  • SHA-256 (SHA256):支持完整对象和复合算法类型。

单分段上传

以单分段(使用 PutObject)上传的对象的校验和被视为完整对象校验和。在 Amazon S3 控制台中上传对象时,您可以选择希望 S3 使用的校验和算法,并且还(可选)提供预先计算的值。然后,Amazon S3 会在存储对象及其校验和值之前验证此校验和。在对象下载期间请求校验和值时,可以验证对象的数据完整性。

分段上传

使用 MultipartUpload API 将对象分成多个分段进行上传时,您可以指定希望 Amazon S3 使用的校验和算法以及校验和类型(完整对象或复合)。

下表显示了分段上传中每种校验和算法支持哪种校验和算法类型:

校验和算法 完整对象 复合键
CRC-64/NVME (CRC64NVME)
CRC-32 (CRC32) 支持
CRC-32C (CRC32C) 支持
SHA-1 (SHA1) 支持
SHA-256 (SHA256) 支持

使用完整对象校验和进行分段上传

创建或执行分段上传时,可以在上传时使用完整对象校验和来进行验证。这意味着您可以为 MultipartUpload API 提供校验和算法,从而简化完整性验证工具,因为您不再需要跟踪已上传对象的分段边界。您可以在 CompleteMultipartUpload 请求中提供整个对象的校验和以及对象大小。

当您在分段上传期间提供完整的对象校验和时,AWS SDK 会将校验和传递给 Amazon S3,而 S3 会在服务器端验证对象完整性,将其与接收到的值进行比较。然后,如果值匹配,Amazon S3 会存储对象。如果这两个值不匹配,S3 将使请求失败并出现 BadDigest 错误。对象的校验和还存储在对象元数据中,您稍后将使用这些元数据来验证对象的数据完整性。

要获得完整对象校验和,可以在 S3 中使用 CRC-64/NVME (CRC64NVME)、CRC-32 (CRC32) 或 CRC-32C (CRC32C) 校验和算法。分段上传中的完整对象校验和仅适用于基于 CRC 的校验和,因为它们可以线性化为完整对象校验和。这种线性化可让 Amazon S3 并行处理您的请求,以提高性能。尤其是,S3 可以根据分段级校验和计算整个对象的校验和。这种类型的验证不适用于其它算法,例如 SHA 和 MD5。由于 S3 具有默认的完整性保护,因此,如果在没有校验和的情况下上传对象,S3 会自动将推荐的完整对象 CRC-64/NVME (CRC64NVME) 校验和算法附加到该对象。

注意

要启动分段上传,您可以指定校验和算法和完整对象校验和类型。指定校验和算法和完整对象校验和类型后,您可以为分段上传提供完整对象校验和值。

使用分段级校验和进行分段上传

将对象上传到 Amazon S3 时,它们可以作为单个对象上传,也可以通过分段上传过程以多个分段进行上传。您可以为分段上传选择校验和类型。对于分段上传分段级校验和(或复合校验和),Amazon S3 使用指定的校验和算法来计算每个分段的校验和。您可以使用 UploadPart 提供每个分段的校验和值。如果您尝试在 Amazon S3 控制台中上传的对象设置为使用 CRC-64/NVME (CRC64NVME) 校验和算法且超过 16 MB,则会自动将其指定为完整对象校验和。

然后,Amazon S3 使用存储的分段级校验和值来确认每个分段是否正确上传。当提供每个分段的校验和(针对整个对象)时,S3 使用每个分段的已存储校验和值来在内部计算完整对象校验和,并将其与提供的校验和值进行比较。这可最大限度地降低计算成本,因为 S3 可以使用各分段的校验和计算整个对象的校验和。有关分段上传的更多信息,请参阅在 Amazon S3 中使用分段上传来上传和复制对象使用完整对象校验和进行分段上传

完全上传对象后,可以使用最终计算出的校验和来验证对象的数据完整性。

上传分段上传的某个分段时,请注意以下几点:

  • 要检索有关对象的信息,包括组成整个对象的分段数,可以使用 GetObjectAttributes 操作。借助其它校验和,还可以恢复每个单独分段的信息,包括该分段的校验和值。

  • 对于已完成的分段上传,您可以使用 GetObjectHeadObject 操作,并指定与单个分段相符的分段编号或字节范围,来获取单个分段的校验和。如果您想检索仍在进行的分段上传的各个分段的校验和值,可以使用 ListParts

  • 由于 Amazon S3 计算分段对象的校验和的方式,复制对象时,对象的校验和值可能会发生变化。如果您使用的是 SDK 或 REST API,并且调用 CopyObject,Amazon S3 将复制不超过 CopyObject API 操作的大小限制的任何对象。无论对象是在单个请求中上传还是作为分段上传的一部分上传,Amazon S3 都可以作为单个操作执行此复制。使用 copy 命令,对象的校验和是完整对象的直接校验和。如果对象最初是使用分段上传进行上传的,即使数据没有变化,校验和值也会发生变化。

  • 大于 CopyObject API 操作的大小限制的对象必须使用分段上传复制命令

  • 当您使用 AWS Management Console执行某些操作时,如果对象大于 16 MB,则 Amazon S3 将使用分段上传。

校验和操作

上传对象后,您可以获取校验和值,并将其与预先计算的或之前存储的相同算法类型的校验和值进行比较。以下示例说明可以使用哪些校验和操作或方法来验证数据完整性。

要了解有关使用控制台和指定在上传对象时要使用的校验和算法的更多信息,请参阅上传对象教程:使用其他校验和检查 Amazon S3 中数据的完整性

以下示例显示了如何使用 AWS SDK 通过分段上传来上传大文件、下载大文件和验证分段上传文件,所有这些都使用 SHA-256 进行文件验证。

Java
例 示例:使用 SHA-256 上传、下载和验证大文件

有关创建和测试有效示例的说明,请参阅《适用于 Java 的 AWS SDK 开发人员指南》中的入门

import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.core.ResponseInputStream; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest; import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; import software.amazon.awssdk.services.s3.model.ChecksumMode; import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest; import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadResponse; import software.amazon.awssdk.services.s3.model.CompletedMultipartUpload; import software.amazon.awssdk.services.s3.model.CompletedPart; import software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest; import software.amazon.awssdk.services.s3.model.CreateMultipartUploadResponse; import software.amazon.awssdk.services.s3.model.GetObjectAttributesRequest; import software.amazon.awssdk.services.s3.model.GetObjectAttributesResponse; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.services.s3.model.GetObjectTaggingRequest; import software.amazon.awssdk.services.s3.model.ObjectAttributes; import software.amazon.awssdk.services.s3.model.PutObjectTaggingRequest; import software.amazon.awssdk.services.s3.model.Tag; import software.amazon.awssdk.services.s3.model.Tagging; import software.amazon.awssdk.services.s3.model.UploadPartRequest; import software.amazon.awssdk.services.s3.model.UploadPartResponse; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Base64; import java.util.List; public class LargeObjectValidation { private static String FILE_NAME = "sample.file"; private static String BUCKET = "sample-bucket"; //Optional, if you want a method of storing the full multipart object checksum in S3. private static String CHECKSUM_TAG_KEYNAME = "fullObjectChecksum"; //If you have existing full-object checksums that you need to validate against, you can do the full object validation on a sequential upload. private static String SHA256_FILE_BYTES = "htCM5g7ZNdoSw8bN/mkgiAhXt5MFoVowVg+LE9aIQmI="; //Example Chunk Size - this must be greater than or equal to 5MB. private static int CHUNK_SIZE = 5 * 1024 * 1024; public static void main(String[] args) { S3Client s3Client = S3Client.builder() .region(Region.US_EAST_1) .credentialsProvider(new AwsCredentialsProvider() { @Override public AwsCredentials resolveCredentials() { return new AwsCredentials() { @Override public String accessKeyId() { return Constants.ACCESS_KEY; } @Override public String secretAccessKey() { return Constants.SECRET; } }; } }) .build(); uploadLargeFileBracketedByChecksum(s3Client); downloadLargeFileBracketedByChecksum(s3Client); validateExistingFileAgainstS3Checksum(s3Client); } public static void uploadLargeFileBracketedByChecksum(S3Client s3Client) { System.out.println("Starting uploading file validation"); File file = new File(FILE_NAME); try (InputStream in = new FileInputStream(file)) { MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); CreateMultipartUploadRequest createMultipartUploadRequest = CreateMultipartUploadRequest.builder() .bucket(BUCKET) .key(FILE_NAME) .checksumAlgorithm(ChecksumAlgorithm.SHA256) .build(); CreateMultipartUploadResponse createdUpload = s3Client.createMultipartUpload(createMultipartUploadRequest); List<CompletedPart> completedParts = new ArrayList<CompletedPart>(); int partNumber = 1; byte[] buffer = new byte[CHUNK_SIZE]; int read = in.read(buffer); while (read != -1) { UploadPartRequest uploadPartRequest = UploadPartRequest.builder() .partNumber(partNumber).uploadId(createdUpload.uploadId()).key(FILE_NAME).bucket(BUCKET).checksumAlgorithm(ChecksumAlgorithm.SHA256).build(); UploadPartResponse uploadedPart = s3Client.uploadPart(uploadPartRequest, RequestBody.fromByteBuffer(ByteBuffer.wrap(buffer, 0, read))); CompletedPart part = CompletedPart.builder().partNumber(partNumber).checksumSHA256(uploadedPart.checksumSHA256()).eTag(uploadedPart.eTag()).build(); completedParts.add(part); sha256.update(buffer, 0, read); read = in.read(buffer); partNumber++; } String fullObjectChecksum = Base64.getEncoder().encodeToString(sha256.digest()); if (!fullObjectChecksum.equals(SHA256_FILE_BYTES)) { //Because the SHA256 is uploaded after the part is uploaded; the upload is bracketed and the full object can be fully validated. s3Client.abortMultipartUpload(AbortMultipartUploadRequest.builder().bucket(BUCKET).key(FILE_NAME).uploadId(createdUpload.uploadId()).build()); throw new IOException("Byte mismatch between stored checksum and upload, do not proceed with upload and cleanup"); } CompletedMultipartUpload completedMultipartUpload = CompletedMultipartUpload.builder().parts(completedParts).build(); CompleteMultipartUploadResponse completedUploadResponse = s3Client.completeMultipartUpload( CompleteMultipartUploadRequest.builder().bucket(BUCKET).key(FILE_NAME).uploadId(createdUpload.uploadId()).multipartUpload(completedMultipartUpload).build()); Tag checksumTag = Tag.builder().key(CHECKSUM_TAG_KEYNAME).value(fullObjectChecksum).build(); //Optionally, if you need the full object checksum stored with the file; you could add it as a tag after completion. s3Client.putObjectTagging(PutObjectTaggingRequest.builder().bucket(BUCKET).key(FILE_NAME).tagging(Tagging.builder().tagSet(checksumTag).build()).build()); } catch (IOException | NoSuchAlgorithmException e) { e.printStackTrace(); } GetObjectAttributesResponse objectAttributes = s3Client.getObjectAttributes(GetObjectAttributesRequest.builder().bucket(BUCKET).key(FILE_NAME) .objectAttributes(ObjectAttributes.OBJECT_PARTS, ObjectAttributes.CHECKSUM).build()); System.out.println(objectAttributes.objectParts().parts()); System.out.println(objectAttributes.checksum().checksumSHA256()); } public static void downloadLargeFileBracketedByChecksum(S3Client s3Client) { System.out.println("Starting downloading file validation"); File file = new File("DOWNLOADED_" + FILE_NAME); try (OutputStream out = new FileOutputStream(file)) { GetObjectAttributesResponse objectAttributes = s3Client.getObjectAttributes(GetObjectAttributesRequest.builder().bucket(BUCKET).key(FILE_NAME) .objectAttributes(ObjectAttributes.OBJECT_PARTS, ObjectAttributes.CHECKSUM).build()); //Optionally if you need the full object checksum, you can grab a tag you added on the upload List<Tag> objectTags = s3Client.getObjectTagging(GetObjectTaggingRequest.builder().bucket(BUCKET).key(FILE_NAME).build()).tagSet(); String fullObjectChecksum = null; for (Tag objectTag : objectTags) { if (objectTag.key().equals(CHECKSUM_TAG_KEYNAME)) { fullObjectChecksum = objectTag.value(); break; } } MessageDigest sha256FullObject = MessageDigest.getInstance("SHA-256"); MessageDigest sha256ChecksumOfChecksums = MessageDigest.getInstance("SHA-256"); //If you retrieve the object in parts, and set the ChecksumMode to enabled, the SDK will automatically validate the part checksum for (int partNumber = 1; partNumber <= objectAttributes.objectParts().totalPartsCount(); partNumber++) { MessageDigest sha256Part = MessageDigest.getInstance("SHA-256"); ResponseInputStream<GetObjectResponse> response = s3Client.getObject(GetObjectRequest.builder().bucket(BUCKET).key(FILE_NAME).partNumber(partNumber).checksumMode(ChecksumMode.ENABLED).build()); GetObjectResponse getObjectResponse = response.response(); byte[] buffer = new byte[CHUNK_SIZE]; int read = response.read(buffer); while (read != -1) { out.write(buffer, 0, read); sha256FullObject.update(buffer, 0, read); sha256Part.update(buffer, 0, read); read = response.read(buffer); } byte[] sha256PartBytes = sha256Part.digest(); sha256ChecksumOfChecksums.update(sha256PartBytes); //Optionally, you can do an additional manual validation again the part checksum if needed in addition to the SDK check String base64PartChecksum = Base64.getEncoder().encodeToString(sha256PartBytes); String base64PartChecksumFromObjectAttributes = objectAttributes.objectParts().parts().get(partNumber - 1).checksumSHA256(); if (!base64PartChecksum.equals(getObjectResponse.checksumSHA256()) || !base64PartChecksum.equals(base64PartChecksumFromObjectAttributes)) { throw new IOException("Part checksum didn't match for the part"); } System.out.println(partNumber + " " + base64PartChecksum); } //Before finalizing, do the final checksum validation. String base64FullObject = Base64.getEncoder().encodeToString(sha256FullObject.digest()); String base64ChecksumOfChecksums = Base64.getEncoder().encodeToString(sha256ChecksumOfChecksums.digest()); if (fullObjectChecksum != null && !fullObjectChecksum.equals(base64FullObject)) { throw new IOException("Failed checksum validation for full object"); } System.out.println(fullObjectChecksum); String base64ChecksumOfChecksumFromAttributes = objectAttributes.checksum().checksumSHA256(); if (base64ChecksumOfChecksumFromAttributes != null && !base64ChecksumOfChecksums.equals(base64ChecksumOfChecksumFromAttributes)) { throw new IOException("Failed checksum validation for full object checksum of checksums"); } System.out.println(base64ChecksumOfChecksumFromAttributes); out.flush(); } catch (IOException | NoSuchAlgorithmException e) { //Cleanup bad file file.delete(); e.printStackTrace(); } } public static void validateExistingFileAgainstS3Checksum(S3Client s3Client) { System.out.println("Starting existing file validation"); File file = new File("DOWNLOADED_" + FILE_NAME); GetObjectAttributesResponse objectAttributes = s3Client.getObjectAttributes(GetObjectAttributesRequest.builder().bucket(BUCKET).key(FILE_NAME) .objectAttributes(ObjectAttributes.OBJECT_PARTS, ObjectAttributes.CHECKSUM).build()); try (InputStream in = new FileInputStream(file)) { MessageDigest sha256ChecksumOfChecksums = MessageDigest.getInstance("SHA-256"); MessageDigest sha256Part = MessageDigest.getInstance("SHA-256"); byte[] buffer = new byte[CHUNK_SIZE]; int currentPart = 0; int partBreak = objectAttributes.objectParts().parts().get(currentPart).size(); int totalRead = 0; int read = in.read(buffer); while (read != -1) { totalRead += read; if (totalRead >= partBreak) { int difference = totalRead - partBreak; byte[] partChecksum; if (totalRead != partBreak) { sha256Part.update(buffer, 0, read - difference); partChecksum = sha256Part.digest(); sha256ChecksumOfChecksums.update(partChecksum); sha256Part.reset(); sha256Part.update(buffer, read - difference, difference); } else { sha256Part.update(buffer, 0, read); partChecksum = sha256Part.digest(); sha256ChecksumOfChecksums.update(partChecksum); sha256Part.reset(); } String base64PartChecksum = Base64.getEncoder().encodeToString(partChecksum); if (!base64PartChecksum.equals(objectAttributes.objectParts().parts().get(currentPart).checksumSHA256())) { throw new IOException("Part checksum didn't match S3"); } currentPart++; System.out.println(currentPart + " " + base64PartChecksum); if (currentPart < objectAttributes.objectParts().totalPartsCount()) { partBreak += objectAttributes.objectParts().parts().get(currentPart - 1).size(); } } else { sha256Part.update(buffer, 0, read); } read = in.read(buffer); } if (currentPart != objectAttributes.objectParts().totalPartsCount()) { currentPart++; byte[] partChecksum = sha256Part.digest(); sha256ChecksumOfChecksums.update(partChecksum); String base64PartChecksum = Base64.getEncoder().encodeToString(partChecksum); System.out.println(currentPart + " " + base64PartChecksum); } String base64CalculatedChecksumOfChecksums = Base64.getEncoder().encodeToString(sha256ChecksumOfChecksums.digest()); System.out.println(base64CalculatedChecksumOfChecksums); System.out.println(objectAttributes.checksum().checksumSHA256()); if (!base64CalculatedChecksumOfChecksums.equals(objectAttributes.checksum().checksumSHA256())) { throw new IOException("Full object checksum of checksums don't match S3"); } } catch (IOException | NoSuchAlgorithmException e) { e.printStackTrace(); } } }

您可以发送 REST 请求以上传具有校验和值的对象,通过 PutObject 验证数据的完整性。您还可以使用 GetObjectHeadObject 检索对象的校验和值。

您可以发送 PUT 请求,在单个操作中上传最大 5 GB 的对象。有关更多信息,请参阅《AWS CLI 命令参考》中的 PutObject。您还可以使用 get-objecthead-object 检索已上传对象的校验和以验证数据的完整性。

有关信息,请参阅《AWS Command Line Interface 用户指南》中的 Amazon S3 CLI FAQ

在上传对象时使用 Content-M5

上传后验证对象完整性的另一种方法是在上传对象时提供其 MD5 摘要。如果您计算对象的 MD5 摘要,则可以使用 Content-MD5 标头向 PUT 命令提供摘要。

上传对象后,Amazon S3 会计算对象的 MD5 摘要,并将其与您提供的值进行比较。只有在两个摘要匹配的情况下,请求才会成功。

不要求提供 MD5 摘要,但您可以在上传过程中使用它来验证对象的完整性。

使用 Content-MD5 和 ETag 验证上传的对象

对象的实体标签 (ETag) 表示该对象的特定版本。请注意,ETag 仅反映对于对象内容的更改,而不反映对其对象元数据的更改。如果只有对象的元数据发生变化,eTag 将保持不变。

根据对象的不同,对象的 ETag 可能是对象数据的 MD5 摘要:

  • 如果对象是由 PutObjectPostObjectCopyObject 操作创建或通过 AWS Management Console创建,并且该对象还是纯文本或使用具有 Amazon S3 托管式密钥的服务器端加密(SSE-S3)进行加密的,则该对象的 ETag 将是其对象数据的 MD5 摘要。

  • 如果对象是由 PutObjectPostObjectCopyObject 操作创建或通过 AWS Management Console创建,并且该对象使用客户提供的密钥 (SSE-C) 或 AWS Key Management Service (AWS KMS) 密钥 (SSE-KMS) 通过服务器端加密进行加密,则该对象的 ETag 将不是其对象数据的 MD5 摘要。

  • 如果对象由分段上传过程或 UploadPartCopy 操作创建,则无论使用哪种加密方法,对象的 ETag 都不是 MD5 摘要。如果对象大于 16 MB,则 AWS Management Console会作为分段上传来上传或复制该对象,因此 ETag 不是 MD5 摘要。

对于 ETag 是对象的 Content-MD5 摘要的对象,您可以将对象的 ETag 值与计算出的或之前存储的 Content-MD5 摘要进行比较。

使用尾随校验和

将对象上传到 Amazon S3 时,您可以为对象提供预先计算的校验和,也可以使用 AWS SDK 代表您自动为分块上传创建尾随校验和。如果您使用尾随校验和,当您上传对象时,Amazon S3 会使用您指定的算法自动生成校验和,以便在分块上传期间验证对象的完整性。

要使用 AWS SDK 时创建尾随校验和,请使用您首选的算法填充 ChecksumAlgorithm 参数。SDK 使用该算法计算对象(或对象分段)的校验和,并自动将其附加到分块上传请求的末尾。这种行为可以节省您的时间,因为 Amazon S3 一次性执行数据的验证和上传。

重要

如果您使用的是 S3 对象 Lambda,则对 S3 对象 Lambda 的所有请求都使用 s3-object-lambda 而不是 s3。此行为会影响尾随校验和值的签名。有关 S3 对象 Lambda 的更多信息,请参阅 使用 S3 对象 Lambda 转换对象

尾随校验和标头

要发出分块内容编码请求,Amazon S3 要求客户端服务器包含多个标头,以正确地解析请求。客户端服务器必须包括以下标头:

  • x-amz-decoded-content-length此标头表示随请求一起上传到 Amazon S3 的实际数据的纯文本大小。

  • x-amz-content-sha256此标头表示请求中包含的分块上传的类型。对于带有尾随校验和的分块上传,针对不使用有效载荷签名的请求,标头值为 STREAMING-UNSIGNED-PAYLOAD-TRAILER,而针对使用 SigV4 有效载荷签名的请求,标头值为 STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER。(有关实现签名有效载荷的更多信息,请参阅 Signature calculations for the authorization header: Transferring a payload in multiple chunks。)

  • x-amz-trailer此标头表示请求中尾随标头的名称。如果尾随校验和存在(其中 AWS SDK 将校验和附加到已编码的请求正文),则 x-amz-trailer 标头值包含 x-amz-checksum- 前缀并以算法名称结尾。目前支持以下 x-amz-trailer 值:

    • x-amz-checksum-crc32

    • x-amz-checksum-crc32c

    • x-amz-checksum-crc64nvme

    • x-amz-checksum-sha1

    • x-amz-checksum-sha256

注意

还可以在请求中包含 Content-Encoding 标头以及分块值。虽然此标头不是必需的,但包含此标头可以最大限度地减少传输编码数据时的 HTTP 代理问题。如果请求中存在其它 Content-Encoding 标头(例如 gzip),则 Content-Encoding 标头会将分块值包含在以逗号分隔的编码列表中。例如,Content-Encoding: aws-chunked, gzip

分块分段

当您使用分块编码将对象上传到 Amazon S3 时,上传请求包括以下类型的分块(按列出的顺序进行格式化):

  • 对象主体分块:可以有一个、多个或零个主体分块与分块上传请求相关联。

  • 完成分块:可以有一个、多个或零个主体分块与分块上传请求相关联。

  • 尾随分块:尾随校验和列在完成分块之后。只支持一个尾随分块。

注意

每个分块上传都必须以最终的 CRLF(例如 \r\n)结尾,以表示请求的结尾。

有关分块格式化的示例,请参阅示例:带有尾随校验和的分块上传

对象主体分块

对象主体分块是包含要上传到 S3 的实际对象数据的分块。这些分块具有一致的大小和格式约束。

对象主体分块大小

这些分块必须包含至少 8192 字节(或 8 KiB)的对象数据,但最后一个主体分块除外,它可以更小。没有明确的最大分块大小,但您可以预期所有分块都将小于 5 GB 的最大上传大小。根据您的客户端服务器实现,每个分块的大小可能会有所不同。

对象主体分块格式

对象主体分块以对象主体分块中字节数的十六进制编码开头,然后是 CRLF(回车换行符)、该分块的对象字节和另一个 CRLF。

例如:

hex-encoding-of-object-bytes-in-chunk\r\n chunk-object-bytes\r\n

但是,在对分块进行签名时,对象主体分块遵循不同的格式,其中签名通过分号分隔符附加到分块大小后面。例如:

hex-encoding-of-object-bytes-in-chunk;chunk-signature\r\n chunk-object-bytes\r\n

有关分块签名的更多信息,请参阅 Signature calculations for the Authorization Header: Transferring a payload in multiple chunks (AWS Signature Version 4)。有关分块格式的更多信息,请参阅 RFC 编辑器 网站上的 Chunked transfer encoding

完成分块

完成分块必须是每个分块上传的最后一个对象主体分块。完成分块的格式与主体分块类似,但始终包含零字节的对象数据。(零字节的对象数据表示所有数据都已上传。) 分块上传必须包含一个完成分块作为其最终对象主体分块,格式如下:

0\r\n

但是,如果内容编码请求使用有效载荷签名,则它将改用以下格式:

0;chunk-signature\r\n

尾随分块

尾随分块保存所有 S3 上传请求的计算校验和。尾随分块包括两个字段:一个标头名称字段和一个标头值字段。上传请求的标头名称字段必须与传递到 x-amz-trailer 请求标头的值相匹配。例如,如果请求包含 x-amz-trailer: x-amz-checksum-crc32 并且尾随分块具有标头名称 x-amz-checksum-sha1,则请求将失败。尾随分块中的值字段包括该对象的大端校验和值的 base64 编码。(大端序将数据的最高有效字节存储在最低的内存地址,将最低有效字节存储在最大的内存地址)。用于计算此校验和的算法与标头名称的后缀相同(例如 crc32)。

尾随分块格式

对于未签名的有效载荷请求,尾随分块使用以下格式:

x-amz-checksum-lowercase-checksum-algorithm-name:base64-checksum-value\n\r\n\r\n

对于具有 SigV4 signed payloads 的请求,尾随分块在尾随分块后面包含一个尾随签名。

trailer-checksum\n\r\n trailer-signature\r\n

也可以将 CRLF 直接添加到 base64 校验和值的末尾。例如:

x-amz-checksum-lowercase-checksum-algorithm-name:base64-checksum-value\r\n\r\n

示例:带有尾随校验和的分块上传

对于带有尾随校验和的 PutObjectUploadPart 请求,Amazon S3 支持使用 aws-chunked 内容编码的分块上传。

例 1:带有尾随 CRC-32 校验和的未签名分块 PutObject 请求

以下是具有尾随 CRC-32 校验和的分块 PutObject 请求的示例。在此示例中,客户端将一个 17 KB 的对象分成三个未签名的分块进行上传,并使用 x-amz-checksum-crc32 标头附加一个尾随 CRC-32 校验和分块。

PUT /Key+ HTTP/1.1 Host: amzn-s3-demo-bucket Content-Encoding: aws-chunked x-amz-decoded-content-length: 17408 x-amz-content-sha256: STREAMING-UNSIGNED-PAYLOAD-TRAILER x-amz-trailer: x-amz-checksum-crc32 2000\r\n // Object body chunk 1 (8192 bytes) object-bytes\r\n 2000\r\n // Object body chunk 2 (8192 bytes) object-bytes\r\n 400\r\n // Object body chunk 3 (1024 bytes) object-bytes\r\n 0\r\n // Completion chunk x-amz-checksum-crc32:YABb/g==\n\r\n\r\n // Trailer chunk (note optional \n character) \r\n // CRLF

以下是示例响应:

HTTP/1.1 200 ETag: ETag x-amz-checksum-crc32: YABb/g==
注意

校验和值末尾的换行符 \n 的使用可能因客户端而异。

例 2:带有尾随 CRC-32 (CRC32) 校验和的 SigV4 签名的分块 PutObject 请求

以下是具有尾随 CRC-32 校验和的分块 PutObject 请求的示例。此请求使用 SigV4 有效载荷签名。在此示例中,客户端将一个 17 KB 的对象分成三个签名的分块进行上传。除了 object body 分块之外,completion chunktrailer chunk 也已签名。

PUT /Key+ HTTP/1.1 Host: amzn-s3-demo-bucket.s3.amazonaws.com Content-Encoding: aws-chunked x-amz-decoded-content-length: 17408 x-amz-content-sha256: STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER x-amz-trailer: x-amz-checksum-crc32 authorization-code // SigV4 headers authorization 2000;chunk-signature=signature-value...\r\n // Object body chunk 1 (8192 bytes) object-bytes\r\n 2000;chunk-signature\r\n // Object body chunk 2 (8192 bytes) object-bytes\r\n 400;chunk-signature\r\n // Object body chunk 3 (1024 bytes) object-bytes\r\n 0;chunk-signature\r\n // Completion chunk x-amz-checksum-crc32:YABb/g==\n\r\n // Trailer chunk (note optional \n character) trailer-signature\r\n \r\n // CRLF

以下是示例响应:

HTTP/1.1 200 ETag: ETag x-amz-checksum-crc32: YABb/g==