Skip to content

Commit ad3dae4

Browse files
committed
Add Cryptography API support with encrypt/decrypt operations
- Add EncryptRequestAlpha1 and DecryptRequestAlpha1 domain classes - Implement encrypt() and decrypt() methods in DaprPreviewClient - Add CryptoExample and StreamingCryptoExample with documentation - Add integration tests for crypto operations - Add localstorage crypto component configuration
1 parent f79ab8c commit ad3dae4

File tree

16 files changed

+2934
-14
lines changed

16 files changed

+2934
-14
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
- name: Run tests
3333
run: ./mvnw clean install -B -q -DskipITs=true
3434
- name: Codecov
35-
uses: codecov/codecov-action@v5.5.1
35+
uses: codecov/codecov-action@v5.5.2
3636
- name: Upload test report for sdk
3737
uses: actions/upload-artifact@v5
3838
with:

.github/workflows/validate.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ jobs:
113113
run: sleep 30 && docker logs dapr_scheduler && nc -vz localhost 50006
114114
- name: Install jars
115115
run: ./mvnw clean install -DskipTests -q
116+
- name: Validate crypto example
117+
working-directory: ./examples
118+
run: |
119+
mm.py ./src/main/java/io/dapr/examples/crypto/README.md
116120
- name: Validate workflows example
117121
working-directory: ./examples
118122
run: |
@@ -186,3 +190,5 @@ jobs:
186190
run: |
187191
mm.py ./src/main/java/io/dapr/examples/pubsub/stream/README.md
188192
193+
194+

