Installation Instructions

These instructions show how to startup the IronCMK Encryption Service which includes both a Docker container as well as a Java SDK that can be used to interact with the service to encrypt and decrypt content. The Docker image can be run locally or in a centralized hosted environment depending on requirements.

Startup of Docker Encryption Service

The CMK Docker image is hosted publicly on Docker Hub. Find the latest tag available and pull down the image by running

docker pull ironcorelabs/encryption-service:{tag}

Note: You will need a keys.env to run the encryption service. If you do not have a keys.env file, please contact IronCore.

Once successful you should be able to see the image listed in the docker images list. Upon startup, the image expects a number of environment variables that tie the service to a specific environment and set of keys. These variables will have been provided in a keys.env file. In order to startup the Docker container you can run

docker run --env-file keys.env -p 32804:7777 ironcorelabs/encryption-service:{tag}

The exposed port 32804 can be changed to any value if you want to run the service on a different port. If the image starts up successfully then the API service will be running locally on the provided port.

Java SDK

The Java SDK provides the ability to interact with the Encryption Service and is used to encrypt and decrypt content. Content encrypted with the SDK are encrypted with AES-256 symmetric encryption.

Minimum Java Version Supported: Java 8 Update 152

SDK Dependency

The SDK to interact with the API is published publicly to Maven. This page has details on how to pull in the SDK as a dependency depending on what build/dependency system your Java project uses. The SDK can also be downloaded directly as a JAR file if needed.

Once pulled in as a dependency the documentation is available here. The IronCMK, EncryptedResult, and DocumentMetadata classes will be the only ones you'll interact with.

IronCMK Instantiation

The IronCMK class takes two arguments as part of it's constructor, the Encryption Service domain and the API key used to authorize requests to the service.

Encryption Service Domain: Because the Encryption Service docker container can be run in various environments, the IronCMK class needs to know what domain to make requests to. This argument should be a fully qualified domain including the port. If running the Docker Encryption Service locally, this domain will point to a localhost domain.

API Key: The API key is used to authorize requests to the Encryption Service. This key can be found in the keys.env file as API_KEY. The API Key passed to the Encryption Service on startup and the key used to initialize the IronCMK SDK must match. If you do not have an API key, please contact IronCore.

//If using the Docker command above, the domain and port of the service would be specified like this
IronCMK cmkSDK = new IronCMK("http://localhost:32804", "<YOUR `API_KEY` HERE>");

Encrypting and Decrypting Documents

It's important to note that when encrypting and decrypting content, the bytes to encrypt and decrypt are never passed to the Encryption Service. The interaction with this service only passes keys back and forth, never actual document data. All encryption and decryption are done directly in the Java SDK. All documents are encrypted with AES-256 in GCM mode.

Encrypt

Single documents (a document is any chunk of a data - field, row, JSON object, etc.) can be encrypted using a unique cryptographic key or you can also encrypt multiple documents/document fields using the same key. The latter is useful if, for example, you have multiple columns in a single database row that need to be encrypted together. This allows you to only have to store a single key for each row of encrypted data.

When encrypting documents you will need to provide a map from the document id/name to the bytes to encrypt as well as metadata about the document you'll be encrypting. The metadata you'll provide has three required fields (tenantID, requestingUserOrServiceID, and dataLabel) and one optional field (otherData).

tenantID: The Encryption Service expects to be able to run in a multi-tenant environment where it can encrypt and decrypt documents that are associated to various tenants/customers. The tenant ID should be a unique ID which will encrypt all encrypted documents to that tenants key. See the note below about some special reserved tenant IDs that can be used to experiement with having different tenants integrate with different KMS systems.

requestingUserOrServiceID: A unique ID that denotes which user or service is making the request to encrypt data. This will be used to log access of the EncryptionService.

dataLabel: A label to apply to the data being encrypted that can be used to properly classify encrypted data.

otherData: Any other String key/value pairs to apply to the encrypted data. This data will be sent to the EncryptionService.

DocumentMetadata metadata = new DocumentMetadata("Customer1", "AnalyticsService", "PII");

Map<String, byte[]> documents = new HashMap<>();
documents.put("secret1", bytes1);
documents.put("secret2", bytes2);

