檢查物件完整性 - 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-32

  • 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 物件上執行大規模批次操作」。

重要

如果您使用的是具有額外檢查總和的分段上傳,則分段號部分編號必須使用連續的部分編號。使用其他總和檢查時,如果您嘗試使用非連續零件編號完成多部分上傳請求,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您也可以使用GetObjectHeadObject擷取物件的總和檢查碼值。

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

如需相關資訊,請參閱CLIFAQ中的 Amazon S3 AWS Command Line Interface 使用者指南

使用內容-上載物件MD5時

另一種在上載後驗證物件完整性的方法是在上傳物件時提供物件的MD5摘要。如果您計算物件的MD5摘要,您可以使用Content-MD5標頭以PUT命令提供摘要。

上傳物件後,Amazon S3 會計算物件的MD5摘要,並將其與您提供的值進行比較。僅當兩個摘要匹配時,請求才會成功。

不需要提供MD5摘要,但您可以使用它來驗證上傳程序中物件的完整性。

使用內容-MD5 和ETag來驗證上載的物件

物件的實體標籤 (ETag) 代表該物件的特定版本。請記住,只會ETag反映物件內容的變更,而不反映其中繼資料。如果只有物件的中繼資料變更,則會ETag保持不變。

視物件而定,物件ETag的可能是物件資料的MD5摘要:

  • 如果物件是由PutObjectPostObject、或CopyObject作業建立的,或透過 AWS Management Console,而且該物件也是純文字或透過使用 Amazon S3 受管金鑰 (SSE-S3) 的伺服器端加密加密,而該物件的物件資料摘要也是ETag該物件MD5資料的摘要。

  • 如果物件是由PutObjectPostObject、或CopyObject作業建立的,或透過 AWS Management Console,而且該物件會透過伺服器端加密,使用客戶提供的金鑰 (SSE-C) 或伺服器端加密 AWS Key Management Service (AWS KMS)key (SSE-KMS),該對象具有一ETag個不是其對象數據的MD5摘要。

  • 如果物件是由多部分上傳程序或UploadPartCopy作業建立的,則無論加密方法為何,該物件都不ETag是MD5摘要。如果物件大於 16 MB,則 AWS Management Console 上傳或複製該對象作為多部分上傳,因此ETag不是MD5摘要。

對於物件Content-MD5摘要ETag的物件,您可以將物件的ETag值與已計算或先前儲存的Content-MD5摘要進行比較。

使用追蹤檢查總和

將物件上傳到 Amazon S3 時,您可以為物件提供預先計算的總和檢查碼,也可以使用 AWS SDK代表您自動建立結尾總和檢查碼。如果您決定使用追蹤檢查總和,則 Amazon S3 會使用您指定的演算法自動產生檢查總和,並在上傳過程中使用該演算法驗證物件的完整性。

若要在使用時建立結尾總和檢查碼 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作業。通過額外的檢查總和,您還可以復原包含每個部分檢查總和的值。

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

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

注意

大於CopyObjectAPI作業大小限制的物件必須使用多部分複製指令。

重要

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

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

前面的控制台操作列表不是您可以在中執行的所有可能操作的完整列表 AWS Management Console 這會導致 Amazon S3 使用多部分上傳功能更新物件。 請記住,無論使用主控台執行大小超過 16 MB 的物件,檢查總和的值很有可能不是整個物件的檢查總和值。