0
# Cryptography and Key Management
1
2
Interfaces for key encryption and cryptographic operations, providing abstractions for both synchronous and asynchronous key encryption scenarios with key resolution capabilities.
3
4
## Capabilities
5
6
### KeyEncryptionKey
7
8
Synchronous interface for key encryption and decryption operations.
9
10
```java { .api }
11
/**
12
* Synchronous key encryption key interface for cryptographic operations.
13
*/
14
interface KeyEncryptionKey {
15
/**
16
* Gets the identifier of the key encryption key.
17
* @return The key identifier
18
*/
19
String getKeyId();
20
21
/**
22
* Encrypts the specified plaintext.
23
* @param algorithm The encryption algorithm to use
24
* @param plaintext The plaintext to encrypt
25
* @return The encrypted data (ciphertext)
26
*/
27
byte[] wrapKey(String algorithm, byte[] plaintext);
28
29
/**
30
* Decrypts the specified ciphertext.
31
* @param algorithm The decryption algorithm to use
32
* @param ciphertext The ciphertext to decrypt
33
* @return The decrypted data (plaintext)
34
*/
35
byte[] unwrapKey(String algorithm, byte[] ciphertext);
36
}
37
```
38
39
### AsyncKeyEncryptionKey
40
41
Asynchronous interface for key encryption and decryption operations using reactive patterns.
42
43
```java { .api }
44
/**
45
* Asynchronous key encryption key interface for cryptographic operations.
46
*/
47
interface AsyncKeyEncryptionKey {
48
/**
49
* Gets the identifier of the key encryption key.
50
* @return A Mono containing the key identifier
51
*/
52
Mono<String> getKeyId();
53
54
/**
55
* Encrypts the specified plaintext asynchronously.
56
* @param algorithm The encryption algorithm to use
57
* @param plaintext The plaintext to encrypt
58
* @return A Mono containing the encrypted data (ciphertext)
59
*/
60
Mono<byte[]> wrapKey(String algorithm, byte[] plaintext);
61
62
/**
63
* Decrypts the specified ciphertext asynchronously.
64
* @param algorithm The decryption algorithm to use
65
* @param ciphertext The ciphertext to decrypt
66
* @return A Mono containing the decrypted data (plaintext)
67
*/
68
Mono<byte[]> unwrapKey(String algorithm, byte[] ciphertext);
69
}
70
```
71
72
### KeyEncryptionKeyResolver
73
74
Synchronous resolver interface for retrieving key encryption keys by identifier.
75
76
```java { .api }
77
/**
78
* Synchronous resolver interface for key encryption keys.
79
*/
80
interface KeyEncryptionKeyResolver {
81
/**
82
* Resolves a key encryption key by its identifier.
83
* @param keyId The identifier of the key to resolve
84
* @return The resolved KeyEncryptionKey, or null if not found
85
*/
86
KeyEncryptionKey resolveKey(String keyId);
87
}
88
```
89
90
### AsyncKeyEncryptionKeyResolver
91
92
Asynchronous resolver interface for retrieving key encryption keys by identifier using reactive patterns.
93
94
```java { .api }
95
/**
96
* Asynchronous resolver interface for key encryption keys.
97
*/
98
interface AsyncKeyEncryptionKeyResolver {
99
/**
100
* Resolves a key encryption key by its identifier asynchronously.
101
* @param keyId The identifier of the key to resolve
102
* @return A Mono containing the resolved AsyncKeyEncryptionKey, or empty if not found
103
*/
104
Mono<AsyncKeyEncryptionKey> resolveKey(String keyId);
105
}
106
```
107
108
## Usage Examples
109
110
### Implementing KeyEncryptionKey
111
112
```java
113
import com.azure.core.cryptography.*;
114
import javax.crypto.Cipher;
115
import javax.crypto.spec.SecretKeySpec;
116
import java.security.Key;
117
118
class AesKeyEncryptionKey implements KeyEncryptionKey {
119
private final String keyId;
120
private final Key key;
121
122
public AesKeyEncryptionKey(String keyId, byte[] keyBytes) {
123
this.keyId = keyId;
124
this.key = new SecretKeySpec(keyBytes, "AES");
125
}
126
127
@Override
128
public String getKeyId() {
129
return keyId;
130
}
131
132
@Override
133
public byte[] wrapKey(String algorithm, byte[] plaintext) {
134
try {
135
Cipher cipher = Cipher.getInstance(algorithm);
136
cipher.init(Cipher.ENCRYPT_MODE, key);
137
return cipher.doFinal(plaintext);
138
} catch (Exception e) {
139
throw new RuntimeException("Encryption failed", e);
140
}
141
}
142
143
@Override
144
public byte[] unwrapKey(String algorithm, byte[] ciphertext) {
145
try {
146
Cipher cipher = Cipher.getInstance(algorithm);
147
cipher.init(Cipher.DECRYPT_MODE, key);
148
return cipher.doFinal(ciphertext);
149
} catch (Exception e) {
150
throw new RuntimeException("Decryption failed", e);
151
}
152
}
153
}
154
```
155
156
### Implementing AsyncKeyEncryptionKey
157
158
```java
159
import com.azure.core.cryptography.*;
160
import reactor.core.publisher.Mono;
161
import reactor.core.scheduler.Schedulers;
162
163
class AsyncAesKeyEncryptionKey implements AsyncKeyEncryptionKey {
164
private final KeyEncryptionKey syncKey;
165
166
public AsyncAesKeyEncryptionKey(KeyEncryptionKey syncKey) {
167
this.syncKey = syncKey;
168
}
169
170
@Override
171
public Mono<String> getKeyId() {
172
return Mono.fromCallable(syncKey::getKeyId)
173
.subscribeOn(Schedulers.boundedElastic());
174
}
175
176
@Override
177
public Mono<byte[]> wrapKey(String algorithm, byte[] plaintext) {
178
return Mono.fromCallable(() -> syncKey.wrapKey(algorithm, plaintext))
179
.subscribeOn(Schedulers.boundedElastic());
180
}
181
182
@Override
183
public Mono<byte[]> unwrapKey(String algorithm, byte[] ciphertext) {
184
return Mono.fromCallable(() -> syncKey.unwrapKey(algorithm, ciphertext))
185
.subscribeOn(Schedulers.boundedElastic());
186
}
187
}
188
```
189
190
### Implementing Key Resolvers
191
192
```java
193
import com.azure.core.cryptography.*;
194
import java.util.concurrent.ConcurrentHashMap;
195
import java.util.Map;
196
197
class InMemoryKeyResolver implements KeyEncryptionKeyResolver {
198
private final Map<String, KeyEncryptionKey> keys = new ConcurrentHashMap<>();
199
200
public void addKey(String keyId, KeyEncryptionKey key) {
201
keys.put(keyId, key);
202
}
203
204
@Override
205
public KeyEncryptionKey resolveKey(String keyId) {
206
return keys.get(keyId);
207
}
208
}
209
210
class AsyncInMemoryKeyResolver implements AsyncKeyEncryptionKeyResolver {
211
private final KeyEncryptionKeyResolver syncResolver;
212
213
public AsyncInMemoryKeyResolver(KeyEncryptionKeyResolver syncResolver) {
214
this.syncResolver = syncResolver;
215
}
216
217
@Override
218
public Mono<AsyncKeyEncryptionKey> resolveKey(String keyId) {
219
return Mono.fromCallable(() -> {
220
KeyEncryptionKey syncKey = syncResolver.resolveKey(keyId);
221
return syncKey != null ? new AsyncAesKeyEncryptionKey(syncKey) : null;
222
}).subscribeOn(Schedulers.boundedElastic());
223
}
224
}
225
```
226
227
### Azure Key Vault Integration Example
228
229
```java
230
import com.azure.core.cryptography.*;
231
import com.azure.security.keyvault.keys.cryptography.*;
232
import com.azure.identity.DefaultAzureCredentialBuilder;
233
234
// Example using Azure Key Vault for key encryption
235
class KeyVaultKeyEncryptionKey implements KeyEncryptionKey {
236
private final CryptographyClient cryptoClient;
237
private final String keyId;
238
239
public KeyVaultKeyEncryptionKey(String keyVaultUrl, String keyName) {
240
CryptographyClientBuilder builder = new CryptographyClientBuilder()
241
.keyIdentifier(keyVaultUrl + "/keys/" + keyName)
242
.credential(new DefaultAzureCredentialBuilder().build());
243
244
this.cryptoClient = builder.buildClient();
245
this.keyId = keyName;
246
}
247
248
@Override
249
public String getKeyId() {
250
return keyId;
251
}
252
253
@Override
254
public byte[] wrapKey(String algorithm, byte[] plaintext) {
255
EncryptionAlgorithm encryptAlg = EncryptionAlgorithm.fromString(algorithm);
256
EncryptResult result = cryptoClient.encrypt(encryptAlg, plaintext);
257
return result.getCipherText();
258
}
259
260
@Override
261
public byte[] unwrapKey(String algorithm, byte[] ciphertext) {
262
EncryptionAlgorithm encryptAlg = EncryptionAlgorithm.fromString(algorithm);
263
DecryptResult result = cryptoClient.decrypt(encryptAlg, ciphertext);
264
return result.getPlainText();
265
}
266
}
267
268
// Async version for Key Vault
269
class AsyncKeyVaultKeyEncryptionKey implements AsyncKeyEncryptionKey {
270
private final CryptographyAsyncClient cryptoClient;
271
private final String keyId;
272
273
public AsyncKeyVaultKeyEncryptionKey(String keyVaultUrl, String keyName) {
274
CryptographyClientBuilder builder = new CryptographyClientBuilder()
275
.keyIdentifier(keyVaultUrl + "/keys/" + keyName)
276
.credential(new DefaultAzureCredentialBuilder().build());
277
278
this.cryptoClient = builder.buildAsyncClient();
279
this.keyId = keyName;
280
}
281
282
@Override
283
public Mono<String> getKeyId() {
284
return Mono.just(keyId);
285
}
286
287
@Override
288
public Mono<byte[]> wrapKey(String algorithm, byte[] plaintext) {
289
EncryptionAlgorithm encryptAlg = EncryptionAlgorithm.fromString(algorithm);
290
return cryptoClient.encrypt(encryptAlg, plaintext)
291
.map(EncryptResult::getCipherText);
292
}
293
294
@Override
295
public Mono<byte[]> unwrapKey(String algorithm, byte[] ciphertext) {
296
EncryptionAlgorithm encryptAlg = EncryptionAlgorithm.fromString(algorithm);
297
return cryptoClient.decrypt(encryptAlg, ciphertext)
298
.map(DecryptResult::getPlainText);
299
}
300
}
301
```
302
303
### Client-Side Encryption Pattern
304
305
```java
306
import com.azure.core.cryptography.*;
307
import com.azure.core.util.BinaryData;
308
309
class EncryptedDataService {
310
private final AsyncKeyEncryptionKey keyEncryptionKey;
311
private final AsyncKeyEncryptionKeyResolver keyResolver;
312
313
public EncryptedDataService(AsyncKeyEncryptionKey kek, AsyncKeyEncryptionKeyResolver resolver) {
314
this.keyEncryptionKey = kek;
315
this.keyResolver = resolver;
316
}
317
318
public Mono<EncryptedBlob> encryptData(BinaryData data) {
319
// Generate a content encryption key (CEK)
320
byte[] contentKey = generateRandomKey(32); // 256-bit key
321
322
return keyEncryptionKey.getKeyId()
323
.flatMap(keyId -> keyEncryptionKey.wrapKey("AES/GCM/NoPadding", contentKey)
324
.map(encryptedKey -> {
325
// Encrypt the actual data with the CEK
326
byte[] encryptedData = encryptWithContentKey(contentKey, data.toBytes());
327
328
return new EncryptedBlob(keyId, encryptedKey, encryptedData);
329
}));
330
}
331
332
public Mono<BinaryData> decryptData(EncryptedBlob encryptedBlob) {
333
return keyResolver.resolveKey(encryptedBlob.getKeyId())
334
.switchIfEmpty(Mono.error(new IllegalArgumentException("Key not found: " + encryptedBlob.getKeyId())))
335
.flatMap(kek -> kek.unwrapKey("AES/GCM/NoPadding", encryptedBlob.getEncryptedKey()))
336
.map(contentKey -> {
337
// Decrypt the data with the recovered content key
338
byte[] decryptedData = decryptWithContentKey(contentKey, encryptedBlob.getEncryptedData());
339
return BinaryData.fromBytes(decryptedData);
340
});
341
}
342
343
private byte[] generateRandomKey(int size) {
344
byte[] key = new byte[size];
345
new java.security.SecureRandom().nextBytes(key);
346
return key;
347
}
348
349
private byte[] encryptWithContentKey(byte[] key, byte[] data) {
350
// Implementation of AES encryption
351
// This is a simplified example - real implementation would handle IV, etc.
352
try {
353
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
354
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
355
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
356
return cipher.doFinal(data);
357
} catch (Exception e) {
358
throw new RuntimeException("Content encryption failed", e);
359
}
360
}
361
362
private byte[] decryptWithContentKey(byte[] key, byte[] encryptedData) {
363
// Implementation of AES decryption
364
try {
365
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
366
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
367
cipher.init(Cipher.DECRYPT_MODE, keySpec);
368
return cipher.doFinal(encryptedData);
369
} catch (Exception e) {
370
throw new RuntimeException("Content decryption failed", e);
371
}
372
}
373
}
374
375
// Helper class for encrypted data
376
class EncryptedBlob {
377
private final String keyId;
378
private final byte[] encryptedKey;
379
private final byte[] encryptedData;
380
381
public EncryptedBlob(String keyId, byte[] encryptedKey, byte[] encryptedData) {
382
this.keyId = keyId;
383
this.encryptedKey = encryptedKey.clone();
384
this.encryptedData = encryptedData.clone();
385
}
386
387
public String getKeyId() { return keyId; }
388
public byte[] getEncryptedKey() { return encryptedKey.clone(); }
389
public byte[] getEncryptedData() { return encryptedData.clone(); }
390
}
391
```
392
393
### Multi-Key Encryption Service
394
395
```java
396
import com.azure.core.cryptography.*;
397
import java.util.List;
398
import java.util.concurrent.ThreadLocalRandom;
399
400
class MultiKeyEncryptionService {
401
private final List<AsyncKeyEncryptionKey> availableKeys;
402
private final AsyncKeyEncryptionKeyResolver keyResolver;
403
404
public MultiKeyEncryptionService(List<AsyncKeyEncryptionKey> keys, AsyncKeyEncryptionKeyResolver resolver) {
405
this.availableKeys = keys;
406
this.keyResolver = resolver;
407
}
408
409
public Mono<EncryptedBlob> encryptWithRandomKey(BinaryData data) {
410
// Select a random key for encryption
411
int keyIndex = ThreadLocalRandom.current().nextInt(availableKeys.size());
412
AsyncKeyEncryptionKey selectedKey = availableKeys.get(keyIndex);
413
414
return encryptWithSpecificKey(selectedKey, data);
415
}
416
417
public Mono<EncryptedBlob> encryptWithSpecificKey(AsyncKeyEncryptionKey kek, BinaryData data) {
418
byte[] contentKey = generateRandomKey(32);
419
420
return kek.getKeyId()
421
.flatMap(keyId -> kek.wrapKey("RSA-OAEP", contentKey)
422
.map(encryptedKey -> {
423
byte[] encryptedData = encryptWithContentKey(contentKey, data.toBytes());
424
return new EncryptedBlob(keyId, encryptedKey, encryptedData);
425
}));
426
}
427
428
public Flux<BinaryData> decryptMultiple(List<EncryptedBlob> encryptedBlobs) {
429
return Flux.fromIterable(encryptedBlobs)
430
.flatMap(blob -> keyResolver.resolveKey(blob.getKeyId())
431
.flatMap(kek -> kek.unwrapKey("RSA-OAEP", blob.getEncryptedKey()))
432
.map(contentKey -> {
433
byte[] decryptedData = decryptWithContentKey(contentKey, blob.getEncryptedData());
434
return BinaryData.fromBytes(decryptedData);
435
})
436
.onErrorResume(error -> {
437
// Log error and continue with next blob
438
System.err.println("Failed to decrypt blob with key " + blob.getKeyId() + ": " + error.getMessage());
439
return Mono.empty();
440
}));
441
}
442
443
private byte[] generateRandomKey(int size) {
444
byte[] key = new byte[size];
445
new java.security.SecureRandom().nextBytes(key);
446
return key;
447
}
448
449
// Content encryption methods (same as previous example)
450
private byte[] encryptWithContentKey(byte[] key, byte[] data) { /* ... */ }
451
private byte[] decryptWithContentKey(byte[] key, byte[] encryptedData) { /* ... */ }
452
}
453
```
454
455
### Key Rotation Example
456
457
```java
458
import com.azure.core.cryptography.*;
459
import java.time.Duration;
460
import java.util.concurrent.atomic.AtomicReference;
461
462
class RotatingKeyEncryptionService {
463
private final AtomicReference<AsyncKeyEncryptionKey> currentKey = new AtomicReference<>();
464
private final AsyncKeyEncryptionKeyResolver keyResolver;
465
private final List<AsyncKeyEncryptionKey> keyRotationOrder;
466
private volatile int currentKeyIndex = 0;
467
468
public RotatingKeyEncryptionService(List<AsyncKeyEncryptionKey> keys, AsyncKeyEncryptionKeyResolver resolver) {
469
this.keyRotationOrder = keys;
470
this.keyResolver = resolver;
471
this.currentKey.set(keys.get(0));
472
473
// Start key rotation timer
474
startKeyRotation();
475
}
476
477
public Mono<EncryptedBlob> encrypt(BinaryData data) {
478
AsyncKeyEncryptionKey key = currentKey.get();
479
return encryptWithKey(key, data);
480
}
481
482
public Mono<BinaryData> decrypt(EncryptedBlob encryptedBlob) {
483
return keyResolver.resolveKey(encryptedBlob.getKeyId())
484
.switchIfEmpty(Mono.error(new IllegalArgumentException("Key not found: " + encryptedBlob.getKeyId())))
485
.flatMap(kek -> decryptWithKey(kek, encryptedBlob));
486
}
487
488
private void startKeyRotation() {
489
// Rotate keys every hour (simplified example)
490
Flux.interval(Duration.ofHours(1))
491
.subscribe(tick -> rotateKey());
492
}
493
494
private void rotateKey() {
495
currentKeyIndex = (currentKeyIndex + 1) % keyRotationOrder.size();
496
currentKey.set(keyRotationOrder.get(currentKeyIndex));
497
System.out.println("Rotated to key index: " + currentKeyIndex);
498
}
499
500
private Mono<EncryptedBlob> encryptWithKey(AsyncKeyEncryptionKey kek, BinaryData data) {
501
// Implementation similar to previous examples
502
return Mono.empty(); // Placeholder
503
}
504
505
private Mono<BinaryData> decryptWithKey(AsyncKeyEncryptionKey kek, EncryptedBlob blob) {
506
// Implementation similar to previous examples
507
return Mono.empty(); // Placeholder
508
}
509
}
510
```