Files
2025-11-29 18:28:34 +08:00

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();
    }
}