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 はランダムに生成された 2 つのバージョンのデータ暗号化キーを返します。

    • クライアントがオブジェクトデータの暗号化に使用するプレーンテキストバージョン。

    • クライアントが Amazon S3 にオブジェクトメタデータとしてアップロードする、同じデータ暗号化キーの暗号 BLOB。

    注記

    クライアントは、アップロードするオブジェクトごとに一意のデータ暗号化キーを取得します。

  • オブジェクトのダウンロード時 — クライアントは、暗号化されたオブジェクトと、オブジェクトメタデータとして保存されているデータ暗号化キーの暗号 BLOB バージョンを Amazon S3 からダウンロードします。次に、オブジェクトデータを復号できるように、AWS KMS に暗号 BLOB を送信して、キーのプレーンテキストバージョンを取得します。

AWS KMS の詳細については、「AWS Key Management Service とは何ですか?」 (AWS Key Management Service Developer Guide) を参照してください。

次の例では、AWS KMS と AWS SDK for Java を使用して、Amazon S3 にオブジェクトをアップロードします。KMS で管理されたカスタマーマスターキー (CMK) を使用して、Amazon S3 にアップロードする前にクライアント側でデータを暗号化します。すでに 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 暗号化クライアントは、1 回限りの使用の対称キー (つまり、データ暗号化キーまたはデータキー) をローカルに生成します。データキーを使用して、単一の Amazon S3 オブジェクトのデータが暗号化されます。クライアントは、オブジェクトごとに個別のデータキーを生成します。

    2. クライアントは、提供されたマスターキーを使用して、データ暗号化キーを暗号化します。クライアントは、暗号化されたデータキーとその素材の説明を、オブジェクトメタデータの一部としてアップロードします。クライアントは、素材の説明を使用して、復号に使用するクライアント側のマスターキーを決定します。

    3. クライアントは暗号化されたデータを Amazon S3 にアップロードし、暗号化されたデータキーをオブジェクトメタデータ (x-amz-meta-x-amz-key) として Amazon S3 に保存します。

  • オブジェクトのダウンロード時 — クライアントは、Amazon S3 から暗号化されたオブジェクトをダウンロードします。クライアントは、オブジェクトのメタデータ内の素材の説明を使用して、まず、データキーの復号にどのマスターキーを使用するかを判断します。クライアントはそのマスターキーを使用してデータキーを復号し、そのデータキーを使用してオブジェクトを復号します。

提供するクライアント側のマスターキーは、対称キーにすることも、パブリック/プライベートのキーペアにすることもできます。次の例では、両方の種類のキーを使用する方法を示します。

詳細については、「AWS SDK for Java および Amazon S3 によるクライアント側のデータ暗号化」を参照してください。

注記

初めて暗号化 API を使用するときに暗号の暗号化エラーメッセージが表示される場合、使用する JDK のバージョンに、暗号化/復号変換に使用する最大キー長を 128 ビットに制限する Java Cryptography Extension (JCE) 管轄ポリシーファイルが含まれている可能性があります。AWS SDK では最大 256 ビットのキー長を必要とします。最大キー長を確認するには、javax.crypto.Cipher クラスの getMaxAllowedKeyLength() メソッドを使用します。キー長の制限を削除するには、Java SE ダウンロードページから Java Cryptography Extension(JCE) 無制限強度の管轄ポリシーファイルをインストールします。

次の例は、以下のタスクを実行する方法を示しています。

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