檢查物件完整性 - Amazon Simple Storage Service

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

檢查物件完整性

Amazon S3 使用檢查總和值來驗證您上傳到 Amazon S3 或從 Amazon S3 下載的資料完整性。此外,您可以請求為儲存在 Amazon S3 中的任何物件計算另一個檢查總和值。您可以從多種檢查總和演算法中進行選擇,以便在上傳或複製資料時使用。Amazon S3 使用此演算法運算額外的檢查總和值並將其儲存為物件中繼資料的一部分。若要進一步了解如何使用其他總和檢查來驗證資料完整性,請參閱教學課程:使用其他總和檢查來檢查 Amazon S3 中資料的完整性

上傳物件時,您也可以選擇將預先計算的檢查總和作為請求的一部分。Amazon S3 會將提供的檢查總和與其使用指定演算法計算的檢查總和進行比較。如果兩個值不相符,Amazon S3 會回報錯誤。

使用支持的檢查總和演算法

Amazon S3 為您提供選擇用於在上傳或下載過程中驗證資料的檢查總和演算法選項。您可以選擇下列其中一個安全雜湊演算法 (SHA) 或循環冗餘檢查 (CRC) 資料完整性檢查演算法:

  • CRC-32

  • CRC-32C

  • SHA-1

  • SHA-256

上傳物件時,您可以指定要使用的演算法:

  • 使用 時 AWS Management Console,您可以選擇要使用的檢查總和演算法。執行此操作時,您可以選擇指定物件的檢查總和值。Amazon S3 接收物件時,它會使用您指定的演算法計算檢查總和。如果兩個檢查總和值不相符,Amazon S3 會產生錯誤。

  • 當您使用 SDK 時,您可以將 ChecksumAlgorithm 參數的值設定為您希望 Amazon S3 在計算檢查總和時使用的演算法。Amazon S3 會自動計算檢查總和值。

  • 當您使用 REST 時API,不使用 x-amz-sdk-checksum-algorithm 參數。相反,您可以使用指定演算法的標頭之一 (例如 x-amz-checksum-crc32)。

如需上傳物件的詳細資訊,請參閱「上傳物件」。

若要將這些檢查總和值應用到任何一個已上傳到 Amazon S3 的物件,您可以複製該物件。複製物件時,您可以指定使用現有檢查總和演算法還是使用新的演算法。在使用任何支持的機制複製物件時,您可以指定檢查總和演算法,包括 S3 批次操作。如需 S3 批次操作的詳細資訊,請參閱「使用批次操作大量執行物件操作」。

重要

如果您使用的是具有額外檢查總和的分段上傳,則分段號部分編號必須使用連續的部分編號。使用其他檢查總和時,如果您嘗試使用非連續的零件編號完成分段上傳請求,Amazon S3 會產生 HTTP 500 Internal Server Error錯誤。

上傳物件後,您可以取得檢查總和值,並將其與使用相同演算法計算的預先計算值或先前存儲的檢查總和值進行比較。

若要進一步了解如何使用主控台,以及如何指定上傳物件時要使用的檢查總和演算法,請參閱 上傳物件教學課程:使用其他檢查總和來檢查 Amazon S3 中資料的完整性

下列範例示範如何使用 AWS SDKs 上傳包含分段上傳的大型檔案、下載大型檔案,以及驗證分段上傳檔案,所有這些檔案都使用 SHA-256 進行檔案驗證。

Java
範例:使用 SHA-256 上傳、下載和驗證大型檔案

如需建立和測試工作範例的說明,請參閱 AWS SDK for Java 開發人員指南中的入門

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 驗證資料的完整性。您也可以使用 GetObject HeadObject 擷取物件的檢查總和值。

您可以傳送 PUT 請求,以便在單一操作中上傳多達 5 GB 的物件。如需詳細資訊,請參閱《AWS CLI 命令參考》中的 PutObject。您也可以使用 get-objecthead-object 以擷取已上傳物件的檢查總和以驗證資料的完整性。

如需詳細資訊,請參閱 AWS Command Line Interface 使用者指南中的 Amazon S3 CLI FAQ

上傳物件時使用 Content-MD5

上傳後驗證物件完整性的另一種方法是在上傳物件時提供物件的 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 會使用您指定的演算法自動產生檢查總和,並在上傳過程中使用該演算法驗證物件的完整性。

