升级AWS SDK至2.31.16后MD5值不符,求原因及适配方案
我有一个使用software.amazon.awssdk依赖管理AWS存储桶内容(复制、移动、删除)的项目。以下是上传对象至存储桶的方法,其中S3Client属于software.amazon.awssdk.services.s3类,原本通过文件MD5值确保上传无损坏:
public String putS3Object(S3Client s3Client, String bucketName, String key, String md5Checksum, String content) { PutObjectRequest metadataPutRequest = PutObjectRequest.builder() .bucket(bucketName) .key(key) .contentMD5(md5Checksum) .build(); byte[] bytes = org.apache.commons.codec.binary.Base64.decodeBase64(content); PutObjectResponse putObjectResponse = s3Client.putObject(metadataPutRequest, RequestBody.fromBytes(bytes)); return putObjectResponse.eTag(); }
MD5校验值通过以下方法计算:
public static String generateFileChecksum(Path path) { try (var inputStream = Files.newInputStream(path)) { return Base64.encodeBase64String(DigestUtils.md5(inputStream)); } catch (IOException e) { throw new IllegalStateException("Failure computing checksum for file "+ path.toFile().getAbsolutePath(), e); } }
此前代码运行正常,但将AWS SDK从2.21.41版本升级到2.31.16版本后,S3Client.putObject方法返回的ETag(MD5校验值)发生了变化。不清楚原因,也不知道如何重构代码适配该变化,想了解最新版本库对MD5计算的处理逻辑。
AWS SDK for Java 2.x在2.22.x版本之后调整了默认的上传分块逻辑:
- 旧版本(2.21.x及之前)中,当上传内容小于默认分块阈值(通常是8MB)时,会使用单块上传,此时ETag就是内容的MD5值(Base64解码后的十六进制字符串)。
- 新版本(2.22.x及之后)默认启用了智能分块上传(将
TransferManager的逻辑整合到了客户端默认行为中),即使内容小于原阈值,SDK也可能自动使用分块上传。分块上传的ETag是各分块MD5值的组合再做MD5,最后加上分块数量的后缀(比如abc123-1),和单块上传的MD5完全不同。
另外,SDK新版本对contentMD5参数的校验逻辑也有调整:如果手动传入了contentMD5,但实际上传方式是分块上传,S3服务端会忽略这个参数,因为分块上传的完整性校验是通过各分块的MD5和整体的x-amz-content-sha256来保证的。
根据需求,有两种适配方式:
方式一:强制使用单块上传,保持ETag为原MD5格式
如果需要继续让ETag等于内容的MD5值,可以在构建PutObjectRequest时显式禁用分块上传:
public String putS3Object(S3Client s3Client, String bucketName, String key, String md5Checksum, String content) { PutObjectRequest metadataPutRequest = PutObjectRequest.builder() .bucket(bucketName) .key(key) .contentMD5(md5Checksum) // 禁用分块上传,强制单块上传 .disableChunkedEncoding(true) .build(); byte[] bytes = org.apache.commons.codec.binary.Base64.decodeBase64(content); PutObjectResponse putObjectResponse = s3Client.putObject(metadataPutRequest, RequestBody.fromBytes(bytes)); return putObjectResponse.eTag(); }
注意:这种方式仅适用于上传内容小于S3单块上传的最大限制(5GB)的场景。
方式二:适配SDK默认的分块上传逻辑,放弃ETag与MD5的绑定
如果不需要ETag保持原MD5格式,而是依赖SDK的自动分块上传优化,可以移除手动传入的contentMD5参数,转而依赖SDK自动处理的完整性校验:
public String putS3Object(S3Client s3Client, String bucketName, String key, String content) { PutObjectRequest metadataPutRequest = PutObjectRequest.builder() .bucket(bucketName) .key(key) // 移除手动传入的contentMD5,SDK会自动处理SHA-256校验 .build(); byte[] bytes = org.apache.commons.codec.binary.Base64.decodeBase64(content); PutObjectResponse putObjectResponse = s3Client.putObject(metadataPutRequest, RequestBody.fromBytes(bytes)); return putObjectResponse.eTag(); }
此时ETag会是分块上传的组合值,但SDK会自动保证上传内容的完整性,无需手动处理MD5校验。
补充:正确计算分块上传的ETag(如果需要验证)
如果需要自己计算分块上传的ETag,可以按照以下逻辑:
- 将内容按SDK默认分块大小(默认8MB)拆分
- 计算每个分块的MD5值
- 将所有分块的MD5字节数组拼接在一起,再计算整体的MD5值
- 将整体MD5转为十六进制字符串,加上
-分块数量的后缀
不过通常不需要手动计算,SDK和S3服务端会自动处理完整性校验。
内容的提问来源于stack exchange,提问作者Space Cadet

