In this tutorial, we explore the AWS Key Management System (KMS) to encrypt and decrypt data via the AWS Java 2 SDK. This tutorial encrypts/decrypts two different ways. We first encrypt and decrypt data directly using an AWS customer managed key (CMK). We then encrypt and decrypt the data using a data key that was generated by the AWS CMK. Of the two, the second is more secure, and the preferred way to encrypt data. Although a CMK can encrypt and decrypt data, a better practice is to use the CMK to generate data keys, which are in turn used with the relevant data. However, for the purpose of demonstration, we also use the CMK directly.
Introduction
AWS SDK for Java Version 2.x
In this tutorial, we use the Java Version 2.x of the AWS SDK. The SDK provides a convenient wrapper around the AWS services’ lower-level REST calls. Be certain you use version 2.x and not 1.x, as 2.x is a rewrite of the API and so there are considerable differences between the two API versions.
Key Management Service (KMS)
AWS KMS is a service that enables generating, storing, and managing symmetric keys. The service is integrated with other Amazon offerings such as S3. Actually, most AWS services are integrated with KMS, as this list of over 50 services illustrates. However, KMS can also be used to generate and manage your own application’s keys even if that application is independent of other AWS services.
A symmetric key is a single key used to encrypt/decrypt data. This is in contrast to an asymmetric key, where a private key and public key encrypt/decrypt data. Wikipedia has a good general introduction to key encryption: Key (Cryptography). A typical strategy for symmetric key encryption is as follows. A single master key is used to encrypt/decrypt data encryption keys. These data keys encrypt/decrypt your application’s data. To ensure security, when not in use, the data keys are encrypted/decrypted by the master key. The master key is then stored in a safe location so it can be used as needed.
AWS KMS provides a secure location to store and manage your master keys. CMKs cannot be exported from KMS and can only be used by users with appropriate permissions assigned. The KMS FAQ summarizes KMS.
AWS KMS is a managed service that enables you to easily encrypt your data. AWS KMS provides a highly available key storage, management, and auditing solution for you to encrypt data within your own applications and control the encryption of stored data across AWS services.
AWS Key Management Service FAQs
AWS KMS offers many benefits for developers using AWS services.
If you are a developer who needs to encrypt data in your applications, you should use the AWS Encryption SDK with AWS KMS support to easily use and protect encryption keys. If you’re an IT administrator looking for a scalable key management infrastructure to support your developers and their growing number of applications, you should use AWS KMS to reduce your licensing costs and operational burden. If you’re responsible for proving data security for regulatory or compliance purposes, you should use AWS KMS to verify that data is encrypted consistently across the applications where it is used and stored.
AWS Key Management Service FAQs
AWS KMS offers an integrated cloud environment for managing keys.
You can perform the following key management functions in AWS KMS:
- Create keys with a unique alias and description
- Import your own key material
- Define which IAM users and roles can manage keys
- Define which IAM users and roles can use keys to encrypt and decrypt data
- Choose to have AWS KMS automatically rotate your keys on an annual basis
- Temporarily disable keys so they cannot be used by anyone
- Re-enable disabled keys
- Delete keys that you no longer use
- Audit use of keys by inspecting logs in AWS CloudTrail
- Create custom key stores*
- Connect and disconnect custom key stores*
- Delete custom key stores*
* The use of custom key stores requires CloudHSM resources to be available in your account.
AWS KMS FAQ.
For more introductory information, refer to The AWS Key Management Features webpage maintained by Amazon. Also refer to the videos embedded at the end of this tutorial.
In this tutorial, we perform the following tasks:
- Create two users, one to manage a CMK and another to use the CMK
- Create a CMK and assign the users to the key
- Create an application that uses the CMK directly to encrypt/decrypt data
- Discuss why using the CMK directly is not an optimal encryption strategy, and
- Create an application that uses the CMK to create a data key to encrypt/decrypt data
In this tutorial, we limit using KMS to generating a data key and to encrypting/decrypting data. You can also manage keys through the Java SDK; however, this tutorial does not cover key management, assuming instead you will do so through the AWS console or AWS Command-line Interface (CLI).
- It is assumed you have an AWS account, know your way around the AWS Console, and have enough experience with Java programming that you do not require help using an IDE such as Eclipse.
Creating the CMK
Before using the CMK, we need to create it. Although you can use the Java SDK to perform all the following tasks, we use the AWS Console for creating the required users and key.
Create Users
We need to create users for our CMK. The first user we create is the key manager. Although this tutorial does not subsequently use this user, we include it as in a real project you would eventually need this user for managing keys. The second user we create is the key user. This is the user that is allowed to use the CMK to encrypt/decrypt data. We do use this data in the Java application.
- All Users, Keys, and potentially sensitive information will have been removed from my account before this tutorial is posted.
Create Manager
Let’s first create the manager user.
- Navigate to IAM, Users, and add a User named
KmsKeyManager
. - Assign the user programmatic and console access.
- Create a password for the user and uncheck the Require password reset checkbox.
Adding the KmsKeyManager user via the Add user page
- When you add the user to groups, create a new group named
KmsKeyTutorialGroup
and assign it AdministratorAccess
.
Adding policies to a KmsKeyTutorialGroup
- Add
KmsKeyManager
to KmsKeyTutorialGroup
.
Passing KmsKeyManager to KmsKeyTutorialGroup
- After creating the user, you should see a screen similar to the following. Do not forget to download the access keys so you can use them in your Java program.
KmsKeyManager created successfully
Create Encrypt/Decrypt User
Let’s now create the user we use to encrypt/decrypt data in our Java application.
- Create a user named
KmsTutorialKeyUser
and assign programmatic access.
Creating the KmsTutorialKeyUser
- Do not assign
KmsTutorialKeyUser
to any groups.
The Users screen with two newly added users, KmsKeyManager and KMSTutorialKeyUser
Create KMS Key
- Navigate to IAM and then select Encryption keys to bring you to the Key Management Service (KMS).
Customer managed keys screen with no keys
- Create a new key with the alias,
KmsTutorialKey
. - Select KMS as the Key material origin.
Creating the KmsTutorialKey
- Assign
KmsKeyManager
as the key administrator.
Key administration permissions assigned to KmsKeyManager
- Assign
KmsTutorialKeyUser
as the key user (can encrypt and decrypt using the key).
Assigning encrypt/decrypt permission to KmsTutorialKeyUser
- If interested, review the JSON document.
Complete policy document is JSON
- After finishing, you should see a screen similar to the following:
KMS screen with newly created CMK
Java Project
Let’s create the Java project using Maven. Although I use Eclipse, any IDE or the command-line should work. It is assumed you can create a Java project that uses Maven to build. If you need help accomplishing this task, refer to a tutorial online. The following is a good introductory tutorial for Maven and Eclipse.
Project Setup
- Create a new Java project that uses Maven to build. Use the following POM.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tutorial.aws</groupId>
<artifactId>KMSTutorial</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.5.25</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>kms</artifactId>
</dependency>
<dependency>
<artifactId>auth</artifactId>
<groupId>software.amazon.awssdk</groupId>
</dependency>
<dependency>
<artifactId>aws-core</artifactId>
<groupId>software.amazon.awssdk</groupId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.4</version>
</dependency>
</dependencies>
</project>
Note the POM includes the following lines. You might not require these lines; however, the Java 2 SDK uses features that require Java 8 or higher, and I could only get the code to compile including these lines. YMMV.
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
The POM adds the AWS Bill of Materials (BOM) to free us from having to manage the library versions, focusing only on including the correct dependencies rather than their versions. The POM also includes the KMS library and the core libraries required by AWS.
- You can access the BOM at this Maven Repository.
- If using Eclipse, add a file named observation.json to the resource folder.
- The observation.json file is a simple JSON record.
{
"stationid": 221,
"date": "1992-03-12",
"time": "091312",
"message":"This is a secret message. Please encrypt it when storing on disk."
}
KmsClient
The AWS SDK is consistent in how you interact with AWS’s different services. The 2.x API version consistently follows the Fluent Interface/Builder pattern. You can find more information on this pattern if interested by starting with the wikipedia page. A good introductory explanation is found in the following blog post: Another builder pattern for Java. Rather than instantiating new instances of a class, you build the class from a builder. When using the AWS SDK, you create a client with the required credentials using the client’s associated builder. For instance, a KmsClient
has a KmsClient.Builder
that builds it. Different services have different clients. The KMS service uses the KmsClient. Clients work with AWS via requests and is returned responses. The KmsClient
class, for example, uses requests to encrypt/decrypt, create keys, and manage keys.
Let’s create the KmsClient
. But first, we need to return to the AWS Console and copy the CMK key’s Amazon Resource Name (ARN). The ARN is how our application’s client will know where to access the CMK in KMS.
- Navigate to the key and copy the key’s ARN.
Copy the CMK ARN in the AWS Console
An ARN identifies any resource on AWS uniquely. The client uses this to access the CMK.
- Create a new class in named
KMSExample
in the com.tutorial.aws.kms
package. - Although it is not recommended you hardcode keys in your code, for convenience, we create the key and
secretKey static
variables to hold the KmsTutorialKeyUser
key
and secretKey
. - Create a
keyArn
static
variable and assign it the ARN you copied above from the AWS console. - Create a
KmsClient
variable and build it in the constructor for KMSExample
. - In
main
, create a KMSExample
instance.
package com.tutorial.aws.kms;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kms.KmsClient;
public class KMSExample {
final static String key = "<key_value_here>";
final static String secretKey = "<secret_key_value_here";
final static String keyArn = "<key_arn_here>";
public KmsClient kmsClient;
public KMSExample() {
AwsBasicCredentials awsCreds = AwsBasicCredentials.create(key,
secretKey);
this.kmsClient = KmsClient.builder()
.credentialsProvider(StaticCredentialsProvider.create(awsCreds))
.region(Region.US_EAST_1).build();
}
public static void main(String[] args) {
try {
KMSExample kmsExample = new KMSExample();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
- Build the application and run the application, just to ensure everything works.
The KMSExample
class uses a KmsClientBuilder
to build a KmsClient
instance. The KmsClientBuilder
uses the region, the credentials, and the key’s ARN to create a KmsClient
that can interact with our CMK (KmsTutorialKey
).
Note that in this tutorial, we create the credentials in a rather un-secure manner. We hardcode them and used StaticCredentialsProvider and AwsBasicCredentials. In a production ready application, you should use the AWS Security Token Service (AWS STS) to grant temporary credentials when running the application. For more information, refer to the ASW Security Token Service documentation. Also, you might refer to the following AWS Security Blog post: Guidelines for protecting your AWS account while using programmatic access.
- The following is an example using the Java 1.x SDK to creating temporary credentials: Making Requests Using IAM User Temporary Credentials – AWS SDK for Java.
- After becoming familiar with the 2.x API version, translating constructors and methods to the 2.x API’s builder methods becomes intuitive.
Encrypting and Decrypting Using the Customer Key
In the first example, we encrypt and decrypt the data directly using the KmsTutorialKey
. As discussed earlier, this is not the recommended way to encrypt/decrypt your application’s data. However, we include it here, as a CMK master key can be used directly in your application to encrypt/decrypt data. And, there might be situations where it’s appropriate to use the CMK directly.
Encrypt
Let’s encrypt the data. We do this by building an EncryptRequest using an EncryptRequest.Builder
. The builder takes the request, the key’s ARN, and builds the EncryptRequest
. We then pass the request to the KmsClient
.
- Add a method named
encrypt
to KMSTutorial
that takes SdkBytes
and returns SdkBytes
. - Create an
EncryptRequest
by specifying the key’s ARN and the string
to encrypt. - Have
kmsClient
encrypt the request and assign the response to an EncryptResponse
. - Return the response’s data as
SdkBytes
.
public SdkBytes encrypt(SdkBytes jsonString) {
EncryptRequest encryptRequest = EncryptRequest.builder()
.keyId(keyArn).plaintext(jsonString).build();
EncryptResponse encryptResponse = this.kmsClient
.encrypt(encryptRequest);
return encryptResponse.ciphertextBlob();
}
The EncryptRequest
returns an EncryptResponse. We use the ciphertextBlob
method to extract the encrypted data from the response. Note that this data is Base64
encoded when accessing it through the HTTP API as we do in this tutorial (remember the Java SDK is a wrapper around AWS Rest APIs and Rest is typically – but not always – HTTP/HTTPS).
The SdkBytes class is Amazon’s wrapper around bytes. It can be created from byte
arrays, a ByteBuffer
, InputStream
, or a String
. The AWS SDK consistently uses this class rather than the classes the SdkBytes
wraps. In the preceding code, we used the cipherTextBlob
to obtain the encrypted data from the response to our request to encrypt data using the CMK. The cipherTextBlob
returns an SdkBytes
.
- Add a method named
writeToFile
that takes the SdkBytes
to write and the path to the file to write the data to.
public static void writeToFile(SdkBytes bytesToWrite, String path ) throws
IOException {
FileChannel fc;
FileOutputStream outputStream = new FileOutputStream(path);
fc = outputStream.getChannel();
fc.write(bytesToWrite.asByteBuffer());
outputStream.close();
fc.close();
}
The writeToFile
method writes the encrypted data to a file using the Java NIO API standard in the JDK. We use the SDKBytes
asByteBuffer
method to convert the data to a ByteBuffer
so that the FileChannel
can write the data to a file.
- Modify
main
to open the observation.json file as an InputStream
. - Create the input
SdkBytes
from the InputStream
. - Call the
encrypt
method and assign the returned SdkBytes
to a variable. - Save the
SdkBytes
to a file using the writeToFile
method.
public static void main(String[] args) {
try
{
KMSExample kmsExample = new KMSExample();
InputStream in = kmsExample.getClass().getClassLoader()
.getResourceAsStream("observation.json");
SdkBytes inputBytes = SdkBytes.fromInputStream(in);
SdkBytes outputBytes = kmsExample.encrypt(inputBytes);
String path = Paths.get(".").toAbsolutePath().normalize().toString() +
"/observation_encrypt.json";
KMSExample.writeToFile(outputBytes, path);
}
catch (Exception e) {
e.printStackTrace();
}
}
- Build and run the application. You should see a new file named
observation_encrypted.json
. The content is binary and encrypted.
Decrypt
Let’s decrypt the encrypted data using the CMK used to encrypt the data.
- Add a method named
deCrypt
to KMSTutorial
that takes SdkBytes
and returns SdkBytes
. - Create a
DecryptRequest
by assigning it the encrypted text. - Have the
KmsClient
decrypt the request and assign the results to a DecryptResponse
. - Return the response text.
public SdkBytes deCrypt(SdkBytes encryptedJsonString) {
DecryptRequest decryptRequest = DecryptRequest.builder()
.ciphertextBlob(encryptedJsonString).build();
DecryptResponse decryptResponse = this.kmsClient
.decrypt(decryptRequest);
return decryptResponse.plaintext();
}
A DecryptRequest uses a DecryptRequest.Builder
to build itself. The builder takes the encrypted text and sends it to the KmsClient
. Note that it does not require passing the key’s ARN to decrypt.
- Modify
main
to decrypt the encrypted file and print the results to the console.
public static void main(String[] args) {
try
{
KMSExample kmsExample = new KMSExample();
InputStream in = kmsExample.getClass().getClassLoader()
.getResourceAsStream("observation.json");
SdkBytes inputBytes = SdkBytes.fromInputStream(in);
SdkBytes outputBytes = kmsExample.encrypt(inputBytes);
String path = Paths.get(".").toAbsolutePath().normalize().toString() +
"/observation_encrypt.json";
KMSExample.writeToFile(outputBytes, path);
SdkBytes output2Bytes = kmsExample.deCrypt(KMSExample
.readFromFile(path));
System.out.println(output2Bytes.asUtf8String());
}
catch (Exception e) {
e.printStackTrace();
}
}
- Build and run the application and you should see the decrypted content, the same as the original file’s content, printed to the console.
Complete KMSExample
Following is the complete source for the KMSExample
class:
package com.tutorial.aws.kms;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.awssdk.services.kms.model.DecryptRequest;
import software.amazon.awssdk.services.kms.model.DecryptResponse;
import software.amazon.awssdk.services.kms.model.EncryptRequest;
import software.amazon.awssdk.services.kms.model.EncryptResponse;
public class KMSExample {
final static String key = "<key_value_here>";
final static String secretKey = "<secret_key_value_here";
final static String keyArn = "<key_arn_here>";
public KmsClient kmsClient;
public KMSExample() {
AwsBasicCredentials awsCreds = AwsBasicCredentials.create(key,
secretKey);
this.kmsClient = KmsClient.builder()
.credentialsProvider(StaticCredentialsProvider.create(awsCreds))
.region(Region.US_EAST_1).build();
}
public SdkBytes encrypt(SdkBytes jsonString) {
EncryptRequest encryptRequest =
EncryptRequest.builder().keyId(keyArn).plaintext(jsonString).build();
EncryptResponse encryptResponse = this.kmsClient
.encrypt(encryptRequest);
return encryptResponse.ciphertextBlob();
}
public SdkBytes deCrypt(SdkBytes encryptedJsonString) {
DecryptRequest decryptRequest = DecryptRequest.builder()
.ciphertextBlob(encryptedJsonString).build();
DecryptResponse decryptResponse = this.kmsClient
.decrypt(decryptRequest);
return decryptResponse.plaintext();
public static void main(String[] args) {
try
{
KMSExample kmsExample = new KMSExample();
InputStream in = kmsExample.getClass().getClassLoader()
.getResourceAsStream("observation.json");
SdkBytes inputBytes = SdkBytes.fromInputStream(in);
SdkBytes outputBytes = kmsExample.encrypt(inputBytes);
String path = Paths.get(".").toAbsolutePath().normalize()
.toString() + "/observation_encrypt.json";
KMSExample.writeToFile(outputBytes, path);
SdkBytes output2Bytes = kmsExample.deCrypt(KMSExample
.readFromFile(path));
System.out.println(output2Bytes.asUtf8String());
}
catch (Exception e) {
e.printStackTrace();
}
}
public static void writeToFile(SdkBytes bytesToWrite,
String path) throws IOException {
FileChannel fc;
FileOutputStream outputStream = new FileOutputStream(path);
fc = outputStream.getChannel();
fc.write(bytesToWrite.asByteBuffer());
outputStream.close();
fc.close();
}
public static SdkBytes readFromFile(String path) throws IOException {
InputStream in2 = new FileInputStream(path);
return SdkBytes.fromInputStream(in2);
}
}
Encrypting and Decrypting Using Data Keys
Recall you cannot export CMKs from AWS KMS. You are also limited to encrypting data of 4kb or less. Both these limitations limit what you can encrypt using a CMK. Also note that you must send the data to the AWS KMS to encrypt/decrypt the data. Although convenient, using a CMK as we did in the preceding section is not ideal. Instead, we should use the CMK as a master key that generates, encrypts, and decrypts data keys. You then use data keys. The CMK is responsible only for encrypting/decrypting data keys.
Data keys are designed to be used within your external application that resides outside KMS. Data keys can encrypt/decrypt data of any size and are stored in your own application.
Envelope Encryption is how AWS KMS protects the generated data key. The KMS creates a data key, encrypts the data key, and returns the encrypted data key version and the plain-text unencrypted version of the data key. The plain-text version of the key is what your application uses to encrypt and decrypt data. The encrypted version of the key is what your application saves to use later. You should always ensure the plain-text data key is deleted and removed from memory soon after use so your data’s security is not compromised. When your application needs to use the data key again, request that the AWS CMK decrypts the data key and then use that decrypted key locally.
In the example below, we use the CMK above to generate a data key and use that data key to encrypt data. The steps to encrypt in this tutorial are as follows:
- Request that the CMK in KMS generates a data key.
- Load unencrypted data from file.
- Use plain-text version of returned data key to encrypt data.
- Store encrypted data to a file.
- Store encrypted version of key to a file.
Using a data key to encrypt data
After encrypting the data, we then decrypt the data using the data key. However, before we can decrypt the data using the data key, we must first use the CMK to decrypt the data key. The process our application uses is as follows:
- Load encrypted data key from file.
- Request AWS KMS used to originally encrypt the data key to decrypt the data key.
- Load encrypted object from file.
- Use decrypted data key to decrypt object.
- Print decrypted data to console.
Using a data key to decrypt data
Although we use the KMS to work with the CMK, we must work with the data key locally. We do so using the Java crypto package.
Encrypt Using Data Key
Let’s encrypt the data using a data key:
- Create a new method named
encryptUsingDataKey
that takes the data to encrypt as an SDKBytes
data object. - Create a
GenerateDataKeyRequest
using the KmsTutorialKey
customer key. - Use the
KmsClient
to return the generated data key as a GenerateDataKeyResponse
. - Use the Java crypto API to create a
SecretKeySpec
from the generated data key. - Create a
Cipher
and use it to encrypt the jsonString
. - Write the encrypted data to a file.
- Get the encrypted data key from the response and save it to a file.
public void encryptUsingDataKey(SdkBytes jsonString) {
try {
GenerateDataKeyRequest generateDataKeyRequest = GenerateDataKeyRequest
.builder().keyId(keyArn)
.keySpec(DataKeySpec.AES_128).build();
GenerateDataKeyResponse generateDataKeyResponse = this.kmsClient
.generateDataKey(generateDataKeyRequest);
SecretKeySpec key = new
SecretKeySpec(generateDataKeyResponse.plaintext().asByteArray(),
"AES");
Cipher cipher;
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encodedSecret = cipher.doFinal(jsonString.asByteArray());
byte[] encryptedDataKey = key.getEncoded();
String path = Paths.get(".").toAbsolutePath().normalize().toString()
+ "/observation_datakey_encrypt.json";
KMSExample.writeToFile(SdkBytes.fromByteArray(encodedSecret),
path);
path = Paths.get(".").toAbsolutePath().normalize().toString() +
"/data_key_encrypt.json";
KMSExample.writeToFile(SdkBytes.fromByteArray(encryptedDataKey), path);
} catch (Exception ex) {
ex.printStackTrace();
}
}
The GenerateDataKeyRequest wraps a request to generate a data key. You create a GenerateDataKeyRequest
using a GenerateDataKeyRequest.Builder
. The builder uses the CMK’s ARN and data keyspec to build the request. The KmsClient
then passes the request to the KMS which uses the specified CMK to generate the data key. The data key is returned in a GenerateDataKeyResponse.
The AES specifies we wish our data key to use the Advanced Encryption Standard. We must use the same keyspec when using our data key that was used to generate the data key.
We take the plain-text (unencrypted) key version from the response and use it to build a <a href="https://docs.oracle.com/javase/8/docs/api/javax/crypto/spec/SecretKeySpec.html" rel="noreferrer noopener">SecretKeySpec</a>
. We then pass the SecretKeySpec
to a Cipher, which encrypts the data using the Java Cryptography Extension framework. You can obtain more information by consulting this resource: Java Cryptography Architecture (JCA) Reference Guide.
- Modify
main
to call encryptUsingDataKey
. - Remove the code that used the CMK to encrypt and decrypt the data.
kmsExample.encryptUsingDataKey(inputBytes);
The main
method should appear as follows after adding the call to encrypt the data using a data key.
public static void main(String[] args) {
try
{
KMSExample kmsExample = new KMSExample();
String path = Paths.get(".").toAbsolutePath().normalize()
.toString() + "/observation.json";
kmsExample.encryptUsingDataKey(inputBytes);
}
catch (Exception e) {
e.printStackTrace();
}
}
- Build and run the program and there should be two files: data_key_encrypt.txt
and observation_datakey_encrypt.json, the encrypted data key and the encrypted data respectively.
Encrypted data key stored locally on disk
Encrypted data stored locally on disk
Decrypting Using Data Key
Let’s decrypt the encrypted data key and use it to decrypt the encrypted data. We first decrypt the local data key by passing it to KMS which uses the CMK to decrypt the key. We then use the returned decrypted key to decrypt the data locally.
- Create a new method named
decryptUsingDataKey
. - Read the encrypted data key from the file.
- Create a new
DecryptRequest
from the data key. - Decrypt the data key by passing the
DecryptRequest
to the kmsClient
. - Create a
SecretKeySpec
from the decrypted data key. - Read the encrypted data from a file.
- Create a
Cipher
and use it and the SecretKeySpec
to decrypt the data.
public void decryptUsingDataKey() {
try {
String path = Paths.get(".").toAbsolutePath().normalize().toString() +
"/resource/data_key_encrypt.txt";
SdkBytes sdkBytes = KMSExample.readFromFile(path);
DecryptRequest decryptRequest = DecryptRequest.builder()
.ciphertextBlob(sdkBytes).build();
DecryptResponse decryptResponse = this.kmsClient
.decrypt(decryptRequest);
SecretKeySpec secretKeySpec = new SecretKeySpec(decryptResponse
.plaintext().asByteArray(), "AES");
path = Paths.get(".").toAbsolutePath().normalize().toString() +
"/observation_datakey_encrypt.json";
sdkBytes = KMSExample.readFromFile(path);
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
System.out.println(SdkBytes.fromByteArray(cipher.doFinal(sdkBytes
.asByteArray())).asUtf8String());
} catch(Exception ex) {
ex.printStackTrace();
}
}
- Modify
main
to call the decryptUsingDataKey
method.
kmsExample.decryptUsingDataKey();
The main
method should appear as follows:
public static void main(String[] args) {
try
{
KMSExample kmsExample = new KMSExample();
String path = Paths.get(".").toAbsolutePath().normalize()
.toString() + "/resource/observation.json";
KMSExample.writeToFile(outputBytes, path);
SdkBytes output2Bytes = kmsExample.deCrypt(KMSExample
.readFromFile(path));
System.out.println(output2Bytes.asUtf8String());
kmsExample.encryptUsingDataKey(inputBytes);
kmsExample.decryptUsingDataKey();
}
catch (Exception e) {
e.printStackTrace();
}
}
- Build and run the application. You should see the JSON record printed to the console.
{
"stationid": 221,
"date": "1992-03-12",
"time": "091312",
"message":"This is a secret message. Please encrypt it when storing on disk."
}
Conclusion
Amazon’s KMS is a convenient and powerful service to manage your organization’s keys. It is integrated with most AWS Services. You can also use it directly in your application, as demonstrated in this tutorial. The most common use pattern is to create a CMK which must reside in KMS as your master key. That CMK is then used to create local data keys. The local data keys encrypt/decrypt the data. Only the encrypted version of the data key should ever be persisted locally. Instead, whenever the local data key is needed, it is passed to KMS so that the associated CMK can decrypt the data key.
You can also administer KMS using the Java API; however, in this tutorial, we restricted ourselves to decrypting and encrypting data.
More Resources
Here are two introductory videos on KMS. Neither are programming specific, but they both provide a greater understanding of KMS.
Git Project
The github project is available at https://github.com/jamesabrannan/kmstutorial.