19 KiB
19 KiB
AWS KMS Technical Guide
Key Management Operations
Create KMS Key
import software.amazon.awssdk.services.kms.model.*;
import java.util.stream.Collectors;
public String createKey(KmsClient kmsClient, String description) {
try {
CreateKeyRequest request = CreateKeyRequest.builder()
.description(description)
.keyUsage(KeyUsageType.ENCRYPT_DECRYPT)
.origin(OriginType.AWS_KMS)
.build();
CreateKeyResponse response = kmsClient.createKey(request);
String keyId = response.keyMetadata().keyId();
System.out.println("Created key: " + keyId);
return keyId;
} catch (KmsException e) {
System.err.println("Error creating key: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
Create Key with Custom Key Store
public String createKeyWithCustomStore(KmsClient kmsClient,
String description,
String customKeyStoreId) {
CreateKeyRequest request = CreateKeyRequest.builder()
.description(description)
.keyUsage(KeyUsageType.ENCRYPT_DECRYPT)
.origin(OriginType.AWS_CLOUDHSM)
.customKeyStoreId(customKeyStoreId)
.build();
CreateKeyResponse response = kmsClient.createKey(request);
return response.keyMetadata().keyId();
}
List Keys
import java.util.List;
public List<KeyListEntry> listKeys(KmsClient kmsClient) {
try {
ListKeysRequest request = ListKeysRequest.builder()
.limit(100)
.build();
ListKeysResponse response = kmsClient.listKeys(request);
response.keys().forEach(key -> {
System.out.println("Key ARN: " + key.keyArn());
System.out.println("Key ID: " + key.keyId());
System.out.println();
});
return response.keys();
} catch (KmsException e) {
System.err.println("Error listing keys: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
List Keys with Pagination (Async)
import software.amazon.awssdk.services.kms.paginators.ListKeysPublisher;
import java.util.concurrent.CompletableFuture;
public CompletableFuture<Void> listAllKeysAsync(KmsAsyncClient kmsAsyncClient) {
ListKeysRequest request = ListKeysRequest.builder()
.limit(15)
.build();
ListKeysPublisher keysPublisher = kmsAsyncClient.listKeysPaginator(request);
return keysPublisher
.subscribe(r -> r.keys().forEach(key ->
System.out.println("Key ARN: " + key.keyArn())))
.whenComplete((result, exception) -> {
if (exception != null) {
System.err.println("Error: " + exception.getMessage());
} else {
System.out.println("Successfully listed all keys");
}
});
}
Describe Key
public KeyMetadata describeKey(KmsClient kmsClient, String keyId) {
try {
DescribeKeyRequest request = DescribeKeyRequest.builder()
.keyId(keyId)
.build();
DescribeKeyResponse response = kmsClient.describeKey(request);
KeyMetadata metadata = response.keyMetadata();
System.out.println("Key ID: " + metadata.keyId());
System.out.println("Key ARN: " + metadata.arn());
System.out.println("Key State: " + metadata.keyState());
System.out.println("Creation Date: " + metadata.creationDate());
System.out.println("Enabled: " + metadata.enabled());
return metadata;
} catch (KmsException e) {
System.err.println("Error describing key: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
Enable/Disable Key
public void enableKey(KmsClient kmsClient, String keyId) {
try {
EnableKeyRequest request = EnableKeyRequest.builder()
.keyId(keyId)
.build();
kmsClient.enableKey(request);
System.out.println("Key enabled: " + keyId);
} catch (KmsException e) {
System.err.println("Error enabling key: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
public void disableKey(KmsClient kmsClient, String keyId) {
try {
DisableKeyRequest request = DisableKeyRequest.builder()
.keyId(keyId)
.build();
kmsClient.disableKey(request);
System.out.println("Key disabled: " + keyId);
} catch (KmsException e) {
System.err.println("Error disabling key: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
Encryption and Decryption
Encrypt Data
import software.amazon.awssdk.core.SdkBytes;
import java.nio.charset.StandardCharsets;
public byte[] encryptData(KmsClient kmsClient, String keyId, String plaintext) {
try {
SdkBytes plaintextBytes = SdkBytes.fromString(plaintext, StandardCharsets.UTF_8);
EncryptRequest request = EncryptRequest.builder()
.keyId(keyId)
.plaintext(plaintextBytes)
.build();
EncryptResponse response = kmsClient.encrypt(request);
byte[] encryptedData = response.ciphertextBlob().asByteArray();
System.out.println("Data encrypted successfully");
return encryptedData;
} catch (KmsException e) {
System.err.println("Error encrypting data: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
Decrypt Data
public String decryptData(KmsClient kmsClient, byte[] ciphertext) {
try {
SdkBytes ciphertextBytes = SdkBytes.fromByteArray(ciphertext);
DecryptRequest request = DecryptRequest.builder()
.ciphertextBlob(ciphertextBytes)
.build();
DecryptResponse response = kmsClient.decrypt(request);
String decryptedText = response.plaintext().asString(StandardCharsets.UTF_8);
System.out.println("Data decrypted successfully");
return decryptedText;
} catch (KmsException e) {
System.err.println("Error decrypting data: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
Encrypt with Encryption Context
import java.util.Map;
public byte[] encryptWithContext(KmsClient kmsClient,
String keyId,
String plaintext,
Map<String, String> encryptionContext) {
try {
EncryptRequest request = EncryptRequest.builder()
.keyId(keyId)
.plaintext(SdkBytes.fromString(plaintext, StandardCharsets.UTF_8))
.encryptionContext(encryptionContext)
.build();
EncryptResponse response = kmsClient.encrypt(request);
return response.ciphertextBlob().asByteArray();
} catch (KmsException e) {
System.err.println("Error encrypting with context: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
Data Key Generation (Envelope Encryption)
Generate Data Key
public record DataKeyPair(byte[] plaintext, byte[] encrypted) {}
public DataKeyPair generateDataKey(KmsClient kmsClient, String keyId) {
try {
GenerateDataKeyRequest request = GenerateDataKeyRequest.builder()
.keyId(keyId)
.keySpec(DataKeySpec.AES_256)
.build();
GenerateDataKeyResponse response = kmsClient.generateDataKey(request);
byte[] plaintextKey = response.plaintext().asByteArray();
byte[] encryptedKey = response.ciphertextBlob().asByteArray();
System.out.println("Data key generated");
return new DataKeyPair(plaintextKey, encryptedKey);
} catch (KmsException e) {
System.err.println("Error generating data key: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
Generate Data Key Without Plaintext
public byte[] generateDataKeyWithoutPlaintext(KmsClient kmsClient, String keyId) {
try {
GenerateDataKeyWithoutPlaintextRequest request =
GenerateDataKeyWithoutPlaintextRequest.builder()
.keyId(keyId)
.keySpec(DataKeySpec.AES_256)
.build();
GenerateDataKeyWithoutPlaintextResponse response =
kmsClient.generateDataKeyWithoutPlaintext(request);
return response.ciphertextBlob().asByteArray();
} catch (KmsException e) {
System.err.println("Error generating data key: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
Digital Signing
Create Signing Key
public String createSigningKey(KmsClient kmsClient, String description) {
try {
CreateKeyRequest request = CreateKeyRequest.builder()
.description(description)
.keySpec(KeySpec.RSA_2048)
.keyUsage(KeyUsageType.SIGN_VERIFY)
.origin(OriginType.AWS_KMS)
.build();
CreateKeyResponse response = kmsClient.createKey(request);
return response.keyMetadata().keyId();
} catch (KmsException e) {
System.err.println("Error creating signing key: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
Sign Data
public byte[] signData(KmsClient kmsClient, String keyId, String message) {
try {
SdkBytes messageBytes = SdkBytes.fromString(message, StandardCharsets.UTF_8);
SignRequest request = SignRequest.builder()
.keyId(keyId)
.message(messageBytes)
.signingAlgorithm(SigningAlgorithmSpec.RSASSA_PSS_SHA_256)
.build();
SignResponse response = kmsClient.sign(request);
byte[] signature = response.signature().asByteArray();
System.out.println("Data signed successfully");
return signature;
} catch (KmsException e) {
System.err.println("Error signing data: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
Verify Signature
public boolean verifySignature(KmsClient kmsClient,
String keyId,
String message,
byte[] signature) {
try {
VerifyRequest request = VerifyRequest.builder()
.keyId(keyId)
.message(SdkBytes.fromString(message, StandardCharsets.UTF_8))
.signature(SdkBytes.fromByteArray(signature))
.signingAlgorithm(SigningAlgorithmSpec.RSASSA_PSS_SHA_256)
.build();
VerifyResponse response = kmsClient.verify(request);
boolean isValid = response.signatureValid();
System.out.println("Signature valid: " + isValid);
return isValid;
} catch (KmsException e) {
System.err.println("Error verifying signature: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
Sign and Verify (Async)
public CompletableFuture<Boolean> signAndVerifyAsync(KmsAsyncClient kmsAsyncClient,
String message) {
String signMessage = message;
// Create signing key
CreateKeyRequest createKeyRequest = CreateKeyRequest.builder()
.keySpec(KeySpec.RSA_2048)
.keyUsage(KeyUsageType.SIGN_VERIFY)
.origin(OriginType.AWS_KMS)
.build();
return kmsAsyncClient.createKey(createKeyRequest)
.thenCompose(createKeyResponse -> {
String keyId = createKeyResponse.keyMetadata().keyId();
SdkBytes messageBytes = SdkBytes.fromString(signMessage, StandardCharsets.UTF_8);
SignRequest signRequest = SignRequest.builder()
.keyId(keyId)
.message(messageBytes)
.signingAlgorithm(SigningAlgorithmSpec.RSASSA_PSS_SHA_256)
.build();
return kmsAsyncClient.sign(signRequest)
.thenCompose(signResponse -> {
byte[] signedBytes = signResponse.signature().asByteArray();
VerifyRequest verifyRequest = VerifyRequest.builder()
.keyId(keyId)
.message(messageBytes)
.signature(SdkBytes.fromByteArray(signedBytes))
.signingAlgorithm(SigningAlgorithmSpec.RSASSA_PSS_SHA_256)
.build();
return kmsAsyncClient.verify(verifyRequest)
.thenApply(VerifyResponse::signatureValid);
});
})
.exceptionally(throwable -> {
throw new RuntimeException("Failed to sign or verify", throwable);
});
}
Key Tagging
Tag Key
public void tagKey(KmsClient kmsClient, String keyId, Map<String, String> tags) {
try {
List<Tag> tagList = tags.entrySet().stream()
.map(entry -> Tag.builder()
.tagKey(entry.getKey())
.tagValue(entry.getValue())
.build())
.collect(Collectors.toList());
TagResourceRequest request = TagResourceRequest.builder()
.keyId(keyId)
.tags(tagList)
.build();
kmsClient.tagResource(request);
System.out.println("Key tagged successfully");
} catch (KmsException e) {
System.err.println("Error tagging key: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
List Tags
public Map<String, String> listTags(KmsClient kmsClient, String keyId) {
try {
ListResourceTagsRequest request = ListResourceTagsRequest.builder()
.keyId(keyId)
.build();
ListResourceTagsResponse response = kmsClient.listResourceTags(request);
return response.tags().stream()
.collect(Collectors.toMap(Tag::tagKey, Tag::tagValue));
} catch (KmsException e) {
System.err.println("Error listing tags: " + e.awsErrorDetails().errorMessage());
throw e;
}
}
Advanced Techniques
Envelope Encryption Service
@Service
public class EnvelopeEncryptionService {
private final KmsClient kmsClient;
@Value("${kms.master-key-id}")
private String masterKeyId;
public EnvelopeEncryptionService(KmsClient kmsClient) {
this.kmsClient = kmsClient;
}
public EncryptedEnvelope encryptLargeData(byte[] data) {
// Generate data key
GenerateDataKeyResponse dataKeyResponse = kmsClient.generateDataKey(
GenerateDataKeyRequest.builder()
.keyId(masterKeyId)
.keySpec(DataKeySpec.AES_256)
.build());
byte[] plaintextKey = dataKeyResponse.plaintext().asByteArray();
byte[] encryptedKey = dataKeyResponse.ciphertextBlob().asByteArray();
try {
// Encrypt data with plaintext data key
byte[] encryptedData = encryptWithAES(data, plaintextKey);
// Clear plaintext key from memory
Arrays.fill(plaintextKey, (byte) 0);
return new EncryptedEnvelope(encryptedData, encryptedKey);
} catch (Exception e) {
throw new RuntimeException("Envelope encryption failed", e);
}
}
public byte[] decryptLargeData(EncryptedEnvelope envelope) {
// Decrypt data key
DecryptResponse decryptResponse = kmsClient.decrypt(
DecryptRequest.builder()
.ciphertextBlob(SdkBytes.fromByteArray(envelope.encryptedKey()))
.build());
byte[] plaintextKey = decryptResponse.plaintext().asByteArray();
try {
// Decrypt data with plaintext data key
byte[] decryptedData = decryptWithAES(envelope.encryptedData(), plaintextKey);
// Clear plaintext key from memory
Arrays.fill(plaintextKey, (byte) 0);
return decryptedData;
} catch (Exception e) {
throw new RuntimeException("Envelope decryption failed", e);
}
}
private byte[] encryptWithAES(byte[] data, byte[] key) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(data);
}
private byte[] decryptWithAES(byte[] data, byte[] key) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(data);
}
public record EncryptedEnvelope(byte[] encryptedData, byte[] encryptedKey) {}
}
Error Handling Strategies
public class KmsErrorHandler {
private static final int MAX_RETRIES = 3;
private static final long RETRY_DELAY_MS = 1000;
public <T> T executeWithRetry(Supplier<T> operation, String operationName) {
int attempt = 0;
KmsException lastException = null;
while (attempt < MAX_RETRIES) {
try {
return operation.get();
} catch (KmsException e) {
lastException = e;
attempt++;
// Check if it's a throttling error and retryable
if (e.awsErrorDetails().errorCode().equals("ThrottlingException") && attempt < MAX_RETRIES) {
try {
Thread.sleep(RETRY_DELAY_MS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Retry interrupted", ie);
}
} else {
// Non-retryable error or max retries exceeded
throw e;
}
}
}
throw new RuntimeException(String.format("Failed to execute %s after %d attempts", operationName, MAX_RETRIES), lastException);
}
public boolean isRetryableError(KmsException e) {
String errorCode = e.awsErrorDetails().errorCode();
return "ThrottlingException".equals(errorCode)
|| "TooManyRequestsException".equals(errorCode)
|| "LimitExceededException".equals(errorCode);
}
}
Connection Pooling Configuration
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
public class KmsConnectionPool {
public static KmsClient createPooledClient() {
// Configure connection pool
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(20);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
ApacheHttpClient.Builder httpClientBuilder = ApacheHttpClient.builder()
.httpClient(httpClient);
return KmsClient.builder()
.region(Region.US_EAST_1)
.httpClientBuilder(httpClientBuilder)
.build();
}
}