Amazon Simple Storage Service
开发人员指南 (API 版本 2006-03-01)
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 Amazon AWS 入门

使用客户端加密保护数据

客户端加密 是在将数据发送到 Amazon S3 之前加密数据的行为。要启用客户端加密,您可以选择以下方法:

  • 使用 AWS KMS 托管客户主密钥

  • 使用客户端主密钥

以下 AWS 开发工具包支持客户端加密:

选项 1:使用 AWS KMS 托管客户主密钥 (CMK)

当使用 AWS KMS 托管客户主密钥启用客户端数据加密时,应提供 AWS KMS 客户主密钥 ID (CMK ID)。

  • 上传对象时 — 通过使用 CMK ID,客户端首先将请求发送到 AWS Key Management Service (AWS KMS) 以获取可用于加密对象数据的密钥。AWS KMS 将返回一个随机生成的数据加密密钥的两个版本:

    • 客户端用于加密对象数据的纯文本版本

    • 客户端将作为对象元数据上传到 Amazon S3 的同一数据加密密钥的密码 blob

    注意

    客户端将为其上传的每个对象获取一个唯一的数据加密密钥。

  • 下载对象时 — 客户端首先从 Amazon S3 下载加密的对象以及作为对象元数据存储的数据加密密钥的密码 blob 版本。然后,客户端将密码 blob 发送到 AWS KMS 以获取密钥的纯文本版本,以便让客户端解密对象数据。

有关 AWS KMS 的更多信息,请参阅什么是 AWS Key Management Service? (在 AWS Key Management Service Developer Guide 中)。

以下示例通过将 AWS KMS 与AWS SDK for Java结合使用来将对象上传到 Amazon S3。该示例在将数据上传到 Amazon S3 之前,使用 KMS 托管客户主密钥 (CMK) 在客户端上加密数据。如果您已经有一个 CMK,则可通过在示例代码中指定 kms_cmk_id 变量的值来使用该 CMK。如果没有 CMK,或需要另一个 CMK,则可以通过 Java API 生成一个。该示例演示如何生成 CMK。

有关密钥材料的更多信息,请参阅在 AWS Key Management Service (AWS KMS) 中导入密钥材料。有关创建和测试有效示例的说明,请参阅测试 Amazon S3 Java 代码示例