examples/components/crypto/keys/.gitkeep

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: dapr.io/v1alpha1
2+
kind: Component
3+
metadata:
4+
name: localstoragecrypto
5+
spec:
6+
type: crypto.dapr.localstorage
7+
version: v1
8+
metadata:
9+
# Path to the directory containing keys (PEM files)
10+
# This path is relative to the resources-path directory
11+
- name: path
12+
value: "C:/Users/svegiraju/IdeaProjects/java-sdk/examples/components/crypto/keys/"
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright 2021 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.dapr.examples.crypto;
15+
16+
import io.dapr.client.DaprClientBuilder;
17+
import io.dapr.client.DaprPreviewClient;
18+
import io.dapr.client.domain.DecryptRequestAlpha1;
19+
import io.dapr.client.domain.EncryptRequestAlpha1;
20+
import io.dapr.config.Properties;
21+
import io.dapr.config.Property;
22+
import reactor.core.publisher.Flux;
23+
24+
import java.io.IOException;
25+
import java.nio.charset.StandardCharsets;
26+
import java.nio.file.Files;
27+
import java.nio.file.Path;
28+
import java.nio.file.Paths;
29+
import java.security.KeyPair;
30+
import java.security.KeyPairGenerator;
31+
import java.security.NoSuchAlgorithmException;
32+
import java.util.Base64;
33+
import java.util.Map;
34+
35+
/**
36+
* CryptoExample demonstrates using the Dapr Cryptography building block
37+
* to encrypt and decrypt data using a cryptography component.
38+
*
39+
* <p>This example shows:
40+
* <ul>
41+
* <li>Encrypting plaintext data with a specified key and algorithm</li>
42+
* <li>Decrypting ciphertext data back to plaintext</li>
43+
* <li>Automatic key generation if keys don't exist</li>
44+
* </ul>
45+
*
46+
* <p>Prerequisites:
47+
* <ul>
48+
* <li>Dapr installed and initialized</li>
49+
* <li>A cryptography component configured (e.g., local storage crypto)</li>
50+
* </ul>
51+
*/
52+
public class CryptoExample {
53+
54+
private static final String CRYPTO_COMPONENT_NAME = "localstoragecrypto";
55+
private static final String KEY_NAME = "rsa-private-key";
56+
private static final String KEY_WRAP_ALGORITHM = "RSA";
57+
private static final String KEYS_DIR = "components/crypto/keys";
58+
59+
/**
60+
* The main method demonstrating encryption and decryption with Dapr.
61+
*
62+
* @param args Command line arguments (unused).
63+
*/
64+
public static void main(String[] args) throws Exception {
65+
// Generate keys if they don't exist
66+
generateKeysIfNeeded();
67+
68+
Map<Property<?>, String> overrides = Map.of(
69+
Properties.HTTP_PORT, "3500",
70+
Properties.GRPC_PORT, "50001"
71+
);
72+
73+
try (DaprPreviewClient client = new DaprClientBuilder().withPropertyOverrides(overrides).buildPreviewClient()) {
74+
75+
String originalMessage = "This is a secret message";
76+
byte[] plainText = originalMessage.getBytes(StandardCharsets.UTF_8);
77+
78+
System.out.println("=== Dapr Cryptography Example ===");
79+
System.out.println("Original message: " + originalMessage);
80+
System.out.println();
81+
82+
// Encrypt the message
83+
System.out.println("Encrypting message...");
84+
EncryptRequestAlpha1 encryptRequest = new EncryptRequestAlpha1(
85+
CRYPTO_COMPONENT_NAME,
86+
Flux.just(plainText),
87+
KEY_NAME,
88+
KEY_WRAP_ALGORITHM
89+
);
90+
91+
byte[] encryptedData = client.encrypt(encryptRequest)
92+
.collectList()
93+
.map(CryptoExample::combineChunks)
94+
.block();
95+
96+
System.out.println("Encryption successful!");
97+
System.out.println("Encrypted data length: " + encryptedData.length + " bytes");
98+
System.out.println();
99+
100+
// Decrypt the message
101+
System.out.println("Decrypting message...");
102+
DecryptRequestAlpha1 decryptRequest = new DecryptRequestAlpha1(
103+
CRYPTO_COMPONENT_NAME,
104+
Flux.just(encryptedData)
105+
);
106+
107+
byte[] decryptedData = client.decrypt(decryptRequest)
108+
.collectList()
109+
.map(CryptoExample::combineChunks)
110+
.block();
111+
112+
String decryptedMessage = new String(decryptedData, StandardCharsets.UTF_8);
113+
System.out.println("Decryption successful!");
114+
System.out.println("Decrypted message: " + decryptedMessage);
115+
System.out.println();
116+
117+
if (originalMessage.equals(decryptedMessage)) {
118+
System.out.println("SUCCESS: The decrypted message matches the original.");
119+
} else {
120+
System.out.println("ERROR: The decrypted message does not match the original.");
121+
}
122+
123+
} catch (Exception e) {
124+
System.err.println("Error during crypto operations: " + e.getMessage());
125+
throw new RuntimeException(e);
126+
}
127+
}
128+
129+
/**
130+
* Generates RSA key pair if the key file doesn't exist.
131+
*/
132+
private static void generateKeysIfNeeded() throws NoSuchAlgorithmException, IOException {
133+
Path keysDir = Paths.get(KEYS_DIR);
134+
Path keyFile = keysDir.resolve(KEY_NAME + ".pem");
135+
136+
if (Files.exists(keyFile)) {
137+
System.out.println("Using existing key: " + keyFile.toAbsolutePath());
138+
return;
139+
}
140+
141+
System.out.println("Generating RSA key pair...");
142+
Files.createDirectories(keysDir);
143+
144+
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
145+
keyGen.initialize(4096);
146+
KeyPair keyPair = keyGen.generateKeyPair();
147+
148+
String privateKeyPem = "-----BEGIN PRIVATE KEY-----\n"
149+
+ Base64.getMimeEncoder(64, "\n".getBytes()).encodeToString(keyPair.getPrivate().getEncoded())
150+
+ "\n-----END PRIVATE KEY-----\n";
151+
152+
String publicKeyPem = "-----BEGIN PUBLIC KEY-----\n"
153+
+ Base64.getMimeEncoder(64, "\n".getBytes()).encodeToString(keyPair.getPublic().getEncoded())
154+
+ "\n-----END PUBLIC KEY-----\n";
155+
156+
Files.writeString(keyFile, privateKeyPem + publicKeyPem);
157+
System.out.println("Key generated: " + keyFile.toAbsolutePath());
158+
}
159+
160+
/**
161+
* Combines byte array chunks into a single byte array.
162+
*/
163+
private static byte[] combineChunks(java.util.List<byte[]> chunks) {
164+
int totalSize = chunks.stream().mapToInt(chunk -> chunk.length).sum();
165+
byte[] result = new byte[totalSize];
166+
int pos = 0;
167+
for (byte[] chunk : chunks) {
168+
System.arraycopy(chunk, 0, result, pos, chunk.length);
169+
pos += chunk.length;
170+
}
171+
return result;
172+
}
173+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
## Dapr Cryptography API Examples
2+
3+
This example provides the different capabilities provided by Dapr Java SDK for Cryptography. For further information about Cryptography APIs please refer to [this link](https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/)
4+
5+
### Using the Cryptography API
6+
7+
The Java SDK exposes several methods for this -
8+
* `client.encrypt(...)` for encrypting data using a cryptography component.
9+
* `client.decrypt(...)` for decrypting data using a cryptography component.
10+
11+
## Pre-requisites
12+
13+
* [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/).
14+
* Java JDK 11 (or greater):
15+
* [Microsoft JDK 11](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-11)
16+
* [Oracle JDK 11](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11)
17+
* [OpenJDK 11](https://jdk.java.net/11/)
18+
* [Apache Maven](https://maven.apache.org/install.html) version 3.x.
19+
20+
### Checking out the code
21+
22+
Clone this repository:
23+
24+
```sh
25+
git clone https://github.com/dapr/java-sdk.git
26+
cd java-sdk
27+
```
28+
29+
Then build the Maven project:
30+
31+
```sh
32+
# make sure you are in the `java-sdk` directory.
33+
mvn install
34+
```
35+
36+
Then get into the examples directory:
37+
38+
```sh
39+
cd examples
40+
```
41+
42+
### Initialize Dapr
43+
44+
Run `dapr init` to initialize Dapr in Self-Hosted Mode if it's not already initialized.
45+
46+
### Running the Example
47+
48+
This example uses the Java SDK Dapr client to **Encrypt and Decrypt** data. The example automatically generates RSA keys if they don't exist.
49+
50+
#### Example 1: Basic Crypto Example
51+
52+
`CryptoExample.java` demonstrates basic encryption and decryption of a simple message.
53+
54+
```java
55+
public class CryptoExample {
56+
private static final String CRYPTO_COMPONENT_NAME = "localstoragecrypto";
57+
private static final String KEY_NAME = "rsa-private-key";
58+
private static final String KEY_WRAP_ALGORITHM = "RSA";
59+
60+
public static void main(String[] args) {
61+
try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) {
62+
63+
String originalMessage = "This is a secret message";
64+
byte[] plainText = originalMessage.getBytes(StandardCharsets.UTF_8);
65+
66+
// Encrypt the message
67+
EncryptRequestAlpha1 encryptRequest = new EncryptRequestAlpha1(
68+
CRYPTO_COMPONENT_NAME,
69+
Flux.just(plainText),
70+
KEY_NAME,
71+
KEY_WRAP_ALGORITHM
72+
);
73+
74+
byte[] encryptedData = client.encrypt(encryptRequest)
75+
.collectList()
76+
.map(chunks -> /* combine chunks */)
77+
.block();
78+
79+
// Decrypt the message
80+
DecryptRequestAlpha1 decryptRequest = new DecryptRequestAlpha1(
81+
CRYPTO_COMPONENT_NAME,
82+
Flux.just(encryptedData)
83+
);
84+
85+
byte[] decryptedData = client.decrypt(decryptRequest)
86+
.collectList()
87+
.map(chunks -> /* combine chunks */)
88+
.block();
89+
}
90+
}
91+
}
92+
```
93+
94+
Use the following command to run this example:
95+
96+
<!-- STEP
97+
name: Generate Crypto Keys
98+
expected_stdout_lines:
99+
- "Keys generated successfully"
100+
background: false
101+
output_match_mode: substring
102+
-->
103+
104+
```bash
105+
mkdir -p ./components/crypto/keys && openssl genrsa -out ./components/crypto/keys/rsa-private-key.pem 4096 && openssl rsa -in ./components/crypto/keys/rsa-private-key.pem -pubout -out ./components/crypto/keys/rsa-private-key.pub.pem && echo "Keys generated successfully"
106+
```
107+
108+
<!-- END_STEP -->
109+
110+
<!-- STEP
111+
name: Run Crypto Example
112+
expected_stdout_lines:
113+
- "== APP == SUCCESS: The decrypted message matches the original."
114+
background: true
115+
output_match_mode: substring
116+
sleep: 10
117+
-->
118+
119+
```bash
120+
dapr run --resources-path ./components/crypto --app-id crypto-app --dapr-http-port 3500 --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.crypto.CryptoExample
121+
```
122+
123+
<!-- END_STEP -->
124+
125+
#### Example 2: Streaming Crypto Example
126+
127+
`StreamingCryptoExample.java` demonstrates advanced scenarios including:
128+
- Multi-chunk data encryption
129+
- Large data encryption (100KB+)
130+
- Custom encryption ciphers
131+
132+
```bash
133+
dapr run --resources-path ./components/crypto --app-id crypto-app --dapr-http-port 3500 --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.crypto.StreamingCryptoExample
134+
```
135+
136+
### Sample Output
137+
138+
```
139+
=== Dapr Cryptography Example ===
140+
Original message: This is a secret message
141+
142+
Encrypting message...
143+
Encryption successful!
144+
Encrypted data length: 512 bytes
145+
146+
Decrypting message...
147+
Decryption successful!
148+
Decrypted message: This is a secret message
149+
150+
SUCCESS: The decrypted message matches the original.
151+
```
152+
153+
### Supported Key Wrap Algorithms
154+
155+
The following key wrap algorithms are supported:
156+
- `A256KW` (alias: `AES`) - AES key wrap
157+
- `A128CBC`, `A192CBC`, `A256CBC` - AES CBC modes
158+
- `RSA-OAEP-256` (alias: `RSA`) - RSA OAEP with SHA-256
159+
160+
### Supported Data Encryption Ciphers
161+
162+
Optional data encryption ciphers:
163+
- `aes-gcm` (default) - AES in GCM mode
164+
- `chacha20-poly1305` - ChaCha20-Poly1305 cipher
165+
166+
### Cleanup
167+
168+
To stop the app, run (or press CTRL+C):
169+
170+
<!-- STEP
171+
name: Cleanup
172+
-->
173+
174+
```bash
175+
dapr stop --app-id crypto-app
176+
```
177+
178+
<!-- END_STEP -->

0 commit comments

Comments
 (0)