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

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

檢查物件完整性

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

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

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

Amazon S3 為您提供選擇用於在上傳或下載過程中驗證資料的檢查總和演算法選項。您可以選擇以下 Secure Hash 演算法 (SHA) 或循環宂餘檢查 (CRC) 資料完整性演算法之一:

  • CRC32

  • CRC32C

  • SHA-1

  • SHA-256

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

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

  • 當您使用的是 SDK,您可以將 x-amz-sdk-checksum-algorithm 參數設定為您希望 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 SDK 上傳具有多部分上傳的大型檔案、下載大型檔案,以及驗證多部分上傳檔案,所有這些都使用 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 的物件。如需詳細資訊,請參閱《AWS CLI 命令參考》中的 PutObject。您也可以使用 get-objecthead-object 以擷取已上傳物件的檢查總和以驗證資料的完整性。

如需相關資訊,請參閱AWS Command Line Interface 使用者指南中的 Amazon S3 CLI 常見問答集

上傳物件時使用 Content-MD5

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

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

MD5 摘要不是強制需求,但您可以將其用於上傳過程以驗證物件的完整性。

使用 Content-MD5 和 ETag 驗證上傳的物件

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

根據物件的不同,物件的 ETag 可能是物件資料的 MD5 摘要:

  • 如果物件是由 PutObjectPostObject,或 CopyObject 操作所建立,或是通過 AWS Management Console,並且該物件也是採用 Amazon S3 受管金鑰 (SSE-S3) 的伺服器端加密或加密,則物件的 ETag 是該物件資料的 MD5 摘要。

  • 如果物件是由PutObjectPostObjectCopyObject作業或透過、建立 AWS Management Console,且該物件使用客戶提供的金鑰 (SSE-C) 或伺服器端加密 () 金鑰 AWS Key Management Service (SSE-KMS AWS KMS) 加密,則該物件的 ETag 不是其物件資料的 MD5 摘要。

  • 如果物件是由 Multipart Upload 或者 Part Copy 操作所建立,則無論加密方法為何,物件的 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 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 計算分段上傳物件的檢查總和的方式,物件的檢查總和值可能會因為複製而變更。如果您使用的是開發套件或 REST API 並進行呼叫 CopyObject,Amazon S3 會根據 CopyObject API 操作的大小限制複製任何物件。無論物件是由單個請求上傳還是作為分段上傳的一部分,Amazon S3 都會將此複製作為單獨操作進行。使用複製命令,物件的檢查總和是完整物件的直接檢查總和。如果對象最初是使用分段上傳上傳,即使資料沒有變更,檢查總和的值也會變更。

注意

超過大小上限的物件,CopyObject API 操作必須使用分段複製命令。

重要

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

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

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