import java.io.ByteArrayOutputStream; import java.io.IOException; import com.amazonaws.AmazonServiceException; import com.amazonaws.SdkClientException; import com.amazonaws.auth.profile.ProfileCredentialsProvider; import com.amazonaws.regions.RegionUtils; import com.amazonaws.services.kms.AWSKMS; import com.amazonaws.services.kms.AWSKMSClientBuilder; import com.amazonaws.services.kms.model.CreateKeyResult; import com.amazonaws.services.s3.AmazonS3Encryption; import com.amazonaws.services.s3.AmazonS3EncryptionClientBuilder; import com.amazonaws.services.s3.model.CryptoConfiguration; import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider; import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.S3ObjectInputStream; public class UploadObjectKMSKey { public static void main(String[] args) throws IOException { String bucketName = "*** Bucket name ***"; String keyName = "*** Object key name ***"; String clientRegion = "*** Client region ***"; String kms_cmk_id = "***AWS KMS customer master key ID***"; int readChunkSize = 4096; try { // Optional: If you don't have a KMS key (or need another one), // create one. This example creates a key with AWS-created // key material. AWSKMS kmsClient = AWSKMSClientBuilder.standard() .withCredentials(new ProfileCredentialsProvider()) .withRegion(clientRegion) .build(); CreateKeyResult keyResult = kmsClient.createKey(); kms_cmk_id = keyResult.getKeyMetadata().getKeyId(); // Create the encryption client. KMSEncryptionMaterialsProvider materialProvider = new KMSEncryptionMaterialsProvider(kms_cmk_id); CryptoConfiguration cryptoConfig = new CryptoConfiguration() .withAwsKmsRegion(RegionUtils.getRegion(clientRegion)); AmazonS3Encryption encryptionClient = AmazonS3EncryptionClientBuilder.standard() .withCredentials(new ProfileCredentialsProvider()) .withEncryptionMaterials(materialProvider) .withCryptoConfiguration(cryptoConfig) .withRegion(clientRegion).build(); // Upload an object using the encryption client. String origContent = "S3 Encrypted Object Using KMS-Managed Customer Master Key."; int origContentLength = origContent.length(); encryptionClient.putObject(bucketName, keyName, origContent); // Download the object. The downloaded object is still encrypted. S3Object downloadedObject = encryptionClient.getObject(bucketName, keyName); S3ObjectInputStream input = downloadedObject.getObjectContent(); // Decrypt and read the object and close the input stream. byte[] readBuffer = new byte[readChunkSize]; ByteArrayOutputStream baos = new ByteArrayOutputStream(readChunkSize); int bytesRead = 0; int decryptedContentLength = 0; while ((bytesRead = input.read(readBuffer)) != -1) { baos.write(readBuffer, 0, bytesRead); decryptedContentLength += bytesRead; } input.close(); // Verify that the original and decrypted contents are the same size. System.out.println("Original content length: " + origContentLength); System.out.println("Decrypted content length: " + decryptedContentLength); } catch(AmazonServiceException e) { // The call was transmitted successfully, but Amazon S3 couldn't process // it, so it returned an error response. e.printStackTrace(); } catch(SdkClientException e) { // Amazon S3 couldn't be contacted for a response, or the client // couldn't parse the response from Amazon S3. e.printStackTrace(); } } }

选项 2:使用客户端主密钥

本节演示如何使用客户端主密钥执行客户端数据加密。

重要

客户端主密钥和未加密的数据从来不会发送到 AWS。务必安全地管理加密密钥。如果您丢失了加密密钥,将无法解密数据。

以下是具体工作原理:

  • 上传对象时 — 将客户端主密钥提供给 Amazon S3 加密客户端。该客户端仅使用该主密钥来加密客户端随机生成的数据加密密钥。该过程的工作方式如下所示:

    1. Amazon S3 加密客户端在本地生成一个一次性对称密钥 (也称为“数据加密密钥”或“数据密钥”)。它使用数据密钥加密单个 Amazon S3 对象的数据。该客户端将为每个对象生成一个单独的数据密钥。

    2. 该客户端使用您提供的主密钥来加密数据加密密钥。客户端会将加密的数据密钥及其材料说明作为对象元数据的一部分上传。该客户端利用材料描述来确定要用于解密的客户端主密钥。

    3. 该客户端将加密数据上传到 Amazon S3 并在 Amazon S3 中将加密数据密钥保存为对象元数据 (x-amz-meta-x-amz-key)。

  • 下载对象时 — 该客户端从 Amazon S3 下载加密的对象。通过使用对象元数据中的材料说明,该客户端将确定要用于解密数据密钥的主密钥。该客户端将使用该主密钥解密数据密钥,然后使用该数据密钥对对象进行解密。

您提供的客户端主密钥可以是对称密钥,也可以是公有/私有密钥对。以下示例演示如何使用这两种密钥。

有关更多信息,请参阅使用AWS SDK for Java和 Amazon S3 进行的客户端数据加密

注意

首次使用加密 API 时,如果您收到密码加密错误消息,则您的 JDK 版本可能带有一个 Java Cryptography Extension (JCE) 法律辖区策略文件,该文件将加密和解密转换的最大密钥长度限制为 128 位。AWS 开发工具包要求的最大密钥长度为 256 位。要检查您的最大密钥长度,请使用 javax.crypto.Cipher 类的 getMaxAllowedKeyLength() 方法。要取消密钥长度限制,请安装位于 Java SE 下载页面上的 Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy File。

以下示例演示如何执行以下任务:

  • 生成 256 位 AES 密钥

  • 在文件系统中保存和加载 AES 密钥

  • 将数据发送到 Amazon S3 之前在客户端上使用 AES 密钥加密数据

  • 使用 AES 密钥解密从 Amazon S3 接收的数据

  • 验证解密数据是否与原始数据相同

有关创建和测试有效示例的说明,请参阅测试 Amazon S3 Java 代码示例

import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import com.amazonaws.AmazonServiceException; import com.amazonaws.SdkClientException; import com.amazonaws.auth.profile.ProfileCredentialsProvider; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3EncryptionClientBuilder; import com.amazonaws.services.s3.model.EncryptionMaterials; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.StaticEncryptionMaterialsProvider; public class S3ClientSideEncryptionSymMasterKey { public static void main(String[] args) throws Exception { String clientRegion = "*** Client region ***"; String bucketName = "*** Bucket name ***"; String objectKeyName = "*** Object key name ***"; String masterKeyDir = System.getProperty("java.io.tmpdir"); String masterKeyName = "secret.key"; // Generate a symmetric 256-bit AES key. KeyGenerator symKeyGenerator = KeyGenerator.getInstance("AES"); symKeyGenerator.init(256); SecretKey symKey = symKeyGenerator.generateKey(); // To see how it works, save and load the key to and from the file system. saveSymmetricKey(masterKeyDir, masterKeyName, symKey); symKey = loadSymmetricAESKey(masterKeyDir, masterKeyName, "AES"); try { // Create the Amazon S3 encryption client. EncryptionMaterials encryptionMaterials = new EncryptionMaterials(symKey); AmazonS3 s3EncryptionClient = AmazonS3EncryptionClientBuilder.standard() .withCredentials(new ProfileCredentialsProvider()) .withEncryptionMaterials(new StaticEncryptionMaterialsProvider(encryptionMaterials)) .withRegion(clientRegion) .build(); // Upload a new object. The encryption client automatically encrypts it. byte[] plaintext = "S3 Object Encrypted Using Client-Side Symmetric Master Key.".getBytes(); s3EncryptionClient.putObject(new PutObjectRequest(bucketName, objectKeyName, new ByteArrayInputStream(plaintext), new ObjectMetadata())); // Download and decrypt the object. S3Object downloadedObject = s3EncryptionClient.getObject(bucketName, objectKeyName); byte[] decrypted = com.amazonaws.util.IOUtils.toByteArray(downloadedObject.getObjectContent()); // Verify that the data that you downloaded is the same as the original data. System.out.println("Plaintext: " + new String(plaintext)); System.out.println("Decrypted text: " + new String(decrypted)); } catch(AmazonServiceException e) { // The call was transmitted successfully, but Amazon S3 couldn't process // it, so it returned an error response. e.printStackTrace(); } catch(SdkClientException e) { // Amazon S3 couldn't be contacted for a response, or the client // couldn't parse the response from Amazon S3. e.printStackTrace(); } } private static void saveSymmetricKey(String masterKeyDir, String masterKeyName, SecretKey secretKey) throws IOException { X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(secretKey.getEncoded()); FileOutputStream keyOutputStream = new FileOutputStream(masterKeyDir + File.separator + masterKeyName); keyOutputStream.write(x509EncodedKeySpec.getEncoded()); keyOutputStream.close(); } private static SecretKey loadSymmetricAESKey(String masterKeyDir, String masterKeyName, String algorithm) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { // Read the key from the specified file. File keyFile = new File(masterKeyDir + File.separator + masterKeyName); FileInputStream keyInputStream = new FileInputStream(keyFile); byte[] encodedPrivateKey = new byte[(int) keyFile.length()]; keyInputStream.read(encodedPrivateKey); keyInputStream.close(); // Reconstruct and return the master key. return new SecretKeySpec(encodedPrivateKey, "AES"); } }

以下示例演示如何执行以下任务:

  • 生成 1024 位 RSA 密钥对

  • 在文件系统中保存和加载 RSA 密钥

  • 将数据发送到 Amazon S3 之前在客户端上使用 RSA 密钥加密数据

  • 使用 RSA 密钥解密从 Amazon S3 接收的数据

  • 验证解密数据是否与原始数据相同

有关创建和测试有效示例的说明,请参阅测试 Amazon S3 Java 代码示例

import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import com.amazonaws.AmazonServiceException; import com.amazonaws.SdkClientException; import com.amazonaws.auth.profile.ProfileCredentialsProvider; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3EncryptionClientBuilder; import com.amazonaws.services.s3.model.EncryptionMaterials; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.StaticEncryptionMaterialsProvider; import com.amazonaws.util.IOUtils; public class S3ClientSideEncryptionAsymmetricMasterKey { public static void main(String[] args) throws Exception { String clientRegion = "*** Client region ***"; String bucketName = "*** Bucket name ***"; String objectKeyName = "*** Key name ***"; String rsaKeyDir = System.getProperty("java.io.tmpdir"); String publicKeyName = "public.key"; String privateKeyName = "private.key"; // Generate a 1024-bit RSA key pair. KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA"); keyGenerator.initialize(1024, new SecureRandom()); KeyPair origKeyPair = keyGenerator.generateKeyPair(); // To see how it works, save and load the key pair to and from the file system. saveKeyPair(rsaKeyDir, publicKeyName, privateKeyName, origKeyPair); KeyPair keyPair = loadKeyPair(rsaKeyDir, publicKeyName, privateKeyName, "RSA"); try { // Create the encryption client. EncryptionMaterials encryptionMaterials = new EncryptionMaterials(keyPair); AmazonS3 s3EncryptionClient = AmazonS3EncryptionClientBuilder.standard() .withCredentials(new ProfileCredentialsProvider()) .withEncryptionMaterials(new StaticEncryptionMaterialsProvider(encryptionMaterials)) .withRegion(clientRegion) .build(); // Create a new object. byte[] plaintext = "S3 Object Encrypted Using Client-Side Asymmetric Master Key.".getBytes(); S3Object object = new S3Object(); object.setKey(objectKeyName); object.setObjectContent(new ByteArrayInputStream(plaintext)); ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(plaintext.length); // Upload the object. The encryption client automatically encrypts it. PutObjectRequest putRequest = new PutObjectRequest(bucketName, object.getKey(), object.getObjectContent(), metadata); s3EncryptionClient.putObject(putRequest); // Download and decrypt the object. S3Object downloadedObject = s3EncryptionClient.getObject(bucketName, object.getKey()); byte[] decrypted = IOUtils.toByteArray(downloadedObject.getObjectContent()); // Verify that the data that you downloaded is the same as the original data. System.out.println("Plaintext: " + new String(plaintext)); System.out.println("Decrypted text: " + new String(decrypted)); } catch(AmazonServiceException e) { // The call was transmitted successfully, but Amazon S3 couldn't process // it, so it returned an error response. e.printStackTrace(); } catch(SdkClientException e) { // Amazon S3 couldn't be contacted for a response, or the client // couldn't parse the response from Amazon S3. e.printStackTrace(); } } private static void saveKeyPair(String dir, String publicKeyName, String privateKeyName, KeyPair keyPair) throws IOException { PrivateKey privateKey = keyPair.getPrivate(); PublicKey publicKey = keyPair.getPublic(); // Write the public key to the specified file. X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded()); FileOutputStream publicKeyOutputStream = new FileOutputStream(dir + File.separator + publicKeyName); publicKeyOutputStream.write(x509EncodedKeySpec.getEncoded()); publicKeyOutputStream.close(); // Write the private key to the specified file. PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded()); FileOutputStream privateKeyOutputStream = new FileOutputStream(dir + File.separator + privateKeyName); privateKeyOutputStream.write(pkcs8EncodedKeySpec.getEncoded()); privateKeyOutputStream.close(); } private static KeyPair loadKeyPair(String dir, String publicKeyName, String privateKeyName, String algorithm) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { // Read the public key from the specified file. File publicKeyFile = new File(dir + File.separator + publicKeyName); FileInputStream publicKeyInputStream = new FileInputStream(publicKeyFile); byte[] encodedPublicKey = new byte[(int) publicKeyFile.length()]; publicKeyInputStream.read(encodedPublicKey); publicKeyInputStream.close(); // Read the private key from the specified file. File privateKeyFile = new File(dir + File.separator + privateKeyName); FileInputStream privateKeyInputStream = new FileInputStream(privateKeyFile); byte[] encodedPrivateKey = new byte[(int) privateKeyFile.length()]; privateKeyInputStream.read(encodedPrivateKey); privateKeyInputStream.close(); // Convert the keys into a key pair. KeyFactory keyFactory = KeyFactory.getInstance(algorithm); X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey); PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey); PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); return new KeyPair(publicKey, privateKey); } }