若要在使用 a AWS SDK 時建立尾隨檢查總和,請將 ChecksumAlgorithm 參數填入您偏好的演算法。SDK 使用該演算法來計算物件 (或物件部分) 的檢查總和,並自動將其附加到上傳請求的結尾。此行為可節省您的時間,因為 Amazon S3 一次同時執行驗證和上傳您的資料。

重要

如果您使用的是 S3 物件 Lambda,則對 S3 物件 Lambda 的所有請求都使用 s3-object-lambda 而不是 s3。此行為會影響追蹤檢查總和值的簽名。如需 S3 Object Lambda 的詳細資訊,請參閱 使用 S3 Object Lambda 轉換物件

對分段上傳使用部分檢查總和

當物件上傳到 Amazon S3 時,它們可以作為單個物件上傳,也可以通過分段上傳過程上傳。通過主控台上傳大於 16 MB 的物件會使用分段上傳自動上傳。如需分段上傳的詳細資訊,請參閱「使用分段上傳來上傳和複製物件」。

當物件作為分段上傳上傳時,物件的 ETag 不是整個物件的 MD5 摘要。Amazon S3 會在上傳時計算每個部分的 MD5 摘要。MD5 摘要用於判斷最後一個物件的 ETag。Amazon S3 將 MD5 摘要的位元組串連在一起,然後計算這些串連值的 MD5 摘要。建立 ETag 的最後步驟是 Amazon S3 將包含總部分數的破折號新增至結尾。

例如,請考慮使用具有 ETag 的分段上傳上傳的物件C9A5A6878D97B48CC965C1E41859F034-14。在這種情況下, C9A5A6878D97B48CC965C1E41859F034是串連在一起的所有摘要的 MD5 摘要。所以,-14 表示有 14 個部分與此物件的分段上傳關聯。

如果您已為多部分物件啟用了其他檢查總和,則 Amazon S3 會使用指定的檢查總和演算法計算每個部分的檢查總和。已完成物件的檢查總和計算方式與 Amazon S3 計算分段上傳的 MD5 摘要相同。您可以使用此檢查總和來驗證物件的完整性。

若要擷取物件的相關資訊,包括組成整個物件的 部分數量,您可以使用 GetObjectAttributes 操作。通過額外的檢查總和,您還可以復原包含每個部分檢查總和的值。

對於已完成的上傳,您可以使用 GetObject HeadObject 操作,並指定與單一部分相符的零件編號或位元組範圍,以取得個別零件的檢查總和。如果您想要擷取仍在進行分段上傳之個別部分的檢查總和值,您可以使用 ListParts

由於 Amazon S3 如何計算分段上傳物件的檢查總和,因此如果您複製物件,物件的檢查總和值可能會變更。如果您使用的是 SDK 或 API REST 且呼叫 CopyObject,Amazon S3 會複製任何大小不超過 API CopyObject 操作限制的物件。無論物件是由單個請求上傳還是作為分段上傳的一部分,Amazon S3 都會將此複製作為單獨操作進行。使用複製命令,物件的檢查總和是完整物件的直接檢查總和。如果對象最初是使用分段上傳上傳,即使資料沒有變更,檢查總和的值也會變更。

注意

大於 API CopyObject 操作大小限制的物件必須使用分段複製命令。

重要

當您使用 執行某些操作時 AWS Management Console,如果物件大小大於 16 MB,Amazon S3 會使用分段上傳。這種情況下,檢查總和不會是完整物件的直接檢查總和,而是基於每個部分的計算的檢查總和值。

例如,請考慮您使用 REST 作為單一部分直接上傳上傳的物件大小為 100 MBAPI。在這種情況下,檢查總和是整個物件的檢查總和。如果您稍後使用主控台重命名該、複製該物件,更改儲存類別或編輯中繼資料,則 Amazon S3 將使用分段上傳功能來更新物件。因此,Amazon S3 以各個部分的檢查總和值計算,為物件建立一個新的檢查總和值。

上述主控台操作清單不是您可以在 中採取的所有可能動作的完整清單 AWS Management Console ,導致 Amazon S3 使用分段上傳功能更新物件。請記住,無論使用主控台執行大小超過 16 MB 的物件,檢查總和的值很有可能不是整個物件的檢查總和值。