Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## v8.1.0

- Add `CachedKey`, which fetches then holds a cached DEK (Document Encryption Key) for repeated encrypt and decrypt operations without making additional TSP wrap/unwrap calls.
This is useful for many cryptographic operations using the same key in quick succession, such as inside a database transaction. Note that this key automatically expires after a short period of time.

## v8.0.1

- We’ve removed the direct constructors for `TenantSecurityClient` and replaced them with a builder-based API. The static TenantSecurityClient.create method is still provided for convenience.
Expand Down
63 changes: 63 additions & 0 deletions examples/cached-key-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Cached Key Example

In order to run this example, you need to be running a _Tenant Security Proxy_ (TSP) on your machine.
Check the [README.md](../README.md) file in the parent directory to see how to start the TSP, if you haven't done so
yet.

Once the TSP is running, you can experiment with this example Java program. It demonstrates using
`CachedEncryptor` and `CachedDecryptor` to encrypt and decrypt multiple records while minimizing
calls to the TSP. The example code shows two scenarios:

- encrypting three of a customer's records using a single cached key (one TSP wrap call)
- decrypting all three records using a single cached key (one TSP unwrap call)

## Why use a cached key?

A normal `encrypt()` call wraps a new DEK through the TSP on every invocation. If you're encrypting
several records in quick succession (like inside a database transaction), each call
adds a network round trip.

A `CachedEncryptor` wraps the DEK once, then all subsequent `encrypt()` calls are purely local
CPU work. This means you can safely encrypt inside a database transaction without adding network
latency or external failure modes to the transaction. The same applies to `CachedDecryptor` for
reads.

## Running the example

To run the example, you will need to have a Java JRE 17+ and Maven installed on your computer.

```bash
export API_KEY='0WUaXesNgbTAuLwn'
mvn package
java -cp target/cached-key-example-0.1.0.jar com.ironcorelabs.cachedkey.CachedKeyExample
```

We've assigned an API key for you, but in production you will make your own and edit the TSP
configuration with it. This should produce output like:

```
Using tenant tenant-gcp-l
Encrypted 3 records with one TSP call
Decrypted: Jim Bridger / 000-12-2345
Decrypted: John Colter / 000-45-6789
Decrypted: Sacagawea / 000-98-7654
Decrypted 3 records with one TSP call
```

If you look at the TSP logs, you should see only two KMS operations: one wrap and one unwrap.
Without cached keys, the same work would have required six KMS operations (three wraps + three
unwraps).

If you would like to experiment with a different tenant, just do:

```bash
export TENANT_ID=<selected-tenant-ID>
java -cp target/cached-key-example-0.1.0.jar com.ironcorelabs.cachedkey.CachedKeyExample
```

The list of available tenants is listed in the [README.md](../README.md) in the parent directory.

## Additional Resources

