diff --git a/src/examples/README.md b/src/examples/README.md new file mode 100644 index 000000000..b6e778219 --- /dev/null +++ b/src/examples/README.md @@ -0,0 +1,114 @@ +# AWS Encryption SDK Examples + +This section features examples that show you +how to use the AWS Encryption SDK. +We demonstrate how to use the encryption and decryption APIs +and how to set up some common configuration patterns. + +## APIs + +The AWS Encryption SDK provides two high-level APIs: +one-step APIs that process the entire operation in memory +and streaming APIs. + +You can find examples that demonstrate these APIs +in the [`examples`](./java/com/amazonaws/crypto/examples) directory. + +## Configuration + +To use the encryption and decryption APIs, +you need to describe how you want the library to protect your data keys. +You can do this by configuring +[keyrings](#keyrings) or [cryptographic materials managers](#cryptographic-materials-managers), +or by configuring [master key providers](#master-key-providers). +These examples will show you how to use the configuration tools that we include for you +and how to create some of your own. +We start with AWS KMS examples, then show how to use other wrapping keys. + +* Using AWS Key Management Service (AWS KMS) + * How to use one AWS KMS CMK + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java) + * How to use multiple AWS KMS CMKs in different regions + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java) + * How to decrypt when you don't know the CMK + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java) + * How to decrypt within a region + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java) + * How to decrypt with a preferred region but failover to others + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java) +* Using raw wrapping keys + * How to use a raw AES wrapping key + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/rawaes/RawAes.java) + * How to use a raw RSA wrapping key + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsa.java) + * How to encrypt with a raw RSA public key wrapping key without access to the private key + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/rawrsa/PublicPrivateKeySeparate.java) + * How to use a raw RSA wrapping key when the key is DER encoded + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsaDerEncoded.java) +* Combining wrapping keys + * How to combine AWS KMS with an offline escrow key + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java) + +### Keyrings + +Keyrings are the most common way for you to configure the AWS Encryption SDK. +They determine how the AWS Encryption SDK protects your data. +You can find these examples in ['examples/keyring`](./java/com/amazonaws/crypto/examples/keyring). + +### Cryptographic Materials Managers + +Keyrings define how your data keys are protected, +but there is more going on here than just protecting data keys. + +Cryptographic materials managers give you higher-level controls +over how the AWS Encryption SDK protects your data. +This can include things like +enforcing the use of certain algorithm suites or encryption context settings, +reusing data keys across messages, +or changing how you interact with keyrings. +You can find these examples in +[`examples/crypto_materials_manager`](./java/com/amazonaws/crypto/examples/cryptomaterialsmanager). + +### Master Key Providers + +Before there were keyrings, there were master key providers. +Master key providers were the original configuration structure +that we provided for defining how you want to protect your data keys. +Keyrings provide a simpler experience and often more powerful configuration options, +but if you need to use master key providers, +need help migrating from master key providers to keyrings, +or simply want to see the difference between these configuration experiences, +you can find these examples in [`examples/masterkeyprovider`](./java/com/amazonaws/crypto/examples/masterkeyprovider). + +## Legacy + +This section includes older examples, +including examples of using master keys and master key providers. +You can use them as a reference, +but we recommend looking at the newer examples, which explain the preferred ways of using this library. +You can find these examples in [`examples/legacy`](./java/com/amazonaws/crypto/examples/legacy). + +# Writing Examples + +If you want to contribute a new example, that's awesome! +To make sure that your example is tested in our CI, +please make sure that it meets the following requirements: + +1. The example MUST be a distinct class in the [`examples`](./java/com/amazonaws/crypto/examples) directory. +1. Each example file MUST contain exactly one example. +1. Each example file MUST contain a static method called `run` that runs the example. +1. If your `run` method needs any of the following inputs, + the parameters MUST have the following types: + * `com.amazonaws.encryptionsdk.kms.AwsKmsCmkId` : A single AWS KMS CMK ARN. + * NOTE: You can assume that automatically discovered credentials have + `kms:GenerateDataKey`, `kms:Encrypt`, and `kms:Decrypt` permissions on this CMK. + * `List` : + A list of AWS KMS CMK ARNs to use for encrypting and decrypting data keys. + * NOTE: You can assume that automatically discovered credentials have + `kms:Encrypt` and `kms:Decrypt` permissions on these CMKs. + * `byte[]` : Plaintext data to encrypt. + * `java.io.File` : A path to a file containing plaintext to encrypt. + * NOTE: You can assume that you have write access to the parent directory + and that anything you do in that directory will be cleaned up + by our test runners. +1. Any additional parameters MUST be optional and nullable and not of the same type as the above parameters. diff --git a/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java b/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java deleted file mode 100644 index ce1dcca3c..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoResult; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.EncryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; - -/** - *

- * Encrypts and then decrypts data using an AWS Key Management Service (AWS KMS) customer master key. - * - *

- * Arguments: - *

    - *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master - * key (CMK), see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html - *
