Skip to content

Commit fed1c11

Browse files
authored
Add cached key encryptor/decryptor (#157)
1 parent b2f820e commit fed1c11

23 files changed

Lines changed: 2020 additions & 109 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## v8.1.0
4+
5+
- 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.
6+
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.
7+
38
## v8.0.1
49

510
- 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.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Cached Key Example
2+
3+
In order to run this example, you need to be running a _Tenant Security Proxy_ (TSP) on your machine.
4+
Check the [README.md](../README.md) file in the parent directory to see how to start the TSP, if you haven't done so
5+
yet.
6+
7+
Once the TSP is running, you can experiment with this example Java program. It demonstrates using
8+
`CachedEncryptor` and `CachedDecryptor` to encrypt and decrypt multiple records while minimizing
9+
calls to the TSP. The example code shows two scenarios:
10+
11+
- encrypting three of a customer's records using a single cached key (one TSP wrap call)
12+
- decrypting all three records using a single cached key (one TSP unwrap call)
13+
14+
## Why use a cached key?
15+
16+
A normal `encrypt()` call wraps a new DEK through the TSP on every invocation. If you're encrypting
17+
several records in quick succession (like inside a database transaction), each call
18+
adds a network round trip.
19+
20+
A `CachedEncryptor` wraps the DEK once, then all subsequent `encrypt()` calls are purely local
21+
CPU work. This means you can safely encrypt inside a database transaction without adding network
22+
latency or external failure modes to the transaction. The same applies to `CachedDecryptor` for
23+
reads.
24+
25+
## Running the example
26+
27+
To run the example, you will need to have a Java JRE 17+ and Maven installed on your computer.
28+
29+
```bash
30+
export API_KEY='0WUaXesNgbTAuLwn'
31+
mvn package
32+
java -cp target/cached-key-example-0.1.0.jar com.ironcorelabs.cachedkey.CachedKeyExample
33+
```
34+
35+
We've assigned an API key for you, but in production you will make your own and edit the TSP
36+
configuration with it. This should produce output like:
37+
38+
```
39+
Using tenant tenant-gcp-l
40+
Encrypted 3 records with one TSP call
41+
Decrypted: Jim Bridger / 000-12-2345
42+
Decrypted: John Colter / 000-45-6789
43+
Decrypted: Sacagawea / 000-98-7654
44+
Decrypted 3 records with one TSP call
45+
```
46+
47+
If you look at the TSP logs, you should see only two KMS operations: one wrap and one unwrap.
48+
Without cached keys, the same work would have required six KMS operations (three wraps + three
49+
unwraps).
50+
51+
If you would like to experiment with a different tenant, just do:
52+
53+
```bash
54+
export TENANT_ID=<selected-tenant-ID>
55+
java -cp target/cached-key-example-0.1.0.jar com.ironcorelabs.cachedkey.CachedKeyExample
56+
```
57+
58+
The list of available tenants is listed in the [README.md](../README.md) in the parent directory.
59+
60+
## Additional Resources
61+
62+
If you would like some more in-depth information, our website features a section of technical
63+
documentation about the [SaaS Shield product](https://ironcorelabs.com/docs/saas-shield/).
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<project xmlns="http://maven.apache.org/POM/4.0.0"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
6+
<modelVersion>4.0.0</modelVersion>
7+
8+
<groupId>com.ironcorelabs</groupId>
9+
<artifactId>cached-key-example</artifactId>
10+
<packaging>jar</packaging>
11+
<version>0.1.0</version>
12+
13+
<name>cached-key-example</name>
14+
<url>https://www.docs.ironcorelabs.com</url>
15+
<licenses>
16+
<license>
17+
<name>Apache-2</name>
18+
<url>https://opensource.org/licenses/Apache-2.0</url>
19+
<distribution>repo</distribution>
20+
</license>
21+
</licenses>
22+
23+
<properties>
24+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
25+
<maven.compiler.source>1.8</maven.compiler.source>
26+
<maven.compiler.target>1.8</maven.compiler.target>
27+
</properties>
28+
29+
<dependencies>
30+
<dependency>
31+
<groupId>com.ironcorelabs</groupId>
32+
<artifactId>tenant-security-java</artifactId>
33+
<version>8.1.0</version>
34+
</dependency>
35+
</dependencies>
36+
37+
<build>
38+
<plugins>
39+
<plugin>
40+
<groupId>org.apache.maven.plugins</groupId>
41+
<artifactId>maven-gpg-plugin</artifactId>
42+
<version>1.6</version>
43+
<executions>
44+
<execution>
45+
<id>sign-artifacts</id>
46+
<phase>verify</phase>
47+
<goals>
48+
<goal>sign</goal>
49+
</goals>
50+
</execution>
51+
</executions>
52+
</plugin>
53+
<plugin>
54+
<groupId>org.apache.maven.plugins</groupId>
55+
<artifactId>maven-compiler-plugin</artifactId>
56+
<version>3.3</version>
57+
<configuration>
58+
<source>1.8</source>
59+
<target>1.8</target>
60+
<testSource>1.8</testSource>
61+
<testTarget>1.8</testTarget>
62+
<compilerArgument>-Xlint:unchecked</compilerArgument>
63+
</configuration>
64+
</plugin>
65+
<plugin>
66+
<groupId>org.apache.maven.plugins</groupId>
67+
<artifactId>maven-source-plugin</artifactId>
68+
<version>3.0.1</version>
69+
</plugin>
70+
<plugin>
71+
<groupId>org.apache.maven.plugins</groupId>
72+
<artifactId>maven-shade-plugin</artifactId>
73+
<version>3.2.1</version>
74+
<executions>
75+
<execution>
76+
<phase>package</phase>
77+
<goals>
78+
<goal>shade</goal>
79+
</goals>
80+
</execution>
81+
</executions>
82+
</plugin>
83+
</plugins>
84+
</build>
85+
</project>
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.ironcorelabs.cachedkey;
2+
3+
import com.ironcorelabs.tenantsecurity.kms.v1.*;
4+
import com.ironcorelabs.tenantsecurity.kms.v1.exception.TenantSecurityException;
5+
import java.nio.charset.StandardCharsets;
6+
import java.util.ArrayList;
7+
import java.util.HashMap;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.concurrent.ExecutionException;
11+
12+
/**
13+
* Demonstrates using CachedEncryptor and CachedDecryptor to encrypt/decrypt multiple records with a
14+
* single TSP call.
15+
*/
16+
public class CachedKeyExample {
17+
18+
private static final String TSP_ADDR = "http://localhost:32804";
19+
20+
public static void main(String[] args) throws Exception {
21+
22+
String API_KEY = System.getenv("API_KEY");
23+
if (API_KEY == null) {
24+
System.out.println("Must set the API_KEY environment variable.");
25+
System.exit(1);
26+
}
27+
28+
String tenantId = System.getenv("TENANT_ID");
29+
if (tenantId == null) {
30+
tenantId = "tenant-gcp-l";
31+
}
32+
System.out.println("Using tenant " + tenantId);
33+
34+
TenantSecurityClient client =
35+
new TenantSecurityClient.Builder(TSP_ADDR, API_KEY).allowInsecureHttp(true).build();
36+
37+
DocumentMetadata metadata = new DocumentMetadata(tenantId, "serviceOrUserId", "PII");
38+
39+
// Simulate a database table: each row has an encrypted record and its EDEK
40+
List<Map<String, byte[]>> encryptedRows = new ArrayList<>();
41+
String sharedEdek;
42+
43+
// Encrypt: one TSP call, then N local encrypts
44+
//
45+
// In a real application this block would be inside a database transaction. The
46+
// createCachedEncryptor call is the only network round trip — every encrypt() after
47+
// that is purely local CPU work, so it won't add latency or failure modes to the
48+
// transaction.
49+
try (CachedEncryptor encryptor = client.createCachedEncryptor(metadata).get()) {
50+
String[][] customers =
51+
{{"000-12-2345", "2825-519 Stone Creek Rd, Bozeman, MT 59715", "Jim Bridger"},
52+
{"000-45-6789", "100 Main St, Helena, MT 59601", "John Colter"},
53+
{"000-98-7654", "742 Evergreen Terrace, Missoula, MT 59801", "Sacagawea"},};
54+
55+
for (String[] customer : customers) {
56+
Map<String, byte[]> record = new HashMap<>();
57+
record.put("ssn", customer[0].getBytes(StandardCharsets.UTF_8));
58+
record.put("address", customer[1].getBytes(StandardCharsets.UTF_8));
59+
record.put("name", customer[2].getBytes(StandardCharsets.UTF_8));
60+
61+
// This encrypt is local — no TSP call
62+
EncryptedDocument encrypted = encryptor.encrypt(record, metadata).get();
63+
encryptedRows.add(encrypted.getEncryptedFields());
64+
}
65+
66+
// All rows share this EDEK; store it alongside the rows (or once per batch)
67+
sharedEdek = encryptor.getEdek();
68+
69+
System.out
70+
.println("Encrypted " + encryptor.getOperationCount() + " records with one TSP call");
71+
}
72+
// leaving the `try` block zeroes the DEK and reports usage to the TSP
73+
74+
// Decrypt: one TSP call, then N local decrypts
75+
try (CachedDecryptor decryptor = client.createCachedDecryptor(sharedEdek, metadata).get()) {
76+
for (Map<String, byte[]> row : encryptedRows) {
77+
EncryptedDocument doc = new EncryptedDocument(row, sharedEdek);
78+
79+
// This decrypt is local — no TSP call
80+
PlaintextDocument plaintext = decryptor.decrypt(doc, metadata).get();
81+
Map<String, byte[]> fields = plaintext.getDecryptedFields();
82+
83+
System.out.println("Decrypted: " + new String(fields.get("name"), StandardCharsets.UTF_8)
84+
+ " / " + new String(fields.get("ssn"), StandardCharsets.UTF_8));
85+
}
86+
87+
System.out
88+
.println("Decrypted " + decryptor.getOperationCount() + " records with one TSP call");
89+
}
90+
91+
System.exit(0);
92+
}
93+
}

examples/large-documents/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ fields that share a DEK. The example code shows two scenarios:
1111
- encrypting a large document as many subdocs, using the disk for persistence
1212
- retrieving and decrypting subdocs individually
1313

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

1716
If java is ready to go, execute these commands:
1817

examples/logging-example/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ to use the Tenant Security Client (TSC) SDK to log security events. The example
1010
- logging a user create security event with minimal metadata
1111
- logging a login security event with additional metadata
1212

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

1615
If java is ready to go, execute these commands:
1716

examples/rekey-example/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ to use the Tenant Security Client (TSC) SDK to rekey data. The example code cont
1515
- Rekeying the encrypted record to a new tenant
1616
- Decrypting the encrypted record with the new tenant
1717

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

2020
```bash
2121
export API_KEY='0WUaXesNgbTAuLwn'

examples/simple-roundtrip/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ to use the Tenant Security Client (TSC) SDK to encrypt and decrypt data. The exa
1010
- encryption and decryption of a record that you might store in a key-value store or a database
1111
- encryption and decryption of a file, using the file-system for storage
1212

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

1515
```bash
1616
export API_KEY='0WUaXesNgbTAuLwn'

flake.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
maven
1414
openjdk17
1515
];
16+
# Nix sets SOURCE_DATE_EPOCH to 1980-01-01T00:00:00Z (315532800) for
17+
# reproducible builds, but maven-javadoc-plugin requires at least
18+
# 1980-01-01T00:00:02Z. Bump by 2 seconds to satisfy the validation.
19+
SOURCE_DATE_EPOCH = 315532802;
1620
};
1721
}
1822
);

0 commit comments

Comments
 (0)