If you would like some more in-depth information, our website features a section of technical
documentation about the [SaaS Shield product](https://ironcorelabs.com/docs/saas-shield/).
85 changes: 85 additions & 0 deletions examples/cached-key-example/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>

<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.ironcorelabs</groupId>
<artifactId>cached-key-example</artifactId>
<packaging>jar</packaging>
<version>0.1.0</version>

<name>cached-key-example</name>
<url>https://www.docs.ironcorelabs.com</url>
<licenses>
<license>
<name>Apache-2</name>
<url>https://opensource.org/licenses/Apache-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>com.ironcorelabs</groupId>
<artifactId>tenant-security-java</artifactId>
<version>8.1.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<testSource>1.8</testSource>
<testTarget>1.8</testTarget>
<compilerArgument>-Xlint:unchecked</compilerArgument>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.ironcorelabs.cachedkey;

import com.ironcorelabs.tenantsecurity.kms.v1.*;
import com.ironcorelabs.tenantsecurity.kms.v1.exception.TenantSecurityException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;

/**
* Demonstrates using CachedEncryptor and CachedDecryptor to encrypt/decrypt multiple records with a
* single TSP call.
*/
public class CachedKeyExample {

private static final String TSP_ADDR = "http://localhost:32804";

public static void main(String[] args) throws Exception {

String API_KEY = System.getenv("API_KEY");
if (API_KEY == null) {
System.out.println("Must set the API_KEY environment variable.");
System.exit(1);
}

String tenantId = System.getenv("TENANT_ID");
if (tenantId == null) {
tenantId = "tenant-gcp-l";
}
System.out.println("Using tenant " + tenantId);

TenantSecurityClient client =
new TenantSecurityClient.Builder(TSP_ADDR, API_KEY).allowInsecureHttp(true).build();

DocumentMetadata metadata = new DocumentMetadata(tenantId, "serviceOrUserId", "PII");

// Simulate a database table: each row has an encrypted record and its EDEK
List<Map<String, byte[]>> encryptedRows = new ArrayList<>();
String sharedEdek;

// Encrypt: one TSP call, then N local encrypts
//
// In a real application this block would be inside a database transaction. The
// createCachedEncryptor call is the only network round trip — every encrypt() after
// that is purely local CPU work, so it won't add latency or failure modes to the
// transaction.
try (CachedEncryptor encryptor = client.createCachedEncryptor(metadata).get()) {
String[][] customers =
{{"000-12-2345", "2825-519 Stone Creek Rd, Bozeman, MT 59715", "Jim Bridger"},
{"000-45-6789", "100 Main St, Helena, MT 59601", "John Colter"},
{"000-98-7654", "742 Evergreen Terrace, Missoula, MT 59801", "Sacagawea"},};

for (String[] customer : customers) {
Map<String, byte[]> record = new HashMap<>();
record.put("ssn", customer[0].getBytes(StandardCharsets.UTF_8));
record.put("address", customer[1].getBytes(StandardCharsets.UTF_8));
record.put("name", customer[2].getBytes(StandardCharsets.UTF_8));

// This encrypt is local — no TSP call
EncryptedDocument encrypted = encryptor.encrypt(record, metadata).get();
encryptedRows.add(encrypted.getEncryptedFields());
}

// All rows share this EDEK; store it alongside the rows (or once per batch)
sharedEdek = encryptor.getEdek();

System.out
.println("Encrypted " + encryptor.getOperationCount() + " records with one TSP call");
}
// leaving the `try` block zeroes the DEK and reports usage to the TSP

// Decrypt: one TSP call, then N local decrypts
try (CachedDecryptor decryptor = client.createCachedDecryptor(sharedEdek, metadata).get()) {
for (Map<String, byte[]> row : encryptedRows) {
EncryptedDocument doc = new EncryptedDocument(row, sharedEdek);

// This decrypt is local — no TSP call
PlaintextDocument plaintext = decryptor.decrypt(doc, metadata).get();
Map<String, byte[]> fields = plaintext.getDecryptedFields();

System.out.println("Decrypted: " + new String(fields.get("name"), StandardCharsets.UTF_8)
+ " / " + new String(fields.get("ssn"), StandardCharsets.UTF_8));
}

System.out
.println("Decrypted " + decryptor.getOperationCount() + " records with one TSP call");
}

System.exit(0);
}
}
3 changes: 1 addition & 2 deletions examples/large-documents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ fields that share a DEK. The example code shows two scenarios:
- encrypting a large document as many subdocs, using the disk for persistence
- retrieving and decrypting subdocs individually

To run the example, you will need to have Java and Maven installed on your computer. Try a `java -version` to see
what version you are using. We tested the example code using 1.8.
To run the example, you will need to have Java JRE 17+ and Maven installed on your computer.

If java is ready to go, execute these commands:

Expand Down
3 changes: 1 addition & 2 deletions examples/logging-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ to use the Tenant Security Client (TSC) SDK to log security events. The example
- logging a user create security event with minimal metadata
- logging a login security event with additional metadata

To run the example, you will need to have Java and Maven installed on your computer. Try a `java -version` to see
what version you are using. We tested the example code using 1.8.
To run the example, you will need to have Java JRE 17+ and Maven installed on your computer.

If java is ready to go, execute these commands:

Expand Down
2 changes: 1 addition & 1 deletion examples/rekey-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ to use the Tenant Security Client (TSC) SDK to rekey data. The example code cont
- Rekeying the encrypted record to a new tenant
- Decrypting the encrypted record with the new tenant

To run the example, you will need to have a Java JRE 8+ and Maven installed on your computer.
To run the example, you will need to have a Java JRE 17+ and Maven installed on your computer.

```bash
export API_KEY='0WUaXesNgbTAuLwn'
Expand Down
2 changes: 1 addition & 1 deletion examples/simple-roundtrip/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ to use the Tenant Security Client (TSC) SDK to encrypt and decrypt data. The exa
- encryption and decryption of a record that you might store in a key-value store or a database
- encryption and decryption of a file, using the file-system for storage

To run the example, you will need to have a Java JRE 8+ and Maven installed on your computer.
To run the example, you will need to have a Java JRE 17+ and Maven installed on your computer.

```bash
export API_KEY='0WUaXesNgbTAuLwn'
Expand Down
12 changes: 6 additions & 6 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
maven
openjdk17
];
# Nix sets SOURCE_DATE_EPOCH to 1980-01-01T00:00:00Z (315532800) for
# reproducible builds, but maven-javadoc-plugin requires at least
# 1980-01-01T00:00:02Z. Bump by 2 seconds to satisfy the validation.
SOURCE_DATE_EPOCH = 315532802;
};
}
);
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<groupId>com.ironcorelabs</groupId>
<artifactId>tenant-security-java</artifactId>
<packaging>jar</packaging>
<version>8.0.1</version>
<version>8.1.0-SNAPSHOT</version>
<name>tenant-security-java</name>
<url>https://ironcorelabs.com/docs</url>
<description>Java client library for the IronCore Labs Tenant Security Proxy.</description>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ironcorelabs.tenantsecurity.kms.v1;

/**
* A cached document decryptor that holds a DEK for repeated decrypt operations without making
* additional TSP unwrap calls. Can only decrypt documents that were encrypted with the same
* DEK/EDEK pair.
*
* <p>
* Instances are created via
* {@link TenantSecurityClient#createCachedDecryptor(String, DocumentMetadata)} or
* {@link TenantSecurityClient#withCachedDecryptor}. The cached key should be closed when done to
* securely zero the DEK.
*
* @see TenantSecurityClient#createCachedDecryptor(String, DocumentMetadata)
* @see TenantSecurityClient#withCachedDecryptor(String, DocumentMetadata,
* java.util.function.Function)
*/
public interface CachedDecryptor extends DocumentDecryptor, CachedKeyLifecycle {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.ironcorelabs.tenantsecurity.kms.v1;

/**
* A cached document encryptor that holds a DEK for repeated encrypt operations without making
* additional TSP wrap calls. All documents encrypted with this instance share the same DEK/EDEK
* pair.
*
* <p>
* Instances are created via {@link TenantSecurityClient#createCachedEncryptor(DocumentMetadata)} or
* {@link TenantSecurityClient#withCachedEncryptor}. The cached key should be closed when done to
* securely zero the DEK.
*
* @see TenantSecurityClient#createCachedEncryptor(DocumentMetadata)
* @see TenantSecurityClient#withCachedEncryptor(DocumentMetadata, java.util.function.Function)
*/
public interface CachedEncryptor extends DocumentEncryptor, CachedKeyLifecycle {
}
Loading
Loading