使用校验和实现数据完整性保护 - AWS SDK for Java 2.x

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

使用校验和实现数据完整性保护

Amazon Simple Storage Service (Amazon S3) 允许您在上传对象时指定校验和。当您指定校验和时,校验和与对象一起存储,并且可以在下载对象时验证该校验和。

传输文件时,校验和可提供额外的数据层完整性。使用校验和,您可以通过确认收到文件与原始文件是否匹配来验证数据一致性。有关 Amazon S3 校验和的更多信息,请参阅 Amazon Simple Storage Service 用户指南,包括支持的算法

您可以灵活地选择最适合自己需求的算法,并让 SDK 计算校验和。或者,您也可以使用任一受支持的算法,提供预先计算好的校验和值。

注意

从AWS SDK for Java 2.x 的版本 2.30.0 开始,SDK 通过自动计算上传文件的 CRC32 校验和来提供默认的完整性保护。如果您未提供预先计算的校验和值,或未指定 SDK 计算校验和时应使用的算法,则 SDK 会计算此校验和。

该 SDK 还提供了可在外部设置的数据完整性保护全局设置,您可以在 AWS SDK 和工具参考指南中查阅相关说明。

我们在两个请求阶段讨论校验和:上传对象和下载对象。

上传对象

使用 putObject 方法上传对象并提供校验和算法时,SDK 会计算指定算法的校验和。

以下代码段展示了上传具有 SHA256 校验和的对象的请求。当 SDK 发送请求时,它会计算 SHA256 校验和并上传对象。Amazon S3 会计算校验和并将其与 SDK 提供的校验和进行对比,从而验证内容的完整性。然后 Amazon S3 将校验和与对象一起存储。

public void putObjectWithChecksum() { s3Client.putObject(b -> b .bucket(bucketName) .key(key) .checksumAlgorithm(ChecksumAlgorithm.SHA256), RequestBody.fromString("This is a test")); }

如果您未在请求中提供校验和算法,则校验和行为将根据您使用的 SDK 版本而异,具体如下表所示。

未提供校验和算法时的校验和行为

Java SDK 版本 校验和行为
2.30.0 之前的版本 SDK 不会自动计算基于 CRC 的校验和,也不会在请求中提供该值。
2.30.0 或更高版本

SDK 使用 CRC32 算法计算校验和,并在请求中提供该值。Amazon S3 通过计算自己的 CRC32 校验和并将其与 SDK 提供的校验和进行对比,从而验证传输的完整性。如果校验和匹配,则校验和将与对象一起保存。

使用预先计算的校验和值

与请求一起提供的预先计算校验和值会禁用 SDK 的自动计算,而是使用提供的值。

以下示例展示了具有预先计算的 SHA256 校验和的请求。

public void putObjectWithPrecalculatedChecksum(String filePath) { String checksum = calculateChecksum(filePath, "SHA-256"); s3Client.putObject((b -> b .bucket(bucketName) .key(key) .checksumSHA256(checksum)), RequestBody.fromFile(Paths.get(filePath))); }

如果 Amazon S3 确定指定算法的校验和值不正确,服务就会返回错误响应。

分段上传

您也可以将校验和用于分段上传。

适用于 Java 2.x 的 SDK 提供了两个选项,用于在分段上传中使用校验和。第一个选项使用 S3TransferManager

以下 Transfer Manager 示例指定了用于上传的 SHA1 算法。

public void multipartUploadWithChecksumTm(String filePath) { S3TransferManager transferManager = S3TransferManager.create(); UploadFileRequest uploadFileRequest = UploadFileRequest.builder() .putObjectRequest(b -> b .bucket(bucketName) .key(key) .checksumAlgorithm(ChecksumAlgorithm.SHA1)) .source(Paths.get(filePath)) .build(); FileUpload fileUpload = transferManager.uploadFile(uploadFileRequest); fileUpload.completionFuture().join(); transferManager.close(); }

