Skip to content

Incorporate Keyrings into AwsCrypto and deprecate MasterKeyProviders. #151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Feb 12, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6bb85a0
Incorporate Keyrings into AwsCrypto and deprecate MasterKeyProviders.
WesleyRosenblum Jan 17, 2020
63ff149
Update example code to use keyrings
WesleyRosenblum Jan 21, 2020
d444f52
Using try-with-resources for AwsCrypto streams
WesleyRosenblum Jan 23, 2020
0a3e6e3
Splitting MKP and keyring unit tests
WesleyRosenblum Jan 23, 2020
30c51a9
Making decryptData with ParsedCiphertext public
WesleyRosenblum Jan 23, 2020
4d8614f
Mark KeyStoreProvider as deprecated
WesleyRosenblum Jan 24, 2020
400f521
Reword some comments on the Basic Encryption example
WesleyRosenblum Jan 27, 2020
59e3045
Add test for compability of Keyrings with MasterKeyProviders
WesleyRosenblum Jan 27, 2020
6e473a1
Create individual request types for each AwsCrypto method
WesleyRosenblum Jan 29, 2020
623f6f5
Make EncryptionMaterials, DecryptionMaterials and KeyringTrace immutable
WesleyRosenblum Jan 30, 2020
3af73f0
Rename KmsKeying and related classes to AwsKmsKeyring
WesleyRosenblum Jan 30, 2020
5258475
Create builders for the standard keyrings
WesleyRosenblum Jan 30, 2020
e04c285
Create AwsKmsCmkId type to represent AWS KMS Key Ids
WesleyRosenblum Jan 31, 2020
6396c6c
Add factory methods to Keyring builders
WesleyRosenblum Jan 31, 2020
15c1def
Add comment on not making a defensive copy of plaintext/ciphertext
WesleyRosenblum Jan 31, 2020
6af9b02
Limit ability to create discovery AWS KMS Keyrings to explicit creation
WesleyRosenblum Jan 31, 2020
c182cd5
Add withKeyring to CachingCMM builder
WesleyRosenblum Jan 31, 2020
37d42bb
Fix DecryptRequestTest
WesleyRosenblum Feb 3, 2020
63057f5
Fix Junit 4 assertions in JUnit5 tests
WesleyRosenblum Feb 4, 2020
f415b7f
Renaming StaticKeyring to TestKeyring
WesleyRosenblum Feb 4, 2020
7ec91d6
Adding convenience methods the create builders internally
WesleyRosenblum Feb 6, 2020
876dd5e
Updating wording and adding more Deprecated annotations
WesleyRosenblum Feb 6, 2020
214fabd
Enable AwsKms Client Caching by default to match KmsMasterKeyProvider
WesleyRosenblum Feb 7, 2020
cffe776
Making tests opt-out instead of opt-in and update TestVectorRunner (#…
WesleyRosenblum Feb 7, 2020
8ed8619
Renaming StandardKeyring builder methods and other minors changes
WesleyRosenblum Feb 10, 2020
28aeee2
Fixing test
WesleyRosenblum Feb 11, 2020
ff9dab6
Updating tests to use assertThrows
WesleyRosenblum Feb 11, 2020
12f0c42
Additional example code for Keyrings (#155)
WesleyRosenblum Feb 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
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;
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;

/**
* <p>
Expand All @@ -39,44 +42,52 @@ 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);
encryptAndDecrypt(AwsKmsCmkId.fromString(args[0]));
}

static void encryptAndDecrypt(final String keyArn) {
static void encryptAndDecrypt(final AwsKmsCmkId keyArn) {
// 1. Instantiate the SDK
final AwsCrypto crypto = new AwsCrypto();

// 2. Instantiate a KMS master key provider
final KmsMasterKeyProvider masterKeyProvider = KmsMasterKeyProvider.builder().withKeysForEncryption(keyArn).build();
// 2. Instantiate an AWS KMS Keyring, supplying the key ARN as the generator for generating a data key. While
// using a key ARN is a best practice, for encryption operations it is also acceptable to use a CMK alias or
// an 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.
// 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
// 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<String, String> encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue");

// 4. Encrypt the data
final CryptoResult<byte[], KmsMasterKey> encryptResult = crypto.encryptData(masterKeyProvider, EXAMPLE_DATA, encryptionContext);
// 4. Encrypt the data with the keyring and encryption context
final AwsCryptoResult<byte[]> encryptResult = crypto.encrypt(
EncryptRequest.builder()
.keyring(keyring)
.encryptionContext(encryptionContext)
.plaintext(EXAMPLE_DATA).build());
final byte[] ciphertext = encryptResult.getResult();

// 5. Decrypt the data
final CryptoResult<byte[], KmsMasterKey> decryptResult = crypto.decryptData(masterKeyProvider, ciphertext);
// 5. Decrypt the data. The same keyring may be used to encrypt and decrypt, but for decryption
// the key IDs must be in the key ARN format.
final AwsCryptoResult<byte[]> decryptResult = crypto.decrypt(
DecryptRequest.builder()
.keyring(keyring)
.ciphertext(ciphertext).build());

// 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)) {
// 6. Before verifying the plaintext, inspect the Keyring Trace to verify that the CMK used
// to decrypt the encrypted data key was the CMK in the encryption keyring.
if(!decryptResult.getKeyringTrace().getEntries().get(0).getKeyName().equals(keyArn.toString())) {
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.
// 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!");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,168 +1,157 @@
/*
* 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 java.io.FileInputStream;
import java.io.FileOutputStream;
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.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 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;
import java.util.Arrays;

/**
* <p>
* Encrypts a file using both KMS and an asymmetric key pair.
* Encrypts data using both KMS and an asymmetric key pair.
*
* <p>
* Arguments:
* <ol>
* <li>Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master
* <li>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
*
* <li>Name of file containing plaintext data to encrypt
* </ol>
*
* 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
* 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,
* 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
* 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 PublicKey publicEscrowKey;
private static PrivateKey privateEscrowKey;
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]));
}

public static void main(final String[] args) throws Exception {
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.
generateEscrowKeyPair();
// 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());

final String kmsArn = args[0];
final String fileName = args[1];
// Decrypt the data using the KMS Key
byte[] standardDecryptedData = standardDecrypt(kmsArn, encryptedData);

standardEncrypt(kmsArn, fileName);
standardDecrypt(kmsArn, fileName);
// Decrypt the data using the escrowed RSA Key
byte[] escrowedDecryptedData = escrowDecrypt(encryptedData, escrowKeyPair.getPrivate());

escrowDecrypt(fileName);
// 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 void standardEncrypt(final String kmsArn, final String fileName) throws Exception {
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 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();
// 2. Instantiate an AWS 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 pass in "null" for the private key parameter.
final Keyring rsaKeyring = StandardKeyrings.rawRsa()
.keyNamespace("Escrow")
.keyName("Escrow")
.publicKey(publicEscrowKey)
.wrappingAlgorithm("RSA/ECB/OAEPWithSHA-512AndMGF1Padding")
.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 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.
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 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();
// 2. Instantiate an AWS 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 void escrowDecrypt(final String fileName) throws Exception {
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 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();

// 2. Instantiate a RawRsaKeyring using the escrowed private key
final Keyring rsaKeyring = StandardKeyrings.rawRsa()
.keyNamespace("Escrow")
.keyName("Escrow")
.privateKey(privateEscrowKey)
.wrappingAlgorithm("RSA/ECB/OAEPWithSHA-512AndMGF1Padding")
.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 void generateEscrowKeyPair() throws GeneralSecurityException {
private static KeyPair generateEscrowKeyPair() throws GeneralSecurityException {
final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA");
kg.initialize(4096); // Escrow keys should be very strong
final KeyPair keyPair = kg.generateKeyPair();
publicEscrowKey = keyPair.getPublic();
privateEscrowKey = keyPair.getPrivate();

return kg.generateKeyPair();
}
}
Loading