cmkSDK.encrypt(documents, metadata);

The encrypt operation takes the map of document id/name to bytes to encrypt and the metadata and returns a CompletableFuture which will resolve with an instance of the EncryptedResult class which contains both a map of document id/name to encrypted bytes (getEncryptedDocumentMap()) as well as a base64 encoded encrypted document key (getEncryptedKey()) that was used to encrypt the document(s). Both of these values must be stored and associated together as they are both required in order to decrypt document content.

EncryptedDocument encryptedDocuments = cmkSDK.encrypt(documentMap, metadata).get();
byte[] encryptedSecret1 = encryptedDocuments.getEncryptedDocumentMap().get("secret1");
byte[] encryptedSecret2 = encryptedDocuments.getEncryptedDocumentMap().get("secret2");

The encrypt operation is non-blocking by default and runs on a separate thread via the CompletableFuture. The result of encrypt can be composed together with other non-blocking calls or it can be retrieved using the blocking get() method.

//Blocking
EncryptedDocument encryptedDoc = cmkSDK.encrypt(documentMap, metadata).get();

//Non-blocking
CompletableFuture
  .supplyAsync(() -> cmkSDK.encrypt(documentMap, metadata))
  .thenCompose(encryptedDoc -> {
      String encryptedKey = encryptedDoc.getEncryptedKey();
      ...
  })

Decrypt

In order to decrypt a document, the decrypt method can be called which takes a map of document id/name to encrypted bytes, the base64 encoded encrypted document key, and the document metadata. Just like encrypt, this method returns a CompletableFuture that can be sequenced into another operation if needed. The CompletableFuture will resolve with a map of the document id/names to decrypted bytes.

DocumentMetadata metadata = new DocumentMetadata("Customer1", "AnalyticsService", "PII");
Map<String, byte[]> documents = new HashMap<>();
documents.put("secret1", encryptedDocumentBytes1);
documents.put("secret2", encryptedDocumentBytes2);

//Blocking
Map<String, byte[]> decryptedDocMap = cmkSDK.decrypt(documents, encryptedDocumentKey, metadata).get();
byte[] decryptedSecret1 = decryptedDocMap.get("secret1");

//Non-blocking
CompletableFuture
  .supplyAsync(() -> cmkSDK.decrypt(documents, encryptedDocumentKey, metadata))
  .thenCompose(decryptedDocMap -> {
      byte[] decryptedSecret2 = decryptedDocMap.get("secret2");
  })

The tenantID field of the provided metadata must be the same as when the data was encrypted so that the proper tenant key is used during decryption. The other metadata fields do not have to match and are just used as part of auditing.

Special Tenant IDs

The Encryption Service supports integrating with multiple different KMS systems. Each tenant can be configured to integrate with a different KMS system. Currently the Encryption Service supports using Amazon (AWS), Google (GCP), or IconCore as the KMS system. By default, all tenants are configured to use IconCore as the KMS. Keys will be generated for these tenants on demand. The first request for a tenant will generate a random cryptographic key for that tenant, store it encrypted with IronCore, and the key will then be used for all future operations on that tenant.

Since the configuration of which KMS a tenant should integrate with is not yet available, we've configured two fixed tenant IDs to integrate with pre-provisioned AWS and GCP KMS instances. The following tenant IDs can be used to simulate a customer who is using either an AWS or GCP KMS. For requests to these tenant IDs, requests will be made to their external KMS for each encrypt/decrypt call.

AWS-KMS: Using this tenant ID will cause all content to be encrypted with a key provisioned by a pre-configured Amazon KMS. Keys for this instance are still using AES-256 keys.

GCP-KMS: Using this tenant ID will cause all content to be encrypted with a key provisioned by a pre-configured Google Cloud KMS. Keys for this instance are still using AES-256 keys.

All other tenant IDs will use the IronCore KMS.

Examples

Examples of using the SDK have also been provided that show how to encrypt files and database rows.

DatabaseExample.java: Encrypt and decrypt data that is stored in a SQL database. Shows how to encrypt multiple columns to the same key and decrypt all the columns in a single SDK call.

FileExample.java: Encrypt and decrypt data stored on a file system.