- */ -public class BasicEncryptionExample { - - private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); - - public static void main(final String[] args) { - encryptAndDecrypt(AwsKmsCmkId.fromString(args[0])); - } - - static void encryptAndDecrypt(final AwsKmsCmkId keyArn) { - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a KMS keyring. Supply the key ARN for the generator key - // that generates a data key. While using a key ARN is a best practice, - // for encryption operations you can also use an alias name or alias ARN. - final Keyring keyring = StandardKeyrings.awsKms(keyArn); - - // 3. Create an encryption context - // - // Most encrypted data should have an associated encryption context - // to protect integrity. This sample uses placeholder values. - // - // For more information see: - // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management - final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); - - // 4. Encrypt the data with the keyring and encryption context - final AwsCryptoResult encryptResult = crypto.encrypt( - EncryptRequest.builder() - .keyring(keyring) - .encryptionContext(encryptionContext) - .plaintext(EXAMPLE_DATA).build()); - final byte[] ciphertext = encryptResult.getResult(); - - // 5. Decrypt the data. You can use the same keyring to encrypt and decrypt, but for decryption - // the key IDs must be in the key ARN format. - final AwsCryptoResult decryptResult = crypto.decrypt( - DecryptRequest.builder() - .keyring(keyring) - .ciphertext(ciphertext).build()); - - // 6. To verify the CMK that was actually used in the decrypt operation, inspect the keyring trace. - if(!decryptResult.getKeyringTrace().getEntries().get(0).getKeyName().equals(keyArn.toString())) { - throw new IllegalStateException("Wrong key ID!"); - } - - // 7. To verify that the encryption context used to decrypt the data was the encryption context you expected, - // examine the encryption context in the result. This helps to ensure that you decrypted the ciphertext that - // you intended. - // - // When verifying, test that your expected encryption context is a subset of the actual encryption context, - // not an exact match. The Encryption SDK adds the signing key to the encryption context when appropriate. - assert decryptResult.getEncryptionContext().get("ExampleContextKey").equals("ExampleContextValue"); - - // 8. Verify that the decrypted plaintext matches the original plaintext - assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); - } -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java deleted file mode 100644 index bb12f737d..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.EncryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; - -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.Arrays; - -/** - *

- * Encrypts data using both KMS and an asymmetric key pair. - * - *

- * Arguments: - *

    - *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master - * key (CMK), see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html - *
- * - * You might use AWS Key Management Service (KMS) for most encryption and decryption operations, but - * still want the option of decrypting your data offline independently of KMS. This sample - * demonstrates one way to do this. - * - * The sample encrypts data under both a KMS customer master key (CMK) and an "escrowed" RSA key pair - * so that either key alone can decrypt it. You might commonly use the KMS CMK for decryption. However, - * at any time, you can use the private RSA key to decrypt the ciphertext independent of KMS. - * - * This sample uses a RawRsaKeyring to generate a RSA public-private key pair - * and saves the key pair in memory. In practice, you would store the private key in a secure offline - * location, such as an offline HSM, and distribute the public key to your development team. - * - */ -public class EscrowedEncryptExample { - private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); - - public static void main(final String[] args) throws GeneralSecurityException { - escrowEncryptAndDecrypt(AwsKmsCmkId.fromString(args[0])); - } - - static void escrowEncryptAndDecrypt(AwsKmsCmkId kmsArn) throws GeneralSecurityException { - // This sample generates a new random key for each operation. - // In practice, you would distribute the public key and save the private key in secure storage. - final KeyPair escrowKeyPair = generateEscrowKeyPair(); - - // Encrypt the data under both a KMS Key and an escrowed RSA Key - byte[] encryptedData = standardEncrypt(kmsArn, escrowKeyPair.getPublic()); - - // Decrypt the data using the KMS Key - byte[] standardDecryptedData = standardDecrypt(kmsArn, encryptedData); - - // Decrypt the data using the escrowed RSA Key - byte[] escrowedDecryptedData = escrowDecrypt(encryptedData, escrowKeyPair.getPrivate()); - - // Verify both decrypted data instances are the same as the original plaintext - assert Arrays.equals(standardDecryptedData, EXAMPLE_DATA); - assert Arrays.equals(escrowedDecryptedData, EXAMPLE_DATA); - } - - private static byte[] standardEncrypt(final AwsKmsCmkId kmsArn, final PublicKey publicEscrowKey) { - // Encrypt with the KMS CMK and the escrowed public key - - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a KMS keyring, supplying the keyArn as the generator for generating a data key. - final Keyring kmsKeyring = StandardKeyrings.awsKms(kmsArn); - - // 3. Instantiate a RawRsaKeyring - // Because the user does not have access to the private escrow key, - // they do not provide the private key parameter. - final Keyring rsaKeyring = StandardKeyrings.rawRsaBuilder() - .keyNamespace("Escrow") - .keyName("Escrow") - .publicKey(publicEscrowKey) - .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) - .build(); - - // 4. Combine the providers into a single MultiKeyring - final Keyring keyring = StandardKeyrings.multi(kmsKeyring, rsaKeyring); - - // 5. Encrypt the data with the keyring. - // To simplify the code, we omit the encryption context. Production code should always - // use an encryption context. For an example, see the other SDK samples. - return crypto.encrypt(EncryptRequest.builder() - .keyring(keyring) - .plaintext(EXAMPLE_DATA).build()) - .getResult(); - } - - private static byte[] standardDecrypt(final AwsKmsCmkId kmsArn, final byte[] cipherText) { - // Decrypt with the KMS CMK - - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a KMS keyring, supplying the keyArn as the generator for generating a data key. - final Keyring kmsKeyring = StandardKeyrings.awsKms(kmsArn); - - // 4. Decrypt the data with the keyring. - // To simplify the code, we omit the encryption context. Production code should always - // use an encryption context. For an example, see the other SDK samples. - return crypto.decrypt(DecryptRequest.builder() - .keyring(kmsKeyring) - .ciphertext(cipherText).build()).getResult(); - } - - private static byte[] escrowDecrypt(final byte[] cipherText, final PrivateKey privateEscrowKey) { - // You can decrypt the stream using only the private key. - // This method does not call KMS. - - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a RawRsaKeyring using the escrowed private key - final Keyring rsaKeyring = StandardKeyrings.rawRsaBuilder() - .keyNamespace("Escrow") - .keyName("Escrow") - .privateKey(privateEscrowKey) - .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) - .build(); - - // 3. Decrypt the data with the keyring - // To simplify the code, we omit the encryption context. Production code should always - // use an encryption context. For an example, see the other SDK samples. - return crypto.decrypt(DecryptRequest.builder() - .keyring(rsaKeyring) - .ciphertext(cipherText).build()).getResult(); - } - - private static KeyPair generateEscrowKeyPair() throws GeneralSecurityException { - final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); - kg.initialize(4096); // Escrow keys should be very strong - return kg.generateKeyPair(); - } -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingDefaults.java b/src/examples/java/com/amazonaws/crypto/examples/FileStreamingDefaults.java new file mode 100644 index 000000000..ae9b97e5f --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/FileStreamingDefaults.java @@ -0,0 +1,135 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoInputStream; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.CreateDecryptingInputStreamRequest; +import com.amazonaws.encryptionsdk.CreateEncryptingInputStreamRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.util.IOUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * This example shows how to use the streaming encrypt and decrypt APIs when working with files. + *

+ * One benefit of using the streaming API is that + * we can check the encryption context before we start decrypting. + *

+ * In this example, we use an AWS KMS customer master key (CMK), + * but you can use other key management options with the AWS Encryption SDK. + * For examples that demonstrate how to use other key management configurations, + * see the 'keyring' and 'masterkeyprovider' directories. + */ +public class FileStreamingDefaults { + + /** + * Demonstrate an encrypt/decrypt cycle using the streaming encrypt/decrypt APIs with files. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintextFile Plaintext file to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final File sourcePlaintextFile) throws IOException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // We assume that you can also write to the directory containing the plaintext file, + // so that is where we will put all of the results. + final File encryptedFile = new File(sourcePlaintextFile.getPath() + ".encrypted"); + final File decryptedFile = new File(sourcePlaintextFile.getPath() + ".decrypted"); + encryptedFile.deleteOnExit(); + decryptedFile.deleteOnExit(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Create the encrypting input stream with the keyring and encryption context. + // Because the file might be too large to load into memory, + // we stream the data, instead of loading it all at once. + try (final AwsCryptoInputStream encryptingStream = awsEncryptionSdk.createEncryptingInputStream( + CreateEncryptingInputStreamRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .inputStream(new FileInputStream(sourcePlaintextFile)).build())) { + + // Encrypt the data and write the ciphertext to the encrypted file. + try (FileOutputStream out = new FileOutputStream(encryptedFile)) { + IOUtils.copy(encryptingStream, out); + } + } + + // Demonstrate that the ciphertext and plaintext are different. + assert !compareFiles(sourcePlaintextFile, encryptedFile); + + // Create the decrypting input stream with the keyring. + try (final AwsCryptoInputStream decryptingStream = awsEncryptionSdk.createDecryptingInputStream( + CreateDecryptingInputStreamRequest.builder() + .keyring(keyring) + .inputStream(new FileInputStream(encryptedFile)).build())) { + + // Check the encryption context before we start decrypting. + // + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + final AwsCryptoResult decryptResult = decryptingStream.getAwsCryptoResult(); + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + + // Now that we are more confident that we will decrypt the right message, + // we can start decrypting. + try (FileOutputStream out = new FileOutputStream(decryptedFile)) { + IOUtils.copy(decryptingStream, out); + } + } + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert compareFiles(sourcePlaintextFile, decryptedFile); + } + + private static boolean compareFiles(File file1, File file2) throws IOException { + if (file1.length() != file2.length()) { + return false; + } + + try (BufferedReader file1Reader = Files.newBufferedReader(file1.toPath()); + BufferedReader file2Reader = Files.newBufferedReader(file2.toPath())) { + String file1Line; + String file2Line; + + while ((file1Line = file1Reader.readLine()) != null && + (file2Line = file2Reader.readLine()) != null) { + if (!Objects.equals(file1Line, file2Line)) { + return false; + } + } + + return true; + } + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java b/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java deleted file mode 100644 index 94cd38853..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoInputStream; -import com.amazonaws.encryptionsdk.CreateDecryptingInputStreamRequest; -import com.amazonaws.encryptionsdk.CreateEncryptingInputStreamRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import com.amazonaws.util.IOUtils; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.security.SecureRandom; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; - -/** - *

- * Encrypts and then decrypts a file under a random key. - * - *

- * Arguments: - *

    - *
  1. Name of file containing plaintext data to encrypt - *
- * - *

- * This program demonstrates using a standard Java {@link SecretKey} object in a {@link Keyring} to - * encrypt and decrypt streaming data. - */ -public class FileStreamingExample { - - public static void main(String[] args) throws IOException { - final File srcFile = new File(args[0]); - final File encryptedFile = new File(args[1]); - final File decryptedFile = new File(args[2]); - - encryptAndDecrypt(srcFile, encryptedFile, decryptedFile); - - } - - static void encryptAndDecrypt(final File srcFile, final File encryptedFile, final File decryptedFile) throws IOException { - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Get an encryption key. In this example, we generate a random key. - // In practice, you would get a key from an existing key store. - final SecretKey cryptoKey = generateEncryptKey(); - - // 3. Instantiate a RawAesKeyring using the random key - final Keyring keyring = StandardKeyrings.rawAesBuilder() - .keyNamespace("Example") - .keyName("RandomKey") - .wrappingKey(cryptoKey) - .build(); - - // 4. Create an encryption context - // - // Most encrypted data should have an associated encryption context - // to protect integrity. This sample uses placeholder values. - // - // For more information see: - // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management - final Map encryptionContext = Collections.singletonMap("Example", "FileStreaming"); - - // 5. Create the encrypting input stream with the keyring and encryption context. - // Because the file might be too large to load into memory, - // we stream the data, instead of loading it all at once. - try (final AwsCryptoInputStream encryptingStream = crypto.createEncryptingInputStream( - CreateEncryptingInputStreamRequest.builder() - .keyring(keyring) - .encryptionContext(encryptionContext) - .inputStream(new FileInputStream(srcFile)).build())) { - - // 6. Copy the encrypted data into the encrypted file. - try (FileOutputStream out = new FileOutputStream(encryptedFile)) { - IOUtils.copy(encryptingStream, out); - } - } - - // 7. Create the decrypting input stream with the keyring. - try (final AwsCryptoInputStream decryptingStream = crypto.createDecryptingInputStream( - CreateDecryptingInputStreamRequest.builder() - .keyring(keyring) - .inputStream(new FileInputStream(encryptedFile)).build())) { - - // 8. Verify that the encryption context that was used to decrypt the data is the one that you expect. - // This helps to ensure that the ciphertext that you decrypted was the one that you intended. - // - // When verifying, test that your expected encryption context is a subset of the actual encryption context, - // not an exact match. When appropriate, the Encryption SDK adds the signing key to the encryption context. - assert "FileStreaming".equals(decryptingStream.getAwsCryptoResult().getEncryptionContext().get("Example")); - - // 9. Copy the plaintext data to a file - try (FileOutputStream out = new FileOutputStream(decryptedFile)) { - IOUtils.copy(decryptingStream, out); - } - } - - // 10. Compare the decrypted file to the original - compareFiles(decryptedFile, srcFile); - } - - /** - * In practice, this key would be saved in a secure location. - * In this example, we generate a new random key for each operation. - */ - private static SecretKey generateEncryptKey() { - SecureRandom rnd = new SecureRandom(); - byte[] rawKey = new byte[16]; // 128 bits - rnd.nextBytes(rawKey); - return new SecretKeySpec(rawKey, "AES"); - } - - private static void compareFiles(File file1, File file2) throws IOException { - assert file1.length() == file2.length(); - - try (BufferedReader file1Reader = Files.newBufferedReader(file1.toPath()); - BufferedReader file2Reader = Files.newBufferedReader(file2.toPath())) { - String file1Line; - String file2Line; - - while ((file1Line = file1Reader.readLine()) != null && - (file2Line = file2Reader.readLine()) != null) { - assert Objects.equals(file1Line, file2Line); - } - } - } - -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/InMemoryStreamingDefaults.java b/src/examples/java/com/amazonaws/crypto/examples/InMemoryStreamingDefaults.java new file mode 100644 index 000000000..94586f7a6 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/InMemoryStreamingDefaults.java @@ -0,0 +1,103 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoInputStream; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.CreateDecryptingInputStreamRequest; +import com.amazonaws.encryptionsdk.CreateEncryptingInputStreamRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.util.IOUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example shows how to use the streaming encrypt and decrypt APIs on data in memory. + *

+ * One benefit of using the streaming API is that + * we can check the encryption context before we start decrypting. + *

+ * In this example, we use an AWS KMS customer master key (CMK), + * but you can use other key management options with the AWS Encryption SDK. + * For examples that demonstrate how to use other key management configurations, + * see the 'keyring' and 'masterkeyprovider' directories. + */ +public class InMemoryStreamingDefaults { + + /** + * Demonstrate an encrypt/decrypt cycle using the streaming encrypt/decrypt APIs in-memory. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) throws IOException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + ByteArrayInputStream inputStream = new ByteArrayInputStream(sourcePlaintext); + + // Create the encrypting input stream with the keyring and encryption context. + final AwsCryptoInputStream encryptingStream = awsEncryptionSdk.createEncryptingInputStream( + CreateEncryptingInputStreamRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .inputStream(inputStream).build()); + + // Encrypt the plaintext and write the results into the ciphertext. + ByteArrayOutputStream ciphertext = new ByteArrayOutputStream(); + IOUtils.copy(encryptingStream, ciphertext); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext.toByteArray(), sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoInputStream decryptingStream = awsEncryptionSdk.createDecryptingInputStream( + CreateDecryptingInputStreamRequest.builder() + .keyring(keyring) + .inputStream(new ByteArrayInputStream(ciphertext.toByteArray())).build()); + + // Check the encryption context before we start decrypting. + // + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + final AwsCryptoResult decryptResult = decryptingStream.getAwsCryptoResult(); + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + + // Now that we are more confident that we will decrypt the right message, + // we can start decrypting. + ByteArrayOutputStream decrypted = new ByteArrayOutputStream(); + IOUtils.copy(decryptingStream, decrypted); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted.toByteArray(), sourcePlaintext); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/OneStepDefaults.java b/src/examples/java/com/amazonaws/crypto/examples/OneStepDefaults.java new file mode 100644 index 000000000..5a9c4bc13 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/OneStepDefaults.java @@ -0,0 +1,83 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example shows how to use the one-step encrypt and decrypt APIs. + *

+ * In this example, we use an AWS KMS customer master key (CMK), + * but you can use other key management options with the AWS Encryption SDK. + * For examples that demonstrate how to use other key management configurations, + * see the 'keyring' and 'masterkeyprovider' directories. + */ +public class OneStepDefaults { + + /** + * Demonstrate an encrypt/decrypt cycle using the one-step encrypt/decrypt APIs. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/OneStepUnsigned.java b/src/examples/java/com/amazonaws/crypto/examples/OneStepUnsigned.java new file mode 100644 index 000000000..5d457d2f3 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/OneStepUnsigned.java @@ -0,0 +1,97 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example shows how to specify an algorithm suite + * when using the one-step encrypt and decrypt APIs. + *

+ * In this example, we use an AWS KMS customer master key (CMK), + * but you can use other key management options with the AWS Encryption SDK. + * For examples that demonstrate how to use other key management configurations, + * see the 'keyring' and 'masterkeyprovider' directories. + *

+ * The default algorithm suite includes a message-level signature + * that protects you from an attacker who has *decrypt* but not *encrypt* capability + * for a wrapping key that you used when encrypting a message + * under multiple wrapping keys. + *

+ * However, if all of your readers and writers have the same permissions, + * then this additional protection does not always add value. + * This example shows you how to select another algorithm suite + * that has all of the other properties of the default suite + * but does not include a message-level signature. + */ +public class OneStepUnsigned { + + /** + * Demonstrate requesting a specific algorithm suite through the one-step encrypt/decrypt APIs. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK and specify the algorithm suite that we want to use. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + awsEncryptionSdk.setEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/RawAesKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/RawAesKeyringExample.java deleted file mode 100644 index 9af19ca54..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/RawAesKeyringExample.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoResult; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.EncryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.nio.charset.StandardCharsets; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -/** - *

- * Encrypts and then decrypts data using the Raw AES keyring. - */ -public class RawAesKeyringExample { - - private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); - - public static void main(final String[] args) { - encryptAndDecrypt(); - } - - static void encryptAndDecrypt() { - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Get an encryption key. In this example, we generate a random key. - // In practice, you would get a key from an existing key store - final SecretKey cryptoKey = generateEncryptKey(); - - // 3. Instantiate a Raw AES keyring with the encryption key - final Keyring keyring = StandardKeyrings.rawAesBuilder() - .keyNamespace("ExampleKeyNamespace") - .keyName("ExampleKeyName") - .wrappingKey(cryptoKey).build(); - - // 4. Create an encryption context - // - // Most encrypted data should have an associated encryption context - // to protect integrity. This sample uses placeholder values. - // - // For more information see: - // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management - final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); - - // 5. Encrypt the data with the keyring and encryption context - final AwsCryptoResult encryptResult = crypto.encrypt(EncryptRequest.builder() - .keyring(keyring) - .encryptionContext(encryptionContext) - .plaintext(EXAMPLE_DATA).build()); - final byte[] ciphertext = encryptResult.getResult(); - - // 6. Decrypt the data - final AwsCryptoResult decryptResult = crypto.decrypt(DecryptRequest.builder() - .keyring(keyring) - .ciphertext(ciphertext).build()); - - // 7. Verify that the encryption context that was used to decrypt the data is the one that you expect. - // This helps to ensure that the ciphertext that you decrypted was the one that you intended. - // - // When verifying, test that your expected encryption context is a subset of the actual encryption context, - // not an exact match. When appropriate, the Encryption SDK adds the signing key to the encryption context. - assert decryptResult.getEncryptionContext().get("ExampleContextKey").equals("ExampleContextValue"); - - // 8. Verify that the decrypted plaintext matches the original plaintext - assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); - } - - /** - * In practice, this key would be saved in a secure location. - * For this demo, we generate a new random key for each operation. - */ - private static SecretKey generateEncryptKey() { - SecureRandom rnd = new SecureRandom(); - byte[] rawKey = new byte[16]; // 128 bits - rnd.nextBytes(rawKey); - return new SecretKeySpec(rawKey, "AES"); - } -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java deleted file mode 100644 index b2cf3010e..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoResult; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; - -import java.security.KeyPair; - -/** - *

- * Decrypts data using the Raw RSA keyring. - */ -public class RawRsaKeyringDecryptExample { - - public static byte[] decrypt(byte[] ciphertext, KeyPair keyPair) { - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a Raw RSA keyring with the private key - final Keyring keyring = StandardKeyrings.rawRsaBuilder() - .keyNamespace("ExampleKeyNamespace") - .keyName("ExampleKeyName") - .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) - .privateKey(keyPair.getPrivate()).build(); - - // 3. Decrypt the ciphertext with the keyring - final AwsCryptoResult decryptResult = crypto.decrypt(DecryptRequest.builder() - .keyring(keyring) - .ciphertext(ciphertext).build()); - - // 4. Verify that the encryption context that was used to decrypt the data is the one that you expect. - // This helps to ensure that the ciphertext that you decrypted was the one that you intended. - // - // When verifying, test that your expected encryption context is a subset of the actual encryption context, - // not an exact match. When appropriate, the Encryption SDK adds the signing key to the encryption context. - assert decryptResult.getEncryptionContext().get("ExampleContextKey").equals("ExampleContextValue"); - - // 5. Return the decrypted byte array result - return decryptResult.getResult(); - } -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java deleted file mode 100644 index 82a8001f4..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoResult; -import com.amazonaws.encryptionsdk.EncryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; - -import java.nio.charset.StandardCharsets; -import java.security.PublicKey; -import java.util.Collections; -import java.util.Map; - -/** - * Encrypts data using the Raw RSA keyring. - */ -public class RawRsaKeyringEncryptExample { - - static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); - - public static byte[] encrypt(PublicKey publicKey) { - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a Raw RSA keyring with the public key - final Keyring keyring = StandardKeyrings.rawRsaBuilder() - .keyNamespace("ExampleKeyNamespace") - .keyName("ExampleKeyName") - .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) - .publicKey(publicKey).build(); - - // 3. Create an encryption context - // - // Most encrypted data should have an associated encryption context - // to protect integrity. This sample uses placeholder values. - // - // For more information see: - // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management - final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); - - // 4. Encrypt the data with the keyring and encryption context - final AwsCryptoResult encryptResult = crypto.encrypt(EncryptRequest.builder() - .keyring(keyring) - .encryptionContext(encryptionContext) - .plaintext(EXAMPLE_DATA).build()); - - // 5. Return the encrypted byte array result - return encryptResult.getResult(); - } -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java new file mode 100644 index 000000000..0e6ddcdf5 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java @@ -0,0 +1,139 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.auth.profile.ProfileCredentialsProvider; +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; +import com.amazonaws.services.kms.AWSKMS; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * By default, the KMS keyring uses a client supplier that + * supplies a client with the same configuration for every region. + * If you need different behavior, you can write your own client supplier. + *

+ * You might use this + * if you need different credentials in different AWS regions. + * This might be because you are crossing partitions (ex: "aws" and "aws-cn") + * or if you are working with regions that have separate authentication silos + * like "ap-east-1" and "me-south-1". + *

+ * This example shows how to create a client supplier + * that will supply KMS clients with valid credentials for the target region + * even when working with regions that need different credentials. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For another example of how to use the KMS keyring with a custom client configuration, + * see the {@link CustomKmsClientConfig} example. + *

+ * For examples of how to use the KMS Discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, {@link DiscoveryDecryptInRegionOnly}, + * and {@link DiscoveryDecryptWithPreferredRegions} examples. + */ +public class CustomClientSupplier { + + static class MultiPartitionClientSupplier implements AwsKmsClientSupplier { + + private final AwsKmsClientSupplier chinaSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + .credentialsProvider(new ProfileCredentialsProvider("china")).build(); + private final AwsKmsClientSupplier middleEastSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + .credentialsProvider(new ProfileCredentialsProvider("middle-east")).build(); + private final AwsKmsClientSupplier hongKongSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + .credentialsProvider(new ProfileCredentialsProvider("hong-kong")).build(); + private final AwsKmsClientSupplier defaultSupplier = StandardAwsKmsClientSuppliers.defaultBuilder().build(); + + /** + * Returns a client for the requested region. + * + * @param regionId The AWS region + * @return The AWSKMS client + */ + @Override + public AWSKMS getClient(String regionId) { + if (regionId.startsWith("cn-")) { + return chinaSupplier.getClient(regionId); + } else if (regionId.startsWith("me-")) { + return middleEastSupplier.getClient(regionId); + } else if (regionId.equals("ap-east-1")) { + return hongKongSupplier.getClient(regionId); + } else { + return defaultSupplier.getClient(regionId); + } + } + } + + /** + * Demonstrate an encrypt/decrypt cycle using a KMS keyring with a custom client supplier. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKmsBuilder() + .generatorKeyId(awsKmsCmk) + .awsKmsClientSupplier(new MultiPartitionClientSupplier()) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java new file mode 100644 index 000000000..fd3af280d --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java @@ -0,0 +1,118 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.internal.VersionInfo; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * By default, the KMS keyring uses the default configurations + * for all KMS clients and uses the default discoverable credentials. + * If you need to change this configuration, + * you can configure the client supplier. + *

+ * This example shows how to use custom-configured clients with the KMS keyring. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For another example of how to use the KMS keyring with a custom client configuration, + * see the {@link CustomKmsClientConfig} example. + *

+ * For examples of how to use the KMS Discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, {@link DiscoveryDecryptInRegionOnly}, + * and {@link DiscoveryDecryptWithPreferredRegions} examples. + */ +public class CustomKmsClientConfig { + + /** + * Demonstrate an encrypt/decrypt cycle using a KMS keyring with custom KMS client configuration. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Prepare your custom configuration values. + // + // Set your custom connection timeout value. + // https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/ClientConfiguration.html + final ClientConfiguration clientConfiguration = new ClientConfiguration() + .withConnectionTimeout(10000) // 10,000 milliseconds + .withUserAgentSuffix(VersionInfo.USER_AGENT); + + // Use your custom configuration values to configure your client supplier. + // For this example we will just use the default credentials provider + // but if you need to, you can set a custom credentials provider as well. + final AwsKmsClientSupplier clientSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + .clientConfiguration(clientConfiguration) + .build(); + + // Create the keyring that determines how your data keys are protected, + // providing the client supplier that you created. + final Keyring keyring = StandardKeyrings.awsKmsBuilder() + .generatorKeyId(awsKmsCmk) + .awsKmsClientSupplier(clientSupplier) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java new file mode 100644 index 000000000..64b031887 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java @@ -0,0 +1,102 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * When you give the KMS keyring specific key IDs it will use those CMKs and nothing else. + * This is true both on encrypt and on decrypt. + * However, sometimes you need more flexibility on decrypt, + * especially when you don't know which CMKs were used to encrypt a message. + * To address this need, you can use a KMS discovery keyring. + * The KMS discovery keyring does nothing on encrypt + * but attempts to decrypt *any* data keys that were encrypted under a KMS CMK. + *

+ * This example shows how to configure and use a KMS discovery keyring. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For examples of how to use the KMS keyring with custom client configurations, + * see the {@link CustomClientSupplier} + * and {@link CustomKmsClientConfig} examples. + *

+ * For examples of how to use the KMS discovery keyring on decrypt, + * see the {@link DiscoveryDecryptInRegionOnly}, + * and {@link DiscoveryDecryptWithPreferredRegions} examples. + */ +public class DiscoveryDecrypt { + + /** + * Demonstrate configuring a KMS discovery keyring for decryption. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring encryptKeyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Create a KMS discovery keyring to use on decrypt. + final Keyring decryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder().build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(encryptKeyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the KMS discovery keyring. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(decryptKeyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java new file mode 100644 index 000000000..ad545cbec --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java @@ -0,0 +1,118 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.arn.Arn; +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Collections.singleton; + +/** + * When you give the KMS keyring specific key IDs it will use those CMKs and nothing else. + * This is true both on encrypt and on decrypt. + * However, sometimes you need more flexibility on decrypt, + * especially if you don't know which CMK was used to encrypt a message. + * To address this need, you can use a KMS discovery keyring. + * The KMS discovery keyring does nothing on encrypt + * but attempts to decrypt *any* data keys that were encrypted under a KMS CMK. + *

+ * However, sometimes you need to be a *bit* more restrictive than that. + * To address this need, you can use a client supplier that restricts the regions a KMS keyring can talk to. + *

+ * This example shows how to configure and use a KMS regional discovery keyring that is restricted to one region. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For examples of how to use the KMS keyring with custom client configurations, + * see the {@link CustomClientSupplier} + * and {@link CustomKmsClientConfig} examples. + *

+ * For examples of how to use the KMS discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, + * and {@link DiscoveryDecryptWithPreferredRegions} examples. + */ +public class DiscoveryDecryptInRegionOnly { + + /** + * Demonstrate configuring a KMS keyring to only work within a single region. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring encryptKeyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Extract the region from the CMK ARN. + final String decryptRegion = Arn.fromString(awsKmsCmk.toString()).getRegion(); + + // Create the KMS discovery keyring that we will use on decrypt. + // + // The client supplier that we specify here will only supply clients for the specified region. + // The keyring only attempts to decrypt data keys if it can get a client for that region, + // so this keyring will now ignore any data keys that were encrypted under a CMK in another region. + final Keyring decryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder() + .awsKmsClientSupplier(StandardAwsKmsClientSuppliers.allowRegionsBuilder(singleton(decryptRegion)).build()) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(encryptKeyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the KMS discovery keyring. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(decryptKeyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java new file mode 100644 index 000000000..5068395d2 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java @@ -0,0 +1,134 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; +import com.amazonaws.services.kms.AWSKMSClientBuilder; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Collections.singleton; + +/** + * When you give the KMS keyring specific key IDs it will use those CMKs and nothing else. + * This is true both on encrypt and on decrypt. + * However, sometimes you need more flexibility on decrypt, + * especially if you might not know beforehand which CMK was used to encrypt a message. + * To address this need, you can use a KMS discovery keyring. + * The KMS discovery keyring will do nothing on encrypt + * but will attempt to decrypt *any* data keys that were encrypted under a KMS CMK. + *

+ * However, sometimes you need to be a *bit* more restrictive than that. + * To address this need, you can use a client supplier to restrict what regions a KMS keyring can talk to. + *

+ * A more complex but more common use-case is that you would *prefer* to stay within a region, + * but you would rather make calls to other regions than fail to decrypt the message. + * In this case, you want a keyring that will try to decrypt data keys in this region first, + * then try other regions. + *

+ * This example shows how to configure and use a multi-keyring with the KMS keyring + * to prefer the current AWS region while also failing over to other AWS regions. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For examples of how to use the KMS keyring with custom client configurations, + * see the {@link CustomClientSupplier} + * and {@link CustomKmsClientConfig} examples. + *

+ * For examples of how to use the KMS discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, + * and {@link DiscoveryDecryptInRegionOnly} examples. + */ +public class DiscoveryDecryptWithPreferredRegions { + + /** + * Demonstrate configuring a keyring preferring a particular AWS region and failing over to others. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring encryptKeyring = StandardKeyrings.awsKms(awsKmsCmk); + + // To create our decrypt keyring, we need to know our current default AWS region. + final String localRegion = AWSKMSClientBuilder.standard().getRegion(); + + // Now, use that region name to create two KMS discovery keyrings: + // + // One that only works in the local region + final Keyring localRegionDecryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder() + .awsKmsClientSupplier(StandardAwsKmsClientSuppliers.allowRegionsBuilder(singleton(localRegion)).build()) + .build(); + // and one that will work in any other region but NOT the local region. + final Keyring otherRegionsDecryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder() + .awsKmsClientSupplier(StandardAwsKmsClientSuppliers.denyRegionsBuilder(singleton(localRegion)).build()) + .build(); + + // Finally, combine those two keyrings into a multi-keyring. + // + // The multi-keyring steps through its member keyrings in the order that you provide them, + // attempting to decrypt every encrypted data key with each keyring before moving on to the next keyring. + // Because of this, otherRegionsDecryptKeyring will not be called + // unless localRegionDecryptKeyring fails to decrypt every encrypted data key. + final Keyring decryptKeyring = StandardKeyrings.multi(localRegionDecryptKeyring, otherRegionsDecryptKeyring); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(encryptKeyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the multi-keyring. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(decryptKeyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java new file mode 100644 index 000000000..8eabf06c6 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java @@ -0,0 +1,122 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This example shows how to configure and use a KMS keyring with CMKs in multiple regions. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with a single CMK, + * see the {@link SingleCmk} example. + *

+ * For examples of how to use the KMS keyring with custom client configurations, + * see the {@link CustomClientSupplier} + * and {@link CustomKmsClientConfig} examples. + *

+ * For examples of how to use the KMS Discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, + * {@link DiscoveryDecryptInRegionOnly}, + * and {@link DiscoveryDecryptWithPreferredRegions} examples. + */ +public class MultipleRegions { + + /** + * Demonstrate an encrypt/decrypt cycle using a KMS keyring with CMKs in multiple regions. + * + * @param awsKmsGeneratorCmk The ARN of an AWS KMS CMK that protects data keys + * @param awsKmsAdditionalCmks Additional ARNs of secondary KMS CMKs + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsGeneratorCmk, final List awsKmsAdditionalCmks, byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that will encrypt your data keys under all requested CMKs. + final Keyring manyCmksKeyring = StandardKeyrings.awsKmsBuilder() + .generatorKeyId(awsKmsGeneratorCmk) + .keyIds(awsKmsAdditionalCmks) + .build(); + + // Create keyrings that each only use one of the CMKs. + // We will use these later to demonstrate that any of the CMKs can be used to decrypt the message. + // + // We provide these in "keyIds" rather than "generatorKeyId" + // so that these keyrings cannot be used to generate a new data key. + // We will only be using them on decrypt. + final Keyring singleCmkKeyringThatGenerated = StandardKeyrings.awsKmsBuilder() + .keyIds(Collections.singletonList(awsKmsGeneratorCmk)) + .build(); + final Keyring singleCmkKeyringThatEncrypted = StandardKeyrings.awsKmsBuilder() + .keyIds(Collections.singletonList(awsKmsAdditionalCmks.get(0))) + .build(); + + // Encrypt your plaintext data using the keyring that uses all requests CMKs. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(manyCmksKeyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Verify that the header contains the expected number of encrypted data keys (EDKs). + // It should contain one EDK for each CMK. + assert encryptResult.getHeaders().getEncryptedKeyBlobCount() == awsKmsAdditionalCmks.size() + 1; + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data separately using the single-CMK keyrings. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult1 = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(singleCmkKeyringThatGenerated) + .ciphertext(ciphertext).build()); + final byte[] decrypted1 = decryptResult1.getResult(); + final AwsCryptoResult decryptResult2 = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(singleCmkKeyringThatEncrypted) + .ciphertext(ciphertext).build()); + final byte[] decrypted2 = decryptResult2.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted1, sourcePlaintext); + assert Arrays.equals(decrypted2, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult1.getEncryptionContext().get(k)); + assert v.equals(decryptResult2.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java new file mode 100644 index 000000000..b77824e83 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java @@ -0,0 +1,92 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example shows how to configure and use a KMS keyring with a single KMS CMK. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For examples of how to use the KMS keyring with custom client configurations, + * see the {@link CustomClientSupplier} + * and {@link CustomKmsClientConfig} examples. + *

+ * For examples of how to use the KMS Discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, + * {@link DiscoveryDecryptInRegionOnly}, + * and {@link DiscoveryDecryptWithPreferredRegions} examples. + */ +public class SingleCmk { + + /** + * Demonstrate an encrypt/decrypt cycle using a KMS keyring with a single CMK. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java new file mode 100644 index 000000000..541c017cb --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java @@ -0,0 +1,149 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.multi; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * One use-case that we have seen customers need is + * the ability to enjoy the benefits of AWS KMS during normal operation + * but retain the ability to decrypt encrypted messages without access to AWS KMS. + * This example shows how you can use the multi-keyring to achieve this + * by combining a KMS keyring with a raw RSA keyring. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-multi-keyring + *

+ * For more examples of how to use the KMS keyring, see the keyring/awskms examples. + *

+ * For more examples of how to use the raw RSA keyring, see the keyring/rawrsa examples. + *

+ * In this example we generate a RSA keypair + * but in practice you would want to keep your private key in an HSM + * or other key management system. + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class AwsKmsWithEscrow { + + /** + * Demonstrate configuring a keyring to use an AWS KMS CMK and a RSA wrapping key. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) throws GeneralSecurityException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an RSA key pair to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + // The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + // https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + + // Create the encrypt keyring that only has access to the public key. + final Keyring escrowEncryptKeyring = StandardKeyrings.rawRsaBuilder() + // The key namespace and key name are defined by you + // and are used by the raw RSA keyring + // to determine whether it should attempt to decrypt + // an encrypted data key. + // + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + .keyNamespace("some managed raw keys") + .keyName("my RSA wrapping key") + .publicKey(keyPair.getPublic()) + // The padding scheme tells the raw RSA keyring + // how to use your wrapping key to encrypt data keys. + // + // We recommend using OAEP_SHA256_MGF1. + // You should not use PKCS1 unless you require it for backwards compatibility. + .paddingScheme(RsaPaddingScheme.OAEP_SHA256_MGF1) + .build(); + + // Create the decrypt keyring that has access to the private key. + final Keyring escrowDecryptKeyring = StandardKeyrings.rawRsaBuilder() + // The key namespace and key name MUST match the encrypt keyring. + .keyNamespace("some managed raw keys") + .keyName("my RSA wrapping key") + .privateKey(keyPair.getPrivate()) + // The padding scheme MUST match the encrypt keyring. + .paddingScheme(RsaPaddingScheme.OAEP_SHA256_MGF1) + .build(); + + // Create the KMS keyring that you will use from decryption during normal operations. + final Keyring kmsKeyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Combine the KMS keyring and the escrow encrypt keyring using the multi-keyring. + final Keyring encryptKeyring = StandardKeyrings.multi(kmsKeyring, escrowEncryptKeyring); + + // Encrypt your plaintext data using the multi-keyring. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(encryptKeyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Verify that the header contains the expected number of encrypted data keys (EDKs). + // It should contain one EDK for KMS and one for the escrow key. + assert encryptResult.getHeaders().getEncryptedKeyBlobCount() == 2; + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data separately using the KMS keyring and the escrow decrypt keyring. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptedKmsResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(kmsKeyring) + .ciphertext(ciphertext).build()); + final byte[] decryptedKms = decryptedKmsResult.getResult(); + final AwsCryptoResult decryptedEscrowResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(escrowDecryptKeyring) + .ciphertext(ciphertext).build()); + final byte[] decryptedEscrow = decryptedKmsResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decryptedKms, sourcePlaintext); + assert Arrays.equals(decryptedEscrow, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptedKmsResult.getEncryptionContext().get(k)); + assert v.equals(decryptedEscrowResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/rawaes/RawAes.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawaes/RawAes.java new file mode 100644 index 000000000..9d79f7440 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawaes/RawAes.java @@ -0,0 +1,101 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.rawaes; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This examples shows how to configure and use a raw AES keyring. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-aes-keyring + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class RawAes { + + /** + * Demonstrate an encrypt/decrypt cycle using a raw AES keyring. + * + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an AES key to use with your keyring. + // + // In practice, you should get this key from a secure key management system such as an HSM. + SecureRandom rnd = new SecureRandom(); + byte[] rawKey = new byte[32]; // 256 bits + rnd.nextBytes(rawKey); + SecretKey key = new SecretKeySpec(rawKey, "AES"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.rawAesBuilder() + // The key namespace and key name are defined by you + // and are used by the raw AES keyring + // to determine whether it should attempt to decrypt + // an encrypted data key. + // + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-aes-keyring + .keyNamespace("some managed raw keys") + .keyName("my AES wrapping key") + .wrappingKey(key) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/PublicPrivateKeySeparate.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/PublicPrivateKeySeparate.java new file mode 100644 index 000000000..0bc31b191 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/PublicPrivateKeySeparate.java @@ -0,0 +1,147 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.rawrsa; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * One of the benefits of asymmetric encryption + * is that you can encrypt with just the public key. + * This means that you can give someone the ability to encrypt + * without giving them the ability to decrypt. + *

+ * The raw RSA keyring supports encrypt-only operations + * when it only has access to a public key. + *

+ * This example shows how to construct and use the raw RSA keyring + * to encrypt with only the public key and decrypt with the private key. + *

+ * If your RSA key is in DER format, + * see the {@link RawRsaDerEncoded} example. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class PublicPrivateKeySeparate { + + /** + * Demonstrate an encrypt/decrypt cycle using separate public and private raw RSA keyrings. + * + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final byte[] sourcePlaintext) throws GeneralSecurityException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an RSA key pair to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + // The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + // https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + + // Create the keyring that determines how your data keys are protected. + // + // Create the encrypt keyring that only has access to the public key. + final Keyring publicKeyKeyring = StandardKeyrings.rawRsaBuilder() + // The key namespace and key name are defined by you + // and are used by the raw RSA keyring + // to determine whether it should attempt to decrypt + // an encrypted data key. + // + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + .keyNamespace("some managed raw keys") + .keyName("my RSA wrapping key") + .publicKey(keyPair.getPublic()) + // The padding scheme tells the raw RSA keyring + // how to use your wrapping key to encrypt data keys. + // + // We recommend using OAEP_SHA256_MGF1. + // You should not use PKCS1 unless you require it for backwards compatibility. + .paddingScheme(RawRsaKeyringBuilder.RsaPaddingScheme.OAEP_SHA256_MGF1) + .build(); + + // Create the decrypt keyring that has access to the private key. + final Keyring privateKeyKeyring = StandardKeyrings.rawRsaBuilder() + // The key namespace and key name MUST match the encrypt keyring. + .keyNamespace("some managed raw keys") + .keyName("my RSA wrapping key") + .privateKey(keyPair.getPrivate()) + // The padding scheme MUST match the encrypt keyring. + .paddingScheme(RawRsaKeyringBuilder.RsaPaddingScheme.OAEP_SHA256_MGF1) + .build(); + + // Encrypt your plaintext data using the encrypt keyring. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(publicKeyKeyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Try to decrypt your encrypted data using the *encrypt* keyring. + // This demonstrates that you cannot decrypt using the public key. + try { + awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(publicKeyKeyring) + .ciphertext(ciphertext) + .build()); + throw new AssertionError("The public key can never decrypt!"); + } catch (AwsCryptoException ex) { + // The public key cannot decrypt. + // Reaching this point means everything is working as expected. + } + + // Decrypt your encrypted data using the decrypt keyring. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(privateKeyKeyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsa.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsa.java new file mode 100644 index 000000000..3ca0a6c0e --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsa.java @@ -0,0 +1,112 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.rawrsa; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This examples shows how to configure and use a raw RSA keyring using a pre-loaded RSA key pair. + *

+ * If your RSA key is in DER format, + * see the {@link RawRsaDerEncoded} example. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class RawRsa { + + /** + * Demonstrate an encrypt/decrypt cycle using a raw RSA keyring. + * + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final byte[] sourcePlaintext) throws GeneralSecurityException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an RSA key pair to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + // The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + // https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.rawRsaBuilder() + // The key namespace and key name are defined by you + // and are used by the raw RSA keyring + // to determine whether it should attempt to decrypt + // an encrypted data key. + // + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + .keyNamespace("some managed raw keys") + .keyName("my RSA wrapping key") + .privateKey(keyPair.getPrivate()) + .publicKey(keyPair.getPublic()) + // The padding scheme tells the raw RSA keyring + // how to use your wrapping key to encrypt data keys. + // + // We recommend using OAEP_SHA256_MGF1. + // You should not use PKCS1 unless you require it for backwards compatibility. + .paddingScheme(RawRsaKeyringBuilder.RsaPaddingScheme.OAEP_SHA256_MGF1) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsaDerEncoded.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsaDerEncoded.java new file mode 100644 index 000000000..c74c80495 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsaDerEncoded.java @@ -0,0 +1,134 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.rawrsa; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; + +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * When you store RSA keys, you have to serialize them somehow. + *

+ * This example shows how to configure and use a raw RSA keyring using a DER-encoded RSA private key. + *

+ * The most commonly used encodings for RSA keys tend to be PEM and DER. + * For parsing PEM-encoded keys, see https://www.bouncycastle.org/docs/pkixdocs1.4/org/bouncycastle/openssl/PEMParser.html + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class RawRsaDerEncoded { + + /** + * Demonstrate an encrypt/decrypt cycle using a raw RSA keyring loaded from a DER-encoded key. + * + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final byte[] sourcePlaintext) throws GeneralSecurityException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an RSA key pair to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + // The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + // https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + + // Serialize the RSA keys to DER encoding. + // This or PEM encoding is likely to be what you get from your key management system in practice. + byte[] publicKeyEncoded = keyPair.getPublic().getEncoded(); + byte[] privateKeyEncoded = keyPair.getPrivate().getEncoded(); + + final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + + // Deserialize the RSA private key. + final PrivateKey privateKey = keyFactory.generatePrivate( + new PKCS8EncodedKeySpec(privateKeyEncoded)); + + // Deserialize the RSA public key. + final PublicKey publicKey = keyFactory.generatePublic( + new X509EncodedKeySpec(publicKeyEncoded)); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.rawRsaBuilder() + // The key namespace and key name are defined by you + // and are used by the raw RSA keyring + // to determine whether it should attempt to decrypt + // an encrypted data key. + // + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + .keyNamespace("some managed raw keys") + .keyName("my RSA wrapping key") + .privateKey(privateKey) + .publicKey(publicKey) + // The padding scheme tells the raw RSA keyring + // how to use your wrapping key to encrypt data keys. + // + // We recommend using OAEP_SHA256_MGF1. + // You should not use PKCS1 unless you require it for backwards compatibility. + .paddingScheme(RawRsaKeyringBuilder.RsaPaddingScheme.OAEP_SHA256_MGF1) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/legacy/BasicEncryptionExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/BasicEncryptionExample.java new file mode 100644 index 000000000..4093004ed --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/BasicEncryptionExample.java @@ -0,0 +1,81 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.legacy; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoResult; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; + +/** + *

+ * Encrypts and then decrypts data using an AWS KMS customer master key. + * NOTE: Master key providers are deprecated and replaced by keyrings. + * We keep these older examples as reference material, + * but we recommend that you use the new examples in examples/keyring + * The new examples reflect our current guidance for using the library. + *

+ * Arguments: + *

    + *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master + * key (CMK), see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
+ */ +public class BasicEncryptionExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + final String keyArn = args[0]; + + encryptAndDecrypt(keyArn); + } + + static void encryptAndDecrypt(final String keyArn) { + // 1. Instantiate the AWS Encryption SDK. + final AwsCrypto crypto = new AwsCrypto(); + + // 2. Instantiate a KMS master key provider. + final KmsMasterKeyProvider masterKeyProvider = KmsMasterKeyProvider.builder().withKeysForEncryption(keyArn).build(); + + // 3. Create an encryption context. + // + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 4. Encrypt the data. + final CryptoResult encryptResult = crypto.encryptData(masterKeyProvider, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 5. Decrypt the data. + final CryptoResult decryptResult = crypto.decryptData(masterKeyProvider, ciphertext); + + // 6. Before verifying the plaintext, verify that the customer master key that + // was used in the encryption operation was the one supplied to the master key provider. + if (!decryptResult.getMasterKeyIds().get(0).equals(keyArn)) { + throw new IllegalStateException("Wrong key ID!"); + } + + // 7. Also, verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. Because the + // SDK can add values to the encryption context, don't require that + // the entire context matches. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // 8. Verify that the decrypted plaintext matches the original plaintext. + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/legacy/EscrowedEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/EscrowedEncryptExample.java new file mode 100644 index 000000000..18286624f --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/EscrowedEncryptExample.java @@ -0,0 +1,164 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.legacy; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoOutputStream; +import com.amazonaws.encryptionsdk.MasterKeyProvider; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; +import com.amazonaws.util.IOUtils; + +/** + *

+ * Encrypts a file using both KMS and an asymmetric key pair. + * NOTE: Master key providers are deprecated and replaced by keyrings. + * We keep these older examples as reference material, + * but we recommend that you use the new examples in examples/keyring + * The new examples reflect our current guidance for using the library. + * + *

+ * Arguments: + *

    + *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master + * key (CMK), see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + * + *
  2. Name of file containing plaintext data to encrypt + *
+ * + * You might use AWS Key Management Service (KMS) for most encryption and decryption operations, but + * still want the option of decrypting your data offline independently of KMS. This sample + * demonstrates one way to do this. + * + * The sample encrypts data under both a KMS customer master key (CMK) and an "escrowed" RSA key pair + * so that either key alone can decrypt it. You might commonly use the KMS CMK for decryption. However, + * at any time, you can use the private RSA key to decrypt the ciphertext independent of KMS. + * + * This sample uses the JCEMasterKey class to generate a RSA public-private key pair + * and saves the key pair in memory. In practice, you would store the private key in a secure offline + * location, such as an offline HSM, and distribute the public key to your development team. + * + */ +public class EscrowedEncryptExample { + private static PublicKey publicEscrowKey; + private static PrivateKey privateEscrowKey; + + public static void main(final String[] args) throws Exception { + // This sample generates a new random key for each operation. + // In practice, you would distribute the public key and save the private key in secure + // storage. + generateEscrowKeyPair(); + + final String kmsArn = args[0]; + final String fileName = args[1]; + + standardEncrypt(kmsArn, fileName); + standardDecrypt(kmsArn, fileName); + + escrowDecrypt(fileName); + } + + private static void standardEncrypt(final String kmsArn, final String fileName) throws Exception { + // Encrypt with the KMS CMK and the escrowed public key + // 1. Instantiate the AWS Encryption SDK. + final AwsCrypto crypto = new AwsCrypto(); + + // 2. Instantiate a KMS master key provider. + final KmsMasterKeyProvider kms = new KmsMasterKeyProvider(kmsArn); + + // 3. Instantiate a JCE master key provider. + // Because the user does not have access to the private escrow key, + // they pass in "null" for the private key parameter. + final JceMasterKey escrowPub = JceMasterKey.getInstance(publicEscrowKey, null, "Escrow", "Escrow", + "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); + + // 4. Combine the providers into a single master key provider. + final MasterKeyProvider provider = MultipleProviderFactory.buildMultiProvider(kms, escrowPub); + + // 5. Encrypt the file. + // To simplify the code, we omit the encryption context. Production code should always + // use an encryption context. For an example, see the other SDK samples. + final FileInputStream in = new FileInputStream(fileName); + final FileOutputStream out = new FileOutputStream(fileName + ".encrypted"); + final CryptoOutputStream encryptingStream = crypto.createEncryptingStream(provider, out); + + IOUtils.copy(in, encryptingStream); + in.close(); + encryptingStream.close(); + } + + private static void standardDecrypt(final String kmsArn, final String fileName) throws Exception { + // Decrypt with the KMS CMK and the escrow public key. You can use a combined provider, + // as shown here, or just the KMS master key provider. + + // 1. Instantiate the AWS Encryption SDK. + final AwsCrypto crypto = new AwsCrypto(); + + // 2. Instantiate a KMS master key provider. + final KmsMasterKeyProvider kms = new KmsMasterKeyProvider(kmsArn); + + // 3. Instantiate a JCE master key provider. + // Because the user does not have access to the private + // escrow key, they pass in "null" for the private key parameter. + final JceMasterKey escrowPub = JceMasterKey.getInstance(publicEscrowKey, null, "Escrow", "Escrow", + "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); + + // 4. Combine the providers into a single master key provider. + final MasterKeyProvider provider = MultipleProviderFactory.buildMultiProvider(kms, escrowPub); + + // 5. Decrypt the file. + // To simplify the code, we omit the encryption context. Production code should always + // use an encryption context. For an example, see the other SDK samples. + final FileInputStream in = new FileInputStream(fileName + ".encrypted"); + final FileOutputStream out = new FileOutputStream(fileName + ".decrypted"); + final CryptoOutputStream decryptingStream = crypto.createDecryptingStream(provider, out); + IOUtils.copy(in, decryptingStream); + in.close(); + decryptingStream.close(); + } + + private static void escrowDecrypt(final String fileName) throws Exception { + // You can decrypt the stream using only the private key. + // This method does not call KMS. + + // 1. Instantiate the AWS Encryption SDK. + final AwsCrypto crypto = new AwsCrypto(); + + // 2. Instantiate a JCE master key provider. + // This method call uses the escrowed private key, not null. + final JceMasterKey escrowPriv = JceMasterKey.getInstance(publicEscrowKey, privateEscrowKey, "Escrow", "Escrow", + "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); + + // 3. Decrypt the file. + // To simplify the code, we omit the encryption context. Production code should always + // use an encryption context. For an example, see the other SDK samples. + final FileInputStream in = new FileInputStream(fileName + ".encrypted"); + final FileOutputStream out = new FileOutputStream(fileName + ".deescrowed"); + final CryptoOutputStream decryptingStream = crypto.createDecryptingStream(escrowPriv, out); + IOUtils.copy(in, decryptingStream); + in.close(); + decryptingStream.close(); + + } + + private static void generateEscrowKeyPair() throws GeneralSecurityException { + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + // The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + // https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + publicEscrowKey = keyPair.getPublic(); + privateEscrowKey = keyPair.getPrivate(); + + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/legacy/FileStreamingExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/FileStreamingExample.java new file mode 100644 index 000000000..050050972 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/FileStreamingExample.java @@ -0,0 +1,93 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package com.amazonaws.crypto.examples.legacy; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Collections; +import java.util.Map; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoInputStream; +import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.util.IOUtils; + +/** + *

+ * Encrypts and then decrypts a file under a random key. + * NOTE: Master key providers are deprecated and replaced by keyrings. + * We keep these older examples as reference material, + * but we recommend that you use the new examples in examples/keyring + * The new examples reflect our current guidance for using the library. + * + *

+ * Arguments: + *

    + *
  1. Name of file containing plaintext data to encrypt + *
+ * + *

+ * This program demonstrates using a standard Java {@link SecretKey} object as a {@link MasterKey} to + * encrypt and decrypt streaming data. + */ +public class FileStreamingExample { + private static String srcFile; + + public static void main(String[] args) throws IOException { + srcFile = args[0]; + + // In this example, we generate a random key. In practice, + // you would get a key from an existing store. + SecretKey cryptoKey = retrieveEncryptionKey(); + + // Create a JCE master key provider using the random key and an AES-GCM encryption algorithm. + JceMasterKey masterKey = JceMasterKey.getInstance(cryptoKey, "Example", "RandomKey", "AES/GCM/NoPadding"); + + // Instantiate the AWS Encryption SDK. + AwsCrypto crypto = new AwsCrypto(); + + // Create an encryption context to identify this ciphertext. + Map context = Collections.singletonMap("Example", "FileStreaming"); + + // Because the file might be to large to load into memory, we stream the data, instead of + //loading it all at once. + FileInputStream in = new FileInputStream(srcFile); + CryptoInputStream encryptingStream = crypto.createEncryptingStream(masterKey, in, context); + + FileOutputStream out = new FileOutputStream(srcFile + ".encrypted"); + IOUtils.copy(encryptingStream, out); + encryptingStream.close(); + out.close(); + + // Decrypt the file. Verify the encryption context before returning the plaintext. + in = new FileInputStream(srcFile + ".encrypted"); + CryptoInputStream decryptingStream = crypto.createDecryptingStream(masterKey, in); + // Does it contain the expected encryption context? + if (!"FileStreaming".equals(decryptingStream.getCryptoResult().getEncryptionContext().get("Example"))) { + throw new IllegalStateException("Bad encryption context"); + } + + // Return the plaintext data. + out = new FileOutputStream(srcFile + ".decrypted"); + IOUtils.copy(decryptingStream, out); + decryptingStream.close(); + out.close(); + } + + /** + * In practice, this key would be saved in a secure location. + * For this demo, we generate a new random key for each operation. + */ + private static SecretKey retrieveEncryptionKey() { + SecureRandom rnd = new SecureRandom(); + byte[] rawKey = new byte[16]; // 128 bits + rnd.nextBytes(rawKey); + return new SecretKeySpec(rawKey, "AES"); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/LambdaDecryptAndWriteExample.java similarity index 87% rename from src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java rename to src/examples/java/com/amazonaws/crypto/examples/legacy/LambdaDecryptAndWriteExample.java index 2419ebb51..badadb3e0 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/LambdaDecryptAndWriteExample.java @@ -1,17 +1,7 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples.datakeycaching; +package com.amazonaws.crypto.examples.legacy; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -115,20 +105,20 @@ public Void handleRequest(KinesisEvent event, Context context) { ByteBuffer ciphertextBuffer = record.getKinesis().getData(); byte[] ciphertext = BinaryUtils.copyAllBytesFrom(ciphertextBuffer); - // Decrypt and unpack record + // Decrypt and unpack record. AwsCryptoResult plaintextResult = crypto_.decrypt( DecryptRequest.builder() .cryptoMaterialsManager(cachingMaterialsManager_) .ciphertext(ciphertext).build()); - // Verify the encryption context value + // Verify the encryption context value. String streamArn = record.getEventSourceARN(); String streamName = streamArn.substring(streamArn.indexOf("/") + 1); if (!streamName.equals(plaintextResult.getEncryptionContext().get("stream"))) { throw new IllegalStateException("Wrong Encryption Context!"); } - // Write record to DynamoDB + // Write record to DynamoDB. String jsonItem = new String(plaintextResult.getResult(), StandardCharsets.UTF_8); table_.putItem(Item.fromJSON(jsonItem)); } diff --git a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/MultiRegionRecordPusherExample.java similarity index 86% rename from src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java rename to src/examples/java/com/amazonaws/crypto/examples/legacy/MultiRegionRecordPusherExample.java index 62ff53d45..a42e78ae1 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/MultiRegionRecordPusherExample.java @@ -1,17 +1,7 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples.datakeycaching; +package com.amazonaws.crypto.examples.legacy; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.encryptionsdk.AwsCrypto; @@ -61,7 +51,7 @@ public MultiRegionRecordPusherExample(final Region[] regions, final String kmsAl final DefaultAWSCredentialsProviderChain credentialsProvider = new DefaultAWSCredentialsProviderChain(); - // Build AwsKmsKeyring and AmazonKinesisClient objects for each target Region + // Build AwsKmsKeyring and AmazonKinesisClient objects for each target Region. final List keyrings = new ArrayList<>(); for (Region region : regions) { @@ -98,10 +88,10 @@ public void putRecord(final Map data) { String partitionKey = UUID.randomUUID().toString(); Map encryptionContext = Collections.singletonMap("stream", streamName_); - // JSON serialize data + // JSON serialize data. String jsonData = Jackson.toJsonString(data); - // Encrypt data + // Encrypt data. AwsCryptoResult result = crypto_.encrypt( EncryptRequest.builder() .cryptoMaterialsManager(cachingMaterialsManager_) @@ -111,7 +101,7 @@ public void putRecord(final Map data) { byte[] encryptedData = result.getResult(); - // Put records to Kinesis stream in all Regions + // Put records to Kinesis stream in all Regions. for (AmazonKinesis regionalKinesisClient : kinesisClients_) { regionalKinesisClient.putRecord( streamName_, diff --git a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/SimpleDataKeyCachingExample.java similarity index 78% rename from src/examples/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExample.java rename to src/examples/java/com/amazonaws/crypto/examples/legacy/SimpleDataKeyCachingExample.java index fa14d7702..90ac05335 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/SimpleDataKeyCachingExample.java @@ -1,17 +1,7 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples.datakeycaching; +package com.amazonaws.crypto.examples.legacy; import com.amazonaws.encryptionsdk.AwsCrypto; import com.amazonaws.encryptionsdk.CryptoMaterialsManager; @@ -71,19 +61,19 @@ public static void main(final String[] args) { static byte[] encryptWithCaching(AwsKmsCmkId kmsCmkArn) { - // Instantiate the SDK + // Instantiate the AWS Encryption SDK. final AwsCrypto crypto = new AwsCrypto(); - // Create an encryption context + // Create an encryption context. final Map encryptionContext = Collections.singletonMap("purpose", "test"); - // Create a keyring + // Create a keyring. final Keyring keyring = StandardKeyrings.awsKms(kmsCmkArn); - // Create a cache + // Create a cache. final CryptoMaterialsCache cache = new LocalCryptoMaterialsCache(CAPACITY); - // Create a caching CMM + // Create a caching CMM. final CryptoMaterialsManager cachingCmm = CachingCryptoMaterialsManager.newBuilder() .withKeyring(keyring) @@ -93,7 +83,7 @@ static byte[] encryptWithCaching(AwsKmsCmkId kmsCmkArn) { .build(); // When the call to encrypt specifies a caching CMM, - // the encryption operation uses the data key cache + // the encryption operation uses the data key cache. return crypto.encrypt(EncryptRequest.builder() .cryptoMaterialsManager(cachingCmm) .plaintext(EXAMPLE_DATA) diff --git a/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java deleted file mode 100644 index 95405fa2b..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.TestUtils; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -@Tag(TestUtils.TAG_INTEGRATION) -class BasicEncryptionExampleTest { - - @Test - void testEncryptAndDecrypt() { - BasicEncryptionExample.encryptAndDecrypt(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); - } -} diff --git a/src/test/java/com/amazonaws/crypto/examples/EscrowedEncryptExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/EscrowedEncryptExampleTest.java deleted file mode 100644 index 6e3cf5137..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/EscrowedEncryptExampleTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.TestUtils; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import java.security.GeneralSecurityException; - -@Tag(TestUtils.TAG_INTEGRATION) -class EscrowedEncryptExampleTest { - - @Test - void testEncryptAndDecrypt() throws GeneralSecurityException { - EscrowedEncryptExample.escrowEncryptAndDecrypt(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); - } -} diff --git a/src/test/java/com/amazonaws/crypto/examples/ExamplesTest.java b/src/test/java/com/amazonaws/crypto/examples/ExamplesTest.java new file mode 100644 index 000000000..26b9e3ee6 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/ExamplesTest.java @@ -0,0 +1,119 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.TestUtils; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestFactory; +import org.junit.platform.commons.support.HierarchyTraversalMode; +import org.junit.platform.commons.support.ReflectionSupport; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.fail; + +@Tag(TestUtils.TAG_INTEGRATION) +class ExamplesTest { + + private static final Logger LOGGER = Logger.getLogger(ExamplesTest.class.getName()); + private static final String RUN_METHOD_NAME = "run"; + private static final String TEST_CLASS_SUFFIX = "Test"; + private static final byte[] STATIC_PLAINTEXT = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + + "Praesent non feugiat leo. Aenean iaculis tellus ut velit consectetur, " + + "quis convallis orci eleifend. Sed eu dictum sapien. Nulla facilisi. Suspendisse potenti. " + + "Proin vehicula vehicula maximus. Donec varius et elit vel rutrum. Nulla lacinia neque turpis," + + " quis consequat orci pharetra et. Etiam consequat ullamcorper mauris. Vivamus molestie mollis " + + "mauris a gravida. Curabitur sed bibendum nisl. Cras varius tortor non erat sodales, quis congue" + + " tellus laoreet. Etiam fermentum purus eu diam sagittis, vitae commodo est vehicula. " + + "Nulla feugiat viverra orci vel interdum. Quisque pulvinar elit eget nulla facilisis varius. " + + "Mauris at suscipit sem. Aliquam in purus ut velit fringilla volutpat id non mi. " + + "Curabitur quis nunc eleifend, ornare lectus non, fringilla quam. Nam maximus volutpat placerat. " + + "Nulla ullamcorper lorem velit, nec sagittis ex tristique posuere. Aliquam fringilla magna commodo" + + " libero faucibus tempor. Vestibulum non ligula tincidunt, finibus sapien in, sollicitudin " + + "ex. Pellentesque congue laoreet mi in condimentum. Cras convallis nisi ac nunc tincidunt " + + "venenatis. Suspendisse urna elit, cursus eu lacus a, aliquet porttitor mi. " + + "Nulla vel congue nibh, sed condimentum dui. Ut ante ligula, blandit eu finibus nec, " + + "scelerisque quis eros. Maecenas gravida odio eget nibh dictum, dictum varius lacus interdum. " + + "Integer quis nulla vulputate, rhoncus diam vitae, mollis mauris. Sed ut porttitor dolor. " + + "Fusce ut justo a ex bibendum imperdiet nec sit amet magna. Sed ullamcorper luctus augue, " + + "tempor viverra elit interdum sed. Cras sit amet arcu eu turpis molestie sollicitudin. " + + "Curabitur fermentum varius nibh, ut aliquet nisi. Aliquam id tempus tellus. " + + "Nulla porttitor nulla at nibh interdum, quis sollicitudin erat egestas. " + + "Ut blandit mauris quis efficitur efficitur. Morbi neque sapien, posuere ut aliquam eget, " + + "aliquam at velit. Morbi sit amet rhoncus felis, et hendrerit sem. Nulla porta dictum ligula " + + "eget iaculis. Cras lacinia ligula quis risus ultrices, sed consectetur metus imperdiet. " + + "Nullam id enim vestibulum nibh ultricies auctor. Morbi neque lacus, faucibus vitae commodo quis, " + + "malesuada sed velit.").getBytes(StandardCharsets.UTF_8); + + @TestFactory + Stream testExamples() { + final List> exampleClasses = ReflectionSupport.findAllClassesInPackage(getClass().getPackage().getName(), + c -> Arrays.stream(c.getDeclaredMethods()).anyMatch(m -> m.getName().equals(RUN_METHOD_NAME)), + c -> !c.endsWith(TEST_CLASS_SUFFIX)); + + return exampleClasses.stream() + .map(c -> ReflectionSupport.findMethods(c, m -> m.getName().equals(RUN_METHOD_NAME), HierarchyTraversalMode.TOP_DOWN).get(0)) + .map(ExamplesTest::createTest); + } + + /** + * Creates a DynamicTest for the given method, matching each parameter type + * to the 4 parameter types that we have predefined values for. + */ + private static DynamicTest createTest(Method method) { + final Class[] parameterTypes = method.getParameterTypes(); + final Object[] parameterValues = new Object[parameterTypes.length]; + + for (int i = 0; i < parameterTypes.length; i++) { + if (parameterTypes[i].isAssignableFrom(AwsKmsCmkId.class)) { + parameterValues[i] = AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0]); + } else if (parameterTypes[i].isAssignableFrom(List.class)) { + parameterValues[i] = Collections.singletonList(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[1])); + } else if (parameterTypes[i].isAssignableFrom(byte[].class)) { + parameterValues[i] = STATIC_PLAINTEXT; + } else if (parameterTypes[i].isAssignableFrom(File.class)) { + try { + final File tempFile = File.createTempFile(method.getDeclaringClass().getSimpleName(), ".tmp"); + tempFile.deleteOnExit(); + + try (OutputStream os = Files.newOutputStream(tempFile.toPath())) { + os.write(STATIC_PLAINTEXT); + } + + parameterValues[i] = tempFile; + } catch (IOException e) { + fail("Failed to create temp file", e); + } + } else { + LOGGER.info(String.format("Setting unsupported parameter type[%s] to null", parameterTypes[i])); + parameterValues[i] = null; + } + } + + return DynamicTest.dynamicTest(method.getDeclaringClass().getName(), () -> { + try { + method.invoke(null, parameterValues); + } catch (IllegalAccessException e) { + fail(method.getDeclaringClass().getName() + " failed", e); + } catch (InvocationTargetException e) { + fail(method.getDeclaringClass().getName() + " failed", e.getCause()); + } + }); + } + +} diff --git a/src/test/java/com/amazonaws/crypto/examples/FileStreamingExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/FileStreamingExampleTest.java deleted file mode 100644 index 28b3f8f66..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/FileStreamingExampleTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.Test; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; - -class FileStreamingExampleTest { - - @Test - void testEncryptAndDecrypt() throws IOException { - final File tempFile = File.createTempFile("FileStreamingExampleTest-TempTestData", ".tmp"); - final File encryptedFile = new File(tempFile.getPath() + ".encrypted"); - final File decryptedFile = new File(tempFile.getPath() + ".decrypted"); - tempFile.deleteOnExit(); - encryptedFile.deleteOnExit(); - decryptedFile.deleteOnExit(); - - try (BufferedWriter writer = Files.newBufferedWriter(tempFile.toPath())) { - for (int i = 0; i < 1000 ; i++) { - writer.write(RandomStringUtils.randomAlphanumeric(100)); - writer.newLine(); - } - } - - FileStreamingExample.encryptAndDecrypt(tempFile, encryptedFile, decryptedFile); - } -} diff --git a/src/test/java/com/amazonaws/crypto/examples/RawAesKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/RawAesKeyringExampleTest.java deleted file mode 100644 index fa4d2906b..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/RawAesKeyringExampleTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import org.junit.jupiter.api.Test; - -class RawAesKeyringExampleTest { - - @Test - void testEncryptAndDecrypt() { - RawAesKeyringExample.encryptAndDecrypt(); - } -} diff --git a/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExampleTest.java deleted file mode 100644 index ca4c165f1..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExampleTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import org.junit.jupiter.api.Test; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -class RawRsaKeyringDecryptExampleTest { - - @Test - void testDecrypt() throws Exception { - final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); - kg.initialize(4096); - final KeyPair keyPair = kg.generateKeyPair(); - - byte[] ciphertext = RawRsaKeyringEncryptExample.encrypt(keyPair.getPublic()); - byte[] decryptedResult = RawRsaKeyringDecryptExample.decrypt(ciphertext, keyPair); - - assertArrayEquals(RawRsaKeyringEncryptExample.EXAMPLE_DATA, decryptedResult); - } - -} diff --git a/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java deleted file mode 100644 index 0329b4e48..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import org.junit.jupiter.api.Test; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -class RawRsaKeyringEncryptExampleTest { - - @Test - void testEncrypt() throws Exception { - final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); - kg.initialize(4096); - final KeyPair keyPair = kg.generateKeyPair(); - - byte[] ciphertext = RawRsaKeyringEncryptExample.encrypt(keyPair.getPublic()); - - final Keyring keyring = StandardKeyrings.rawRsaBuilder() - .keyNamespace("ExampleKeyNamespace") - .keyName("ExampleKeyName") - .privateKey(keyPair.getPrivate()) - .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) - .build(); - - - final AwsCrypto awsCrypto = new AwsCrypto(); - byte[] decryptedResult = awsCrypto.decrypt(DecryptRequest.builder() - .keyring(keyring) - .ciphertext(ciphertext) - .build()).getResult(); - - assertArrayEquals(RawRsaKeyringEncryptExample.EXAMPLE_DATA, decryptedResult); - } - -} diff --git a/src/test/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExampleTest.java deleted file mode 100644 index e1ebb3be9..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExampleTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples.datakeycaching; - -import com.amazonaws.encryptionsdk.TestUtils; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -@Tag(TestUtils.TAG_INTEGRATION) -class SimpleDataKeyCachingExampleTest { - - @Test - void testEncryptWithCaching() { - SimpleDataKeyCachingExample.encryptWithCaching(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); - } -}