Amazon S3로 데이터를 업로드할 때 객체 무결성 확인하기
Amazon S3로 데이터를 업로드할 때 객체 무결성 확인하기
Amazon S3는 업로드와 다운로드 작업 중 체크섬 값을 사용해 데이터 무결성을 검증해요. 데이터를 업로드할 때 AWS SDK와 AWS Management Console은 데이터를 전송하기 전에 여러분이 선택한 체크섬 알고리즘으로 체크섬 값을 계산해요. 그러면 S3가 데이터의 체크섬을 독립적으로 계산하고 제공된 체크섬 값과 대조해요. 전송 중에 데이터 무결성이 유지되었는지 확인한 뒤에만 객체를 받아들이지요. S3는 체크섬 값을 객체 메타데이터로 저장하고 객체 자체도 저장해요.
객체 무결성을 검증하려면 다운로드 중에 체크섬 값을 요청할 수 있어요. 이 검증은 암호화 모드, 객체 크기, 스토리지 클래스, 단일 파트 및 멀티파트 업로드를 가리지 않고 일관되게 작동해요. 업로드의 체크섬 알고리즘을 바꾸려면 개별 객체를 복사하거나 여러 객체에는 일괄 복사를 사용할 수 있어요.
단일 파트 업로드의 경우 체크섬 값을 헤더로 제공할 수 있어요. 사전 계산된 값을 제공하거나 AWS SDK가 업로드 중에 계산하게 할 수 있어요. S3가 계산한 체크섬 값이 여러분이 제공한 값과 일치하면 요청이 수락되고, 일치하지 않으면 요청이 거부돼요.
멀티파트 업로드의 경우 AWS SDK는 청크 업로드에 대한 후행 체크섬(trailing checksum)을 자동으로 만들 수 있어요. 후행 체크섬을 사용하면 Amazon S3가 지정한 알고리즘으로 모든 파트에 대한 체크섬 값을 생성하고, 청크 업로드 요청의 끝에 체크섬 값을 덧붙여요. S3는 검증과 업로드를 단일 패스로 수행해 효율을 높여요. 자세한 내용은 "후행 체크섬 사용"을 참고하세요.
출처: 문서
본문
지원되는 체크섬 알고리즘 사용
Amazon S3에서는 업로드 중 체크섬 값을 계산할 체크섬 알고리즘을 선택할 수 있어요. 지정된 체크섬 알고리즘은 객체와 함께 저장되고, 다운로드 중 데이터 무결성을 검증하는 데 사용할 수 있어요. 다음 Secure Hash Algorithm(SHA) 또는 Cyclic Redundancy Check(CRC) 체크섬 알고리즘 중 하나를 선택해 체크섬 값을 계산할 수 있어요.
- CRC-64/NVME (
CRC64NVME) - CRC-32 (
CRC32) - CRC-32C (
CRC32C) - SHA-1 (
SHA1) - SHA-256 (
SHA256) - MD5 (
MD5) - XXHash64 (
XXHASH64) - XXHash3 (
XXHASH3) - XXHash128 (
XXHASH128) - SHA-512 (
SHA512)
참고
content-MD5헤더는 SSE-S3 암호화를 사용하는 단일 파트 업로드(PUT작업)로 업로드된 객체에 대해서만 S3 ETag를 사용해 사용할 수 있어요.
또한 각 요청과 함께 Content-MD5 헤더를 사용해 체크섬을 제공할 수 있어요.
객체를 업로드할 때 사용할 알고리즘을 지정해요.
- AWS Management Console을 사용할 때 사용하려는 체크섬 알고리즘을 선택해요. 객체의 체크섬 값을 선택적으로 지정할 수 있어요. Amazon S3가 객체를 받으면 지정한 알고리즘으로 체크섬을 계산해요. 두 체크섬 값이 일치하지 않으면 Amazon S3가 오류를 생성해요.
- SDK를 사용할 때 다음을 기억하세요.
- Amazon S3가 사용하길 원하는 알고리즘으로
ChecksumAlgorithm매개변수를 설정해요. 이미 사전 계산된 체크섬이 있다면 체크섬 값을 AWS SDK에 전달하면 SDK가 그 값을 요청에 포함해요. 체크섬 값을 전달하지 않거나 체크섬 알고리즘을 지정하지 않으면, SDK가 여러분을 대신해 체크섬 값을 자동으로 계산해 무결성 보호를 제공하는 요청에 포함해요. 개별 체크섬 값이 체크섬 알고리즘의 설정 값과 일치하지 않으면 Amazon S3가BadDigest오류로 요청을 실패시켜요. - 업그레이드된 AWS SDK를 사용한다면 SDK가 체크섬 알고리즘을 선택해줘요. 하지만 이 체크섬 알고리즘은 재정의할 수 있어요.
- 체크섬 알고리즘을 지정하지 않고 SDK도 체크섬을 계산하지 않는다면, S3가 CRC-64/NVME (
CRC64NVME) 체크섬 알고리즘을 자동으로 선택해요.
- Amazon S3가 사용하길 원하는 알고리즘으로
- REST API를 사용할 때
x-amz-sdk-checksum-algorithm매개변수를 사용하지 마세요. 대신 알고리즘별 헤더(예:x-amz-checksum-crc32) 중 하나를 사용하세요.
이미 Amazon S3에 업로드된 객체에 이 체크섬 값 중 어느 것을 적용하려면 객체를 복사하고 기존 체크섬 알고리즘을 사용할지 새 알고리즘을 사용할지 지정할 수 있어요. 알고리즘을 지정하지 않으면 S3는 기존 알고리즘을 사용해요. 소스 객체에 지정된 체크섬 알고리즘 또는 체크섬 값이 없다면, Amazon S3는 CRC-64/NVME 알고리즘으로 대상 객체의 체크섬 값을 계산해요. S3 Batch Operations로 객체를 복사할 때도 체크섬 알고리즘을 지정할 수 있어요.
중요
복합(또는 파트 수준) 체크섬용 Checksums로 멀티파트 업로드를 사용한다면, 멀티파트 업로드 파트 번호는 연속적이어야 하고 1부터 시작해야 해요. 연속되지 않은 파트 번호로 멀티파트 업로드 요청을 완료하려고 하면 Amazon S3가
HTTP 500 Internal Server오류를 생성해요.
전체 객체·복합 체크섬 유형
Amazon S3에는 지원되는 체크섬이 두 가지 유형이 있어요.
- 전체 객체 체크섬(Full object checksums): 전체 객체 체크섬은 멀티파트 업로드의 모든 내용, 즉 첫 번째 파트의 첫 바이트부터 마지막 파트의 마지막 바이트까지의 모든 데이터를 기준으로 계산돼요. AWS Management Console로 16MB보다 작은 객체를 업로드할 때는 전체 객체 체크섬 유형만 지원된다는 점을 기억하세요.
참고
PutObject로 업로드한 객체는 전체 객체 체크섬 유형을 사용해요. 체크섬 유형을 지정할 필요는 없어요.
- 복합 체크섬(Composite checksums): 복합 체크섬은 멀티파트 업로드의 각 파트 개별 체크섬을 기준으로 계산돼요. 모든 데이터 내용을 기준으로 체크섬을 계산하는 대신, 이 방식은 파트 수준 체크섬(첫 파트부터 마지막 파트까지)을 집계해 완전한 객체에 대한 단일 결합 체크섬을 만들어요. 복합 체크섬 유형은 특정 체크섬 알고리즘에서만, 그리고 객체를 멀티파트 업로드로 업로드할 때만 사용할 수 있어요. 각 체크섬 유형을 지원하는 알고리즘은 "멀티파트 업로드" 섹션을 참고하세요.
참고
객체가 멀티파트 업로드로 업로드되면 객체의 엔티티 태그(ETag)는 전체 객체의 MD5 다이제스트가 아니에요. 대신 Amazon S3는 각 파트가 업로드될 때 각 개별 파트의 MD5 다이제스트를 계산해요. 그 MD5 다이제스트들이 최종 객체의 ETag를 결정하는 데 사용돼요. Amazon S3는 MD5 다이제스트의 바이트를 연결한 다음 이 연결된 값의 MD5 다이제스트를 계산해요. 최종 ETag 생성 단계에서 Amazon S3는 끝에 총 파트 수가 있는 대시를 추가해요.
단일 파트 업로드
단일 파트( 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) |
아니요 | 예 |
MD5 (MD5) |
아니요 | 예 |
XXHash64 (XXHASH64) |
아니요 | 예 |
XXHash3 (XXHASH3) |
아니요 | 예 |
XXHash128 (XXHASH128) |
아니요 | 예 |
SHA-512 (SHA512) |
아니요 | 예 |
멀티파트 업로드에 전체 객체 체크섬 사용
멀티파트 업로드를 만들거나 수행할 때 업로드 검증에 전체 객체 체크섬을 사용할 수 있어요. 이렇게 하면 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) 체크섬 알고리즘을 자동으로 붙여줘요.
참고
멀티파트 업로드를 시작할 때 체크섬 알고리즘과 전체 객체 체크섬 유형을 지정할 수 있어요. 체크섬 알고리즘과 전체 객체 체크섬 유형을 지정한 후에는 멀티파트 업로드에 전체 객체 체크섬 값을 제공할 수 있어요.
중요
새 체크섬 알고리즘(MD5, XXHash3, XXHash64, XXHash128, SHA-512)으로 멀티파트 업로드를 사용할 때는
CreateMultipartUpload요청에서x-amz-checksum-algorithm헤더로 체크섬 알고리즘을 지정해야 해요.CreateMultipartUpload에서 알고리즘을 지정하지 않고CompleteMultipartUpload요청에 체크섬 값(예:x-amz-checksum-sha512)을 제공하면 요청이InvalidRequest오류로 실패해요.
참고
기존 체크섬 알고리즘(CRC32, CRC32C, SHA-1, SHA-256)의 경우
CreateMultipartUpload에서 알고리즘이 지정되지 않으면CompleteMultipartUpload에서 제공된 체크섬 헤더는 현재 수락되지만 검증되거나 객체와 함께 저장되지는 않아요. 체크섬이 검증되고 저장되도록 보장하려면CreateMultipartUpload에서 항상 알고리즘을 지정할 것을 권장해요.
멀티파트 업로드에 파트 수준 체크섬 사용
객체를 Amazon S3에 업로드할 때 단일 객체로 업로드하거나 멀티파트 업로드 과정으로 파트로 나눠 업로드할 수 있어요. 멀티파트 업로드에 Checksum 유형을 선택할 수 있어요. 멀티파트 업로드 파트 수준 체크섬(또는 복합 체크섬)의 경우 Amazon S3는 지정된 체크섬 알고리즘으로 각 개별 파트의 체크섬을 계산해요. UploadPart로 각 파트의 체크섬 값을 제공할 수 있어요. Amazon S3 콘솔에서 업로드하려는 객체가 CRC-64/NVME (CRC64NVME) 체크섬 알고리즘으로 설정되어 있고 16MB를 초과하면 자동으로 전체 객체 체크섬으로 지정돼요.
그러면 Amazon S3는 저장된 파트 수준 체크섬 값을 사용해 각 파트가 올바르게 업로드되었는지 확인해요. 각 파트의 체크섬(전체 객체에 대한)이 제공되면, S3는 각 파트의 저장된 체크섬 값을 사용해 전체 객체 체크섬을 내부적으로 계산하고 제공된 체크섬 값과 비교해요. S3가 파트의 체크섬을 사용해 전체 객체의 체크섬을 계산할 수 있으므로 계산 비용이 최소화돼요. 멀티파트 업로드에 대한 자세한 내용은 Amazon S3에서 멀티파트 업로드를 사용한 객체 업로드·복사와 "멀티파트 업로드에 전체 객체 체크섬 사용"을 참고하세요.
객체가 완전히 업로드되면 최종 계산된 체크섬으로 객체의 데이터 무결성을 검증할 수 있어요.
멀티파트 업로드의 파트를 업로드할 때 다음을 기억하세요.
- 객체에 대한 정보(전체 객체를 구성하는 파트 수 포함)를 검색하려면
GetObjectAttributes작업을 사용할 수 있어요. 추가 체크섬을 사용하면 파트의 체크섬 값을 포함해 각 개별 파트에 대한 정보도 복구할 수 있어요. - 완료된 업로드의 경우
GetObject또는HeadObject작업을 사용하고 단일 파트와 일치하는 파트 번호나 바이트 범위를 지정해 개별 파트의 체크섬을 얻을 수 있어요. 아직 진행 중인 멀티파트 업로드의 개별 파트에 대한 체크섬 값을 검색하려면ListParts를 사용할 수 있어요. - Amazon S3가 멀티파트 객체의 체크섬을 계산하는 방식 때문에, 객체를 복사하면 객체의 체크섬 값이 바뀔 수 있어요. SDK나 REST API를 사용하고
CopyObject를 호출하면, Amazon S3는CopyObjectAPI 작업의 크기 제한까지의 객체를 복사해요. Amazon S3는 객체가 단일 요청으로 업로드되었는지 멀티파트 업로드의 일부로서 업로드되었는지와 무관하게 이 복사를 단일 작업으로 수행해요. 복사 명령에서 객체의 체크섬은 전체 객체의 직접 체크섬이에요. 객체가 원래 멀티파트 업로드로 업로드되었다면 데이터는 같아도 체크섬 값은 바뀌어요. CopyObjectAPI 작업의 크기 제한보다 큰 객체는 멀티파트 업로드 복사 명령을 사용해야 해요.- AWS Management Console로 일부 작업을 수행할 때 객체가 16MB보다 크면 Amazon S3는 멀티파트 업로드를 사용해요.
체크섬 방법
객체 업로드 후 체크섬 값을 가져와 같은 체크섬 알고리즘 유형의 사전 계산된(또는 이전에 저장된) 체크섬 값과 비교할 수 있어요. 다음 예시들은 데이터 무결성을 검증하는 데 사용할 수 있는 체크섬 계산 방법을 보여줘요.
콘솔 사용과 객체 업로드 시 지정할 체크섬 알고리즘에 대한 자세한 내용은 객체 업로드와 튜토리얼: 추가 체크섬으로 Amazon S3 데이터 무결성 확인을 참고하세요.
다음 예시는 AWS SDK를 사용해 대용량 파일을 멀티파트 업로드로 업로드하고, 대용량 파일을 다운로드하고, 멀티파트 업로드 파일을 검증하는 방법을 보여줘요. 모두 파일 검증에 SHA-256을 사용해요.
Java
예시: SHA-256로 대용량 파일 업로드·다운로드·검증
작동하는 샘플을 만들고 테스트하는 지침은 AWS SDK for Java Developer Guide의 시작하기를 참고하세요.
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();
}
}
}
PutObject로 체크섬 값과 함께 객체를 업로드해 데이터 무결성을 검증하는 REST 요청을 보낼 수 있어요. GetObject 또는 HeadObject로 객체의 체크섬 값을 검색할 수도 있어요.
단일 작업으로 최대 5GB 객체를 업로드하는 PUT 요청을 보낼 수 있어요. 자세한 내용은 _AWS CLI Command Reference_의 PutObject를 참고하세요. get-object와 head-object를 사용해 이미 업로드된 객체의 체크섬을 검색해 데이터 무결성을 검증할 수도 있어요.
자세한 내용은 _AWS Command Line Interface User Guide_의 Amazon S3 CLI FAQ를 참고하세요.
객체 업로드 시 Content-MD5 사용
업로드 후 객체의 무결성을 검증하는 또 다른 방법은 업로드할 때 객체의 MD5 다이제스트를 제공하는 거예요. 객체의 MD5 다이제스트를 계산했다면 Content-MD5 헤더로 PUT 명령에 다이제스트를 제공할 수 있어요.
객체를 업로드한 후 Amazon S3는 객체의 MD5 다이제스트를 계산해 여러분이 제공한 값과 비교해요. 두 다이제스트가 일치할 때만 요청이 성공해요.
MD5 다이제스트를 제공하는 것은 필수가 아니지만, 업로드 과정의 일부로 객체의 무결성을 검증하는 데 사용할 수 있어요.
Content-MD5와 ETag를 사용해 업로드 객체 검증
객체의 엔티티 태그(ETag)는 그 객체의 특정 버전을 나타내요. ETag는 객체의 내용 변경만 반영할 뿐 메타데이터 변경은 반영하지 않는다는 점을 기억하세요. 객체의 메타데이터만 변경되면 ETag는 그대로 유지돼요.
객체에 따라 객체의 ETag가 객체 데이터의 MD5 다이제스트일 수 있어요.
- 객체가
PutObject,PostObject,CopyObject작업이나 AWS Management Console로 만들어졌고, 그 객체가 평문이거나 Amazon S3 관리 키로 서버 측 암호화(SSE-S3)된 경우, 그 객체의 ETag는 그 객체 데이터의 MD5 다이제스트예요. - 객체가
PutObject,PostObject,CopyObject작업이나 AWS Management Console로 만들어졌고, 고객 제공 키로 서버 측 암호화(SSE-C)되거나 AWS Key Management Service(AWS KMS) 키로 서버 측 암호화(SSE-KMS)된 경우, 그 객체의 ETag는 그 객체 데이터의 MD5 다이제스트가 아니에요. - 객체가 멀티파트 업로드 과정이나
UploadPartCopy작업으로 만들어진 경우, 암호화 방법과 무관하게 객체의 ETag는 MD5 다이제스트가 아니에요. 객체가 16MB보다 크면 AWS Management Console은 그 객체를 멀티파트 업로드로 업로드하거나 복사하므로 ETag는 MD5 다이제스트가 아니에요.
ETag가 객체의 Content-MD5 다이제스트인 객체의 경우, 객체의 ETag 값을 계산된(또는 이전에 저장된) Content-MD5 다이제스트와 비교할 수 있어요.
후행 체크섬 사용
Amazon S3에 큰 객체를 업로드할 때 객체의 사전 계산된 체크섬을 제공하거나, AWS SDK가 여러분을 대신해 청크 업로드에 대한 후행 체크섬을 자동으로 만들게 할 수 있어요. 후행 체크섬을 사용하면 Amazon S3가 객체를 업로드할 때 지정한 알고리즘으로 체크섬 값을 자동으로 생성해 청크 업로드에서 객체의 무결성을 검증해요.
AWS SDK를 사용할 때 후행 체크섬을 만들려면 ChecksumAlgorithm 매개변수를 선호하는 알고리즘으로 채워요. SDK는 그 알고리즘으로 객체(또는 객체 파트)의 체크섬 값을 계산하고 청크 업로드 요청의 끝에 자동으로 덧붙여요. Amazon S3가 데이터의 검증과 업로드를 단일 패스로 수행하므로 시간을 절약할 수 있어요.
중요
S3 Object Lambda를 사용한다면 S3 Object Lambda에 대한 모든 요청은
s3대신s3-object-lambda로 서명돼요. 이 동작은 후행 체크섬 값의 서명에 영향을 줘요. S3 Object Lambda에 대한 자세한 내용은 S3 Object 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값이에요. (서명된 페이로드 구현에 대한 자세한 내용은 권한 부여 헤더의 서명 계산: 여러 청크로 페이로드 전송을 참고하세요.)x-amz-trailer: 이 헤더는 요청의 후행 헤더 이름을 나타내요. 후행 체크섬이 존재하면(AWS SDK가 인코딩된 요청 본문에 체크섬을 덧붙일 때)x-amz-trailer헤더 값은x-amz-checksum-접두사를 포함하고 알고리즘 이름으로 끝나요. 현재 지원되는x-amz-trailer값은 다음과 같아요.x-amz-checksum-crc32x-amz-checksum-crc32cx-amz-checksum-crc64nvmex-amz-checksum-sha1x-amz-checksum-sha256
참고
요청에
Content-Encoding헤더를 chunked 값으로 포함할 수도 있어요. 이 헤더는 필수는 아니지만, 포함하면 인코딩된 데이터를 전송할 때 HTTP 프록시 문제를 최소화할 수 있어요. 요청에 gzip 같은 다른Content-Encoding헤더가 있으면Content-Encoding헤더는 인코딩 목록에 chunked 값을 쉼표로 구분해 포함해요. 예:Content-Encoding: aws-chunked, gzip.
청크 파트
청크 인코딩으로 Amazon S3에 객체를 업로드할 때 업로드 요청은 다음 유형의 청크(나열된 순서로 형식화됨)를 포함해요.
- 객체 본문 청크(Object body chunks): 청크 업로드 요청과 연결된 본문 청크는 하나, 여러 개, 또는 0개일 수 있어요.
- 완료 청크(Completion chunks): 청크 업로드 요청과 연결된 본문 청크는 하나, 여러 개, 또는 0개일 수 있어요.
- 후행 청크(Trailing chunks): 후행 체크섬은 완료 청크 뒤에 나열돼요. 후행 청크는 하나만 허용돼요.
참고
모든 청크 업로드는 요청의 끝을 나타내는 최종 CRLF(예:
\r\n)로 끝나야 해요.
청크 형식의 예시는 "후행 체크섬이 있는 청크 업로드 예시"를 참고하세요.
객체 본문 청크
객체 본문 청크는 S3로 업로드되는 실제 객체 데이터를 담고 있는 청크예요. 이 청크들은 일관된 크기와 형식 제약을 가져요.
객체 본문 청크 크기
이 청크들은 마지막 본문 청크(더 작을 수 있음)를 제외하고 최소 8,192바이트(또는 8KiB)의 객체 데이터를 포함해야 해요. 명시적 최대 청크 크기는 없지만, 모든 청크가 5GB 최대 업로드 크기보다 작을 것으로 기대할 수 있어요. 클라이언트 서버 구현에 따라 청크 크기는 청크마다 달라질 수 있어요.
객체 본문 청크 형식
객체 본문 청크는 객체 본문 청크의 바이트 수를 16진수로 인코딩한 값으로 시작하고, 그다음 CRLF(Carriage Return Line Feed), 해당 청크의 객체 바이트, 그리고 또 다른 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
청크 서명에 대한 자세한 내용은 권한 부여 헤더의 서명 계산: 여러 청크로 페이로드 전송(AWS Signature Version 4)을 참고하세요. 청크 형식에 대한 자세한 내용은 RFC Editor 웹사이트의 청크 전송 인코딩을 참고하세요.
완료 청크
완료 청크는 모든 청크 업로드의 최종 객체 본문 청크여야 해요. 완료 청크의 형식은 본문 청크와 비슷하지만 항상 0바이트의 객체 데이터를 포함해요. (0바이트의 객체 데이터는 모든 데이터가 업로드되었음을 나타내요.) 청크 업로드는 다음과 같은 형식의 완료 청크를 최종 객체 본문 청크로 포함해야 해요.
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 서명 페이로드가 있는 요청의 경우 후행 청크는 후행 청크 뒤에 후행 서명을 포함해요.
trailer-checksum\n\r\n
trailer-signature\r\n
base64 체크섬 값 끝에 CRLF를 직접 추가할 수도 있어요. 예를 들어:
x-amz-checksum-lowercase-checksum-algorithm-name:base64-checksum-value\r\n\r\n
후행 체크섬이 있는 청크 업로드 예시
Amazon S3는 후행 체크섬과 함께 PutObject 및 UploadPart 요청에 aws-chunked 콘텐츠 인코딩을 사용하는 청크 업로드를 지원해요.
예시 1 – 후행 CRC-32 체크섬이 있는 서명되지 않은 청크 PutObject 요청
다음은 후행 CRC-32 체크섬이 있는 청크 PutObject 요청의 예시예요. 이 예시에서 클라이언트는 17KB 객체를 서명되지 않은 세 개의 청크로 업로드하고 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 페이로드 서명을 사용해요. 이 예시에서 클라이언트는 17KB 객체를 세 개의 서명된 청크로 업로드해요. object body 청크에 더해 completion chunk와 trailer 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==