Amazon Simple Storage Service
개발자 안내서 (API 버전 2006-03-01)

클라이언트측 암호화를 사용하여 데이터 보호

클라이언트 측 암호화는 Amazon S3로 보내기 전에 데이터를 암호화하는 것을 가리킵니다. 클라이언트 측 암호화를 활성화하기 위한 다음 옵션이 있습니다.

  • AWS KMS 관리형 고객 마스터 키 사용

  • 클라이언트측 마스터 키 사용

다음 AWS SDK에서 클라이언트측 암호화를 지원합니다.

옵션 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 버전과 함께 다운로드합니다. 그런 다음 클라이언트에서 AWS KMS로 암호 BLOB를 보내 객체 데이터의 암호 해독을 위해 키의 일반 텍스트 버전을 가져옵니다.

AWS KMS에 대한 자세한 내용은 What is the AWS Key Management Service?(AWS Key Management Service Developer Guide)를 참조하십시오.

다음 예제에서는 AWS SDK for Java에서 AWS KMS를 사용하여 Amazon S3로 객체를 업로드합니다. 이 예제에서는 AWS KMS 관리형 고객 마스터 키(CMK)를 사용하여 Amazon S3로 업로드하기 전에 클라이언트 측 데이터를 암호화합니다. CMK가 이미 있는 경우에는 샘플 코드에서 kms_cmk_id 변수의 값을 지정하여 이 키를 사용할 수 있습니다. 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로 업로드하고 암호화된 데이터 키를 객체 메타데이터(x-amz-meta-x-amz-key)로 Amazon S3에 저장합니다.

  • 객체를 다운로드할 때 - 클라이언트에서 먼저 Amazon S3의 암호화된 객체를 다운로드합니다. 클라이언트에서 먼저 객체 메타데이터의 구성 요소 설명을 사용하여 데이터 키의 암호 해독에 어떤 마스터 키를 사용할지를 결정합니다. 클라이언트는 해당 마스터 키를 사용하여 데이터 키를 암호 해독하고 나서 데이터 키를 사용하여 객체를 암호 해독합니다.

사용자가 제공하는 클라이언트 측 마스터 키는 대칭 키 또는 퍼블릭/프라이빗 키 페어일 수 있습니다. 다음 예제에서는 두 가지 유형의 키를 사용하는 방법을 보여줍니다.

자세한 내용은 Client-Side Data Encryption with the AWS SDK for Java and Amazon S3를 참조하십시오.

참고

처음으로 암호화 API를 사용할 때 암호 암호화 오류 메시지가 표시되는 경우 사용 중인 버전의 JDK에 암호화 및 암호 해독 변환에 대한 최대 키 길이를 128비트로 제한하는 Java Cryptography Extension(JCE) Jurisdiction Policy File이 있을 수 있습니다. AWS SDK에서는 최대 키 길이가 256비트여야 합니다. 최대 키 길이를 확인하려면 javax.crypto.Cipher 클래스의 getMaxAllowedKeyLength() 메서드를 사용하십시오. 키 길이 제한을 제거하려면 Java Cryptography Extension(JCE) Unlimited Strength Jurisdiction Policy File을 Java SE 다운로드 페이지에서 설치하십시오.

다음 예에서는 이러한 작업을 수행하는 방법을 보여줍니다.

  • 256비트 AES 키 생성

  • AES 키를 저장한 다음 파일 시스템에/에서 로드

  • AES 키를 사용하여 Amazon S3에 데이터를 보내기 전에 클라이언트 측에서 데이터 암호화

  • 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 키를 저장한 다음 파일 시스템에/에서 로드

  • RSA 키를 사용하여 Amazon S3에 데이터를 보내기 전에 클라이언트 측에서 데이터 암호화

  • 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); } }