如果您在使用传输管理器上传时未提供校验和算法,则 SDK 会根据 CRC32 算法自动计算校验和。SDK 会对所有版本的 SDK 执行此计算。

第二个选项使用 S3Client API(或 S3AsyncClient API)来执行分段上传。如果用此方法来指定校验和,则您必须指定在启动上传时使用的算法。您还必须为每个分段请求指定算法,并提供每个分段在上传后计算得出的校验和。

public void multipartUploadWithChecksumS3Client(String filePath) { ChecksumAlgorithm algorithm = ChecksumAlgorithm.CRC32; // Initiate the multipart upload. CreateMultipartUploadResponse createMultipartUploadResponse = s3Client.createMultipartUpload(b -> b .bucket(bucketName) .key(key) .checksumAlgorithm(algorithm)); // Checksum specified on initiation. String uploadId = createMultipartUploadResponse.uploadId(); // Upload the parts of the file. int partNumber = 1; List<CompletedPart> completedParts = new ArrayList<>(); ByteBuffer bb = ByteBuffer.allocate(1024 * 1024 * 5); // 5 MB byte buffer try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) { long fileSize = file.length(); long position = 0; while (position < fileSize) { file.seek(position); long read = file.getChannel().read(bb); bb.flip(); // Swap position and limit before reading from the buffer. UploadPartRequest uploadPartRequest = UploadPartRequest.builder() .bucket(bucketName) .key(key) .uploadId(uploadId) .checksumAlgorithm(algorithm) // Checksum specified on each part. .partNumber(partNumber) .build(); UploadPartResponse partResponse = s3Client.uploadPart( uploadPartRequest, RequestBody.fromByteBuffer(bb)); CompletedPart part = CompletedPart.builder() .partNumber(partNumber) .checksumCRC32(partResponse.checksumCRC32()) // Provide the calculated checksum. .eTag(partResponse.eTag()) .build(); completedParts.add(part); bb.clear(); position += read; partNumber++; } } catch (IOException e) { System.err.println(e.getMessage()); } // Complete the multipart upload. s3Client.completeMultipartUpload(b -> b .bucket(bucketName) .key(key) .uploadId(uploadId) .multipartUpload(CompletedMultipartUpload.builder().parts(completedParts).build())); }

完整示例的代码测试位于 GitHub 代码示例存储库中。

下载对象

使用 getObject 方法下载对象时,在以下情况下 SDK 会自动验证校验和:如果适用于 GetObjectRequest 的生成器的 checksumMode 方法设置为 ChecksumMode.ENABLED

以下代码段中的请求引导 SDK 通过计算校验和并比较值来验证响应中的校验和。

public GetObjectResponse getObjectWithChecksum() { return s3Client.getObject(b -> b .bucket(bucketName) .key(key) .checksumMode(ChecksumMode.ENABLED)) .response(); }
注意

如果上传对象时没有使用校验和,则不会进行验证。

其他校验和计算选项

注意

为了验证已传输数据的数据完整性以及识别任何传输错误,我们建议用户为校验和计算选项保留 SDK 默认设置。默认情况下,SDK 会为许多 S3 操作(包括 PutObjectGetObject)添加这项重要检查。

但如果您希望在使用 Amazon S3 时仅进行最少的校验和验证,则可以通过更改默认配置设置来禁用许多检查。

除非必要,否则请禁用自动校验和计算

对于支持校验和的操作(例如 PutObjectGetObject),您可以禁用 SDK 的自动校验和计算。但某些 S3 操作需要校验和计算;您不能为这些操作禁用校验和计算。

SDK 为请求的有效载荷和响应的有效载荷的校验和计算提供了单独的设置。

下面的列表说明了可用于在不同范围内更大限度地减少校验和计算的设置。

  • 所有应用程序范围 - 通过更改环境变量或共享 AWS configcredentials 文件的配置文件中的设置,所有应用程序都可以使用这些设置。除非在应用程序或服务客户端范围内已覆盖,否则这些设置会影响所有 AWS SDK 应用程序中的所有服务客户端。

    • 在配置文件中添加设置:

      [default] request_checksum_calculation = WHEN_REQUIRED response_checksum_validation = WHEN_REQUIRED
    • 添加环境变量:

      AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED AWS_RESPONSE_CHECKSUM_VALIDATION=WHEN_REQUIRED
  • 当前应用程序范围 - 您可以将 Java 系统属性 aws.requestChecksumCalculation 设置为 WHEN_REQUIRED,以便限制校验和计算。响应的相应系统属性为 aws.responseChecksumValidation

    除非在服务客户端创建期间已覆盖,否则这些设置会影响应用程序中的所有 SDK 服务客户端。

    在应用程序启动时设置系统属性:

    import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.checksums.RequestChecksumCalculation; import software.amazon.awssdk.core.checksums.ResponseChecksumValidation; import software.amazon.awssdk.services.s3.S3Client; class DemoClass { public static void main(String[] args) { System.setProperty(SdkSystemSetting.AWS_REQUEST_CHECKSUM_CALCULATION.property(), // Resolves to "aws.requestChecksumCalculation". "WHEN_REQUIRED"); System.setProperty(SdkSystemSetting.AWS_RESPONSE_CHECKSUM_VALIDATION.property(), // Resolves to "aws.responseChecksumValidation". "WHEN_REQUIRED"); S3Client s3Client = S3Client.builder().build(); // Use s3Client. } }
  • 单个 S3 服务客户端范围 - 您可以使用生成器方法配置单个 S3 服务客户端,使其仅计算最少必要数量的校验和:

    import software.amazon.awssdk.core.checksums.RequestChecksumCalculation; import software.amazon.awssdk.services.s3.S3Client; public class RequiredChecksums { public static void main(String[] args) { S3Client s3 = S3Client.builder() .requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED) .responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED) .build(); // Use s3Client. } // ... }

使用 LegacyMd5Plugin 以简化 MD5 兼容性

随着版本 2.30.0 引入对 CRC32 校验和的支持,SDK 停止计算所需操作的 MD5 校验和。

如果您的 S3 操作仍需要传统的 MD5 校验和行为,则可以使用 SDK 版本 2.31.32 中发布的 LegacyMd5Plugin

当您需要与依赖传统 MD5 校验和行为的应用程序保持兼容时,尤其是在使用与 S3 兼容的第三方存储提供程序 [例如与 S3A 文件系统连接器(Apache Spark、Iceberg)一起使用的存储提供程序] 时,LegacyMd5Plugin 特别有用。

要使用 LegacyMd5Plugin,请将其添加到 S3 客户端生成器中:

// For synchronous S3 client. S3Client s3Client = S3Client.builder() .addPlugin(LegacyMd5Plugin.create()) .build(); // For asynchronous S3 client. S3AsyncClient asyncClient = S3AsyncClient.builder() .addPlugin(LegacyMd5Plugin.create()) .build();

如果您希望仅为需要校验和的操作添加 MD5 校验和,同时跳过为那些“支持但非必需校验和”的操作添加 SDK 默认校验和,则可以将 ClientBuilder 选项 requestChecksumCalculationresponseChecksumValidation 设置为 WHEN_REQUIRED。这样就只会将 SDK 默认校验和添加到需要校验和的操作中:

// Use the `LegacyMd5Plugin` with `requestChecksumCalculation` and `responseChecksumValidation` set to WHEN_REQUIRED. S3AsyncClient asyncClient = S3AsyncClient.builder() .addPlugin(LegacyMd5Plugin.create()) .requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED) .responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED) .build();

当使用与 S3 兼容的第三方存储系统时,此配置特别有用,这些存储系统可能不完全支持较新的校验和算法,但某些操作仍需要 MD5 校验和。