|
1 | 1 | /*
|
2 | 2 | * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3 |
| - * |
| 3 | + * |
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except
|
5 | 5 | * in compliance with the License. A copy of the License is located at
|
6 |
| - * |
| 6 | + * |
7 | 7 | * http://aws.amazon.com/apache2.0
|
8 |
| - * |
| 8 | + * |
9 | 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
|
10 | 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
11 | 11 | * specific language governing permissions and limitations under the License.
|
12 | 12 | */
|
13 | 13 |
|
14 | 14 | package com.amazonaws.crypto.examples;
|
15 | 15 |
|
16 |
| -import java.io.FileInputStream; |
17 |
| -import java.io.FileOutputStream; |
| 16 | +import com.amazonaws.encryptionsdk.AwsCrypto; |
| 17 | +import com.amazonaws.encryptionsdk.keyrings.Keyring; |
| 18 | +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; |
| 19 | +import com.amazonaws.encryptionsdk.kms.KmsClientSupplier; |
| 20 | + |
| 21 | +import java.nio.charset.StandardCharsets; |
18 | 22 | import java.security.GeneralSecurityException;
|
19 | 23 | import java.security.KeyPair;
|
20 | 24 | import java.security.KeyPairGenerator;
|
21 | 25 | import java.security.PrivateKey;
|
22 | 26 | import java.security.PublicKey;
|
| 27 | +import java.util.Arrays; |
23 | 28 |
|
24 |
| -import com.amazonaws.encryptionsdk.AwsCrypto; |
25 |
| -import com.amazonaws.encryptionsdk.CryptoOutputStream; |
26 |
| -import com.amazonaws.encryptionsdk.MasterKeyProvider; |
27 |
| -import com.amazonaws.encryptionsdk.jce.JceMasterKey; |
28 |
| -import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; |
29 |
| -import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; |
30 |
| -import com.amazonaws.util.IOUtils; |
| 29 | +import static java.util.Collections.emptyList; |
31 | 30 |
|
32 | 31 | /**
|
33 | 32 | * <p>
|
34 |
| - * Encrypts a file using both KMS and an asymmetric key pair. |
| 33 | + * Encrypts data using both KMS and an asymmetric key pair. |
35 | 34 | *
|
36 | 35 | * <p>
|
37 | 36 | * Arguments:
|
38 | 37 | * <ol>
|
39 |
| - * <li>Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master |
| 38 | + * <li>Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master |
40 | 39 | * key (CMK), see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html
|
41 |
| - * |
42 |
| - * <li>Name of file containing plaintext data to encrypt |
43 | 40 | * </ol>
|
44 | 41 | *
|
45 |
| - * You might use AWS Key Management Service (KMS) for most encryption and decryption operations, but |
46 |
| - * still want the option of decrypting your data offline independently of KMS. This sample |
| 42 | + * You might use AWS Key Management Service (KMS) for most encryption and decryption operations, but |
| 43 | + * still want the option of decrypting your data offline independently of KMS. This sample |
47 | 44 | * demonstrates one way to do this.
|
48 |
| - * |
| 45 | + * |
49 | 46 | * The sample encrypts data under both a KMS customer master key (CMK) and an "escrowed" RSA key pair
|
50 |
| - * so that either key alone can decrypt it. You might commonly use the KMS CMK for decryption. However, |
| 47 | + * so that either key alone can decrypt it. You might commonly use the KMS CMK for decryption. However, |
51 | 48 | * at any time, you can use the private RSA key to decrypt the ciphertext independent of KMS.
|
52 | 49 | *
|
53 |
| - * This sample uses the JCEMasterKey class to generate a RSA public-private key pair |
54 |
| - * and saves the key pair in memory. In practice, you would store the private key in a secure offline |
| 50 | + * This sample uses an RawRsaKeyring to generate a RSA public-private key pair |
| 51 | + * and saves the key pair in memory. In practice, you would store the private key in a secure offline |
55 | 52 | * location, such as an offline HSM, and distribute the public key to your development team.
|
56 | 53 | *
|
57 | 54 | */
|
58 | 55 | public class EscrowedEncryptExample {
|
59 |
| - private static PublicKey publicEscrowKey; |
60 |
| - private static PrivateKey privateEscrowKey; |
| 56 | + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); |
| 57 | + |
| 58 | + public static void main(final String[] args) throws GeneralSecurityException { |
| 59 | + final String kmsArn = args[0]; |
61 | 60 |
|
62 |
| - public static void main(final String[] args) throws Exception { |
| 61 | + escrowEncryptAndDecrypt(kmsArn); |
| 62 | + } |
| 63 | + |
| 64 | + static void escrowEncryptAndDecrypt(String kmsArn) throws GeneralSecurityException { |
63 | 65 | // This sample generates a new random key for each operation.
|
64 |
| - // In practice, you would distribute the public key and save the private key in secure |
65 |
| - // storage. |
66 |
| - generateEscrowKeyPair(); |
| 66 | + // In practice, you would distribute the public key and save the private key in secure storage. |
| 67 | + final KeyPair escrowKeyPair = generateEscrowKeyPair(); |
67 | 68 |
|
68 |
| - final String kmsArn = args[0]; |
69 |
| - final String fileName = args[1]; |
| 69 | + // Encrypt the data under both a KMS Key and an escrowed RSA Key |
| 70 | + byte[] encryptedData = standardEncrypt(kmsArn, escrowKeyPair.getPublic()); |
70 | 71 |
|
71 |
| - standardEncrypt(kmsArn, fileName); |
72 |
| - standardDecrypt(kmsArn, fileName); |
| 72 | + // Decrypt the data using the KMS Key |
| 73 | + byte[] standardDecryptedData = standardDecrypt(kmsArn, encryptedData); |
73 | 74 |
|
74 |
| - escrowDecrypt(fileName); |
| 75 | + // Decrypt the data using the escrowed RSA Key |
| 76 | + byte[] escrowedDecryptedData = escrowDecrypt(encryptedData, escrowKeyPair.getPublic(), escrowKeyPair.getPrivate()); |
| 77 | + |
| 78 | + // Verify both decrypted data instances are the same as the original plaintext |
| 79 | + assert Arrays.equals(standardDecryptedData, EXAMPLE_DATA); |
| 80 | + assert Arrays.equals(escrowedDecryptedData, EXAMPLE_DATA); |
75 | 81 | }
|
76 | 82 |
|
77 |
| - private static void standardEncrypt(final String kmsArn, final String fileName) throws Exception { |
| 83 | + private static byte[] standardEncrypt(final String kmsArn, final PublicKey publicEscrowKey) { |
78 | 84 | // Encrypt with the KMS CMK and the escrowed public key
|
| 85 | + |
79 | 86 | // 1. Instantiate the SDK
|
80 | 87 | final AwsCrypto crypto = new AwsCrypto();
|
81 | 88 |
|
82 |
| - // 2. Instantiate a KMS master key provider |
83 |
| - final KmsMasterKeyProvider kms = new KmsMasterKeyProvider(kmsArn); |
84 |
| - |
85 |
| - // 3. Instantiate a JCE master key provider |
86 |
| - // Because the user does not have access to the private escrow key, |
87 |
| - // they pass in "null" for the private key parameter. |
88 |
| - final JceMasterKey escrowPub = JceMasterKey.getInstance(publicEscrowKey, null, "Escrow", "Escrow", |
89 |
| - "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); |
90 |
| - |
91 |
| - // 4. Combine the providers into a single master key provider |
92 |
| - final MasterKeyProvider<?> provider = MultipleProviderFactory.buildMultiProvider(kms, escrowPub); |
93 |
| - |
94 |
| - // 5. Encrypt the file |
95 |
| - // To simplify the code, we omit the encryption context. Production code should always |
96 |
| - // use an encryption context. For an example, see the other SDK samples. |
97 |
| - final FileInputStream in = new FileInputStream(fileName); |
98 |
| - final FileOutputStream out = new FileOutputStream(fileName + ".encrypted"); |
99 |
| - final CryptoOutputStream<?> encryptingStream = crypto.createEncryptingStream(provider, out); |
100 |
| - |
101 |
| - IOUtils.copy(in, encryptingStream); |
102 |
| - in.close(); |
103 |
| - encryptingStream.close(); |
| 89 | + // 2. Instantiate a KMS Client Supplier. This example uses the default client supplier but you can |
| 90 | + // also configure the credentials provider, client configuration and other settings as necessary |
| 91 | + final KmsClientSupplier clientSupplier = KmsClientSupplier.builder().build(); |
| 92 | + |
| 93 | + // 3. Instantiate a KMS Keyring, supplying the keyArn as the generator for generating a data key. |
| 94 | + // For this example, empty lists are provided for grant tokens and additional keys to encrypt the data |
| 95 | + // key with, but those can be supplied as necessary. |
| 96 | + final Keyring kmsKeyring = StandardKeyrings.kms(clientSupplier, emptyList(), emptyList(), kmsArn); |
| 97 | + |
| 98 | + // 4. Instantiate an RawRsaKeyring |
| 99 | + // Because the user does not have access to the private escrow key, |
| 100 | + // they pass in "null" for the private key parameter. |
| 101 | + final Keyring rsaKeyring = StandardKeyrings.rawRsa("Escrow", "Escrow", |
| 102 | + publicEscrowKey, null, "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); |
| 103 | + |
| 104 | + // 5. Combine the providers into a single MultiKeyring |
| 105 | + final Keyring keyring = StandardKeyrings.multi(kmsKeyring, rsaKeyring); |
| 106 | + |
| 107 | + // 6. Instantiate the AwsCryptoConfig input to AwsCrypto with the keyring |
| 108 | + // To simplify the code, we omit the encryption context. Production code should always |
| 109 | + // use an encryption context. For an example, see the other SDK samples. |
| 110 | + final AwsCrypto.AwsCryptoConfig config = AwsCrypto.AwsCryptoConfig.builder() |
| 111 | + .keyring(keyring) |
| 112 | + .build(); |
| 113 | + |
| 114 | + // 7. Encrypt the data |
| 115 | + return crypto.encryptData(config, EXAMPLE_DATA).getResult(); |
104 | 116 | }
|
105 | 117 |
|
106 |
| - private static void standardDecrypt(final String kmsArn, final String fileName) throws Exception { |
107 |
| - // Decrypt with the KMS CMK and the escrow public key. You can use a combined provider, |
108 |
| - // as shown here, or just the KMS master key provider. |
| 118 | + private static byte[] standardDecrypt(final String kmsArn, final byte[] cipherText) { |
| 119 | + // Decrypt with the KMS CMK |
109 | 120 |
|
110 | 121 | // 1. Instantiate the SDK
|
111 | 122 | final AwsCrypto crypto = new AwsCrypto();
|
112 | 123 |
|
113 |
| - // 2. Instantiate a KMS master key provider |
114 |
| - final KmsMasterKeyProvider kms = new KmsMasterKeyProvider(kmsArn); |
115 |
| - |
116 |
| - // 3. Instantiate a JCE master key provider |
117 |
| - // Because the user does not have access to the private |
118 |
| - // escrow key, they pass in "null" for the private key parameter. |
119 |
| - final JceMasterKey escrowPub = JceMasterKey.getInstance(publicEscrowKey, null, "Escrow", "Escrow", |
120 |
| - "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); |
121 |
| - |
122 |
| - // 4. Combine the providers into a single master key provider |
123 |
| - final MasterKeyProvider<?> provider = MultipleProviderFactory.buildMultiProvider(kms, escrowPub); |
124 |
| - |
125 |
| - // 5. Decrypt the file |
126 |
| - // To simplify the code, we omit the encryption context. Production code should always |
127 |
| - // use an encryption context. For an example, see the other SDK samples. |
128 |
| - final FileInputStream in = new FileInputStream(fileName + ".encrypted"); |
129 |
| - final FileOutputStream out = new FileOutputStream(fileName + ".decrypted"); |
130 |
| - final CryptoOutputStream<?> decryptingStream = crypto.createDecryptingStream(provider, out); |
131 |
| - IOUtils.copy(in, decryptingStream); |
132 |
| - in.close(); |
133 |
| - decryptingStream.close(); |
| 124 | + // 2. Instantiate a KMS Client Supplier. This example uses the default client supplier but you can |
| 125 | + // also configure the credentials provider, client configuration and other settings as necessary |
| 126 | + final KmsClientSupplier clientSupplier = KmsClientSupplier.builder().build(); |
| 127 | + |
| 128 | + // 3. Instantiate a KMS Keyring, supplying the keyArn as the generator for generating a data key. |
| 129 | + // For this example, empty lists are provided for grant tokens and additional keys to encrypt the data |
| 130 | + // key with, but those can be supplied as necessary. |
| 131 | + final Keyring kmsKeyring = StandardKeyrings.kms(clientSupplier, emptyList(), emptyList(), kmsArn); |
| 132 | + |
| 133 | + // 4. Instantiate the AwsCryptoConfig input to AwsCrypto with the keyring |
| 134 | + // To simplify the code, we omit the encryption context. Production code should always |
| 135 | + // use an encryption context. For an example, see the other SDK samples. |
| 136 | + final AwsCrypto.AwsCryptoConfig config = AwsCrypto.AwsCryptoConfig.builder() |
| 137 | + .keyring(kmsKeyring) |
| 138 | + .build(); |
| 139 | + |
| 140 | + // 5. Decrypt the data |
| 141 | + return crypto.decryptData(config, cipherText).getResult(); |
134 | 142 | }
|
135 | 143 |
|
136 |
| - private static void escrowDecrypt(final String fileName) throws Exception { |
| 144 | + private static byte[] escrowDecrypt(final byte[] cipherText, final PublicKey publicEscrowKey, final PrivateKey privateEscrowKey) { |
137 | 145 | // You can decrypt the stream using only the private key.
|
138 | 146 | // This method does not call KMS.
|
139 | 147 |
|
140 | 148 | // 1. Instantiate the SDK
|
141 | 149 | final AwsCrypto crypto = new AwsCrypto();
|
142 | 150 |
|
143 |
| - // 2. Instantiate a JCE master key provider |
144 |
| - // This method call uses the escrowed private key, not null |
145 |
| - final JceMasterKey escrowPriv = JceMasterKey.getInstance(publicEscrowKey, privateEscrowKey, "Escrow", "Escrow", |
146 |
| - "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); |
147 |
| - |
148 |
| - // 3. Decrypt the file |
149 |
| - // To simplify the code, we omit the encryption context. Production code should always |
150 |
| - // use an encryption context. For an example, see the other SDK samples. |
151 |
| - final FileInputStream in = new FileInputStream(fileName + ".encrypted"); |
152 |
| - final FileOutputStream out = new FileOutputStream(fileName + ".deescrowed"); |
153 |
| - final CryptoOutputStream<?> decryptingStream = crypto.createDecryptingStream(escrowPriv, out); |
154 |
| - IOUtils.copy(in, decryptingStream); |
155 |
| - in.close(); |
156 |
| - decryptingStream.close(); |
| 151 | + // 2. Instantiate a RawRsaKeyring using the escrowed private key |
| 152 | + final Keyring rsaKeyring = StandardKeyrings.rawRsa("Escrow", "Escrow", |
| 153 | + publicEscrowKey, privateEscrowKey, "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); |
157 | 154 |
|
| 155 | + // 3. Instantiate the AwsCryptoConfig input to AwsCrypto with the keyring |
| 156 | + // To simplify the code, we omit the encryption context. Production code should always |
| 157 | + // use an encryption context. For an example, see the other SDK samples. |
| 158 | + final AwsCrypto.AwsCryptoConfig config = AwsCrypto.AwsCryptoConfig.builder() |
| 159 | + .keyring(rsaKeyring) |
| 160 | + .build(); |
| 161 | + |
| 162 | + // 4. Decrypt the data |
| 163 | + return crypto.decryptData(config, cipherText).getResult(); |
158 | 164 | }
|
159 | 165 |
|
160 |
| - private static void generateEscrowKeyPair() throws GeneralSecurityException { |
| 166 | + private static KeyPair generateEscrowKeyPair() throws GeneralSecurityException { |
161 | 167 | final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA");
|
162 | 168 | kg.initialize(4096); // Escrow keys should be very strong
|
163 |
| - final KeyPair keyPair = kg.generateKeyPair(); |
164 |
| - publicEscrowKey = keyPair.getPublic(); |
165 |
| - privateEscrowKey = keyPair.getPrivate(); |
166 |
| - |
| 169 | + return kg.generateKeyPair(); |
167 | 170 | }
|
168 | 171 | }
|
0 commit comments