# Payload encryption

> For the complete documentation index, see [llms.txt](https://docs.temporal.io/llms.txt).
> Any documentation page is available as raw Markdown by appending `.md` to its URL.
> For related questions, query https://docs.temporal.io/api/retrieve?q=<question>&jtb=<job-to-be-done>&src=md-page&agent=<your-name> (no auth; returns JSON `{content, source_url, page_url}`, with `source_url` pointing to Markdown for docs pages). Don't include personal data or credentials.
> If this page is wrong or missing something you needed, or Temporal behaves differently from what it says, report it with https://docs.temporal.io/api/feedback?title=<summary>&message=<what-happened>&category=documentation&page_url=/develop/java/best-practices/data-handling/data-encryption&src=md-page&agent=<your-name> (use `category=product` when the product, not the page, is wrong). Report only a specific problem you hit, send it once, and don't include personal data or credentials.

> Encrypt data sent to and from the Temporal Service using a custom Payload Codec in the Java SDK.

Temporal's security model is designed around client-side encryption of Payloads.
A client may encrypt Payloads before sending them to the server, and decrypt them after receiving them from the server.
This provides a high degree of confidentiality because the Temporal Server has no knowledge of the actual data.
It also gives implementers more power and more freedom regarding which client is able to read which data -- they can control access with keys, algorithms, or other security measures.

A Temporal developer adds client-side encryption of Payloads by providing a Custom Payload Codec to its Client.
Depending on business needs, a complete implementation of Payload Encryption may involve selecting appropriate encryption algorithms, managing encryption keys, restricting a subset of their users from viewing payload output, or a combination of these.

The server itself never adds encryption over Payloads.
Therefore, unless client-side encryption is implemented, Payload data will be persisted in non-encrypted form to the data store, and any Client that can make requests to a Temporal namespace (including the Temporal UI and CLI) will be able to read Payloads contained in Workflows.
When working with sensitive data, you should always implement Payload encryption.

## Custom Payload Codec in Java 

Create a custom implementation of [`PayloadCodec`](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/payload/codec/PayloadCodec.html) and use it in [`CodecDataConverter`](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/common/converter/CodecDataConverter.html) to set a custom Data Converter.

The Payload Codec does byte-to-byte conversion and must be set with a Data Converter.

Define custom encryption or compression logic in your `encode` method and decryption or decompression logic in your `decode` method.

The following codec, adapted from the [Java encryption sample](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/encryptedpayloads), encrypts each Payload with AES-GCM.
It records the key ID on the encrypted Payload, so after you rotate to a new active key, Workers can still decrypt Payloads encrypted with an older key.
In `decode`, it returns Payloads that it didn't encrypt unchanged.

<!--SNIPSTART java-data-encryption-payload-codec-->
[java-sandbox/best-practices/data-encryption/src/main/java/io/temporal/docs/bestpractices/dataencryption/EncryptionCodec.java](https://github.com/temporalio/documentation-sdk-code-examples/blob/main/java-sandbox/best-practices/data-encryption/src/main/java/io/temporal/docs/bestpractices/dataencryption/EncryptionCodec.java)
```java
public class EncryptionCodec implements PayloadCodec {

    static final ByteString METADATA_ENCODING = ByteString.copyFromUtf8("binary/encrypted");

    private static final String CIPHER = "AES/GCM/NoPadding";

    // Metadata keys and values added to each encrypted Payload. The decode method checks them.
    static final String METADATA_ENCRYPTION_CIPHER_KEY = "encryption-cipher";
    static final ByteString METADATA_ENCRYPTION_CIPHER = ByteString.copyFromUtf8(CIPHER);
    static final String METADATA_ENCRYPTION_KEY_ID_KEY = "encryption-key-id";

    private static final int GCM_NONCE_LENGTH_BYTE = 12;
    private static final int GCM_TAG_LENGTH_BIT = 128;

    private static final SecureRandom RANDOM = new SecureRandom();

    // Load the keys from a KMS or other secure storage. The codec records the key ID on each
    // Payload, so after you rotate to a new active key, it can still decrypt older Payloads.
    private final Map<String, SecretKey> keys;
    private final String activeKeyId;

    public EncryptionCodec(Map<String, SecretKey> keys, String activeKeyId) {
        if (!keys.containsKey(activeKeyId)) {
            throw new IllegalArgumentException("No key with ID " + activeKeyId);
        }
        this.keys = Map.copyOf(keys);
        this.activeKeyId = activeKeyId;
    }

    @Override
    public List<Payload> encode(List<Payload> payloads) {
        return payloads.stream().map(this::encodePayload).collect(Collectors.toList());
    }

    @Override
    public List<Payload> decode(List<Payload> payloads) {
        return payloads.stream().map(this::decodePayload).collect(Collectors.toList());
    }

    private Payload encodePayload(Payload payload) {
        byte[] encryptedData;
        try {
            // Encrypt the whole Payload, including its metadata.
            encryptedData = encrypt(payload.toByteArray(), keys.get(activeKeyId));
        } catch (Exception e) {
            throw new PayloadCodecException(e);
        }
        return Payload.newBuilder()
            .putMetadata(EncodingKeys.METADATA_ENCODING_KEY, METADATA_ENCODING)
            .putMetadata(METADATA_ENCRYPTION_CIPHER_KEY, METADATA_ENCRYPTION_CIPHER)
            .putMetadata(METADATA_ENCRYPTION_KEY_ID_KEY, ByteString.copyFromUtf8(activeKeyId))
            .setData(ByteString.copyFrom(encryptedData))
            .build();
    }

    private Payload decodePayload(Payload payload) {
        // Leave Payloads this codec didn't encrypt unchanged.
        if (!METADATA_ENCODING.equals(
                payload.getMetadataOrDefault(EncodingKeys.METADATA_ENCODING_KEY, null))) {
            return payload;
        }
        try {
            String keyId =
                payload.getMetadataOrThrow(METADATA_ENCRYPTION_KEY_ID_KEY)
                    .toString(StandardCharsets.UTF_8);
            SecretKey key = keys.get(keyId);
            if (key == null) {
                throw new IllegalStateException("No key with ID " + keyId);
            }
            byte[] plainData = decrypt(payload.getData().toByteArray(), key);
            return Payload.parseFrom(plainData);
        } catch (Exception e) {
            throw new PayloadCodecException(e);
        }
    }

    private static byte[] encrypt(byte[] plainData, SecretKey key) throws Exception {
        // Use a new random nonce for every Payload and store it in front of the ciphertext.
        byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTE];
        RANDOM.nextBytes(nonce);

        Cipher cipher = Cipher.getInstance(CIPHER);
        cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH_BIT, nonce));
        byte[] encryptedData = cipher.doFinal(plainData);

        return ByteBuffer.allocate(nonce.length + encryptedData.length)
            .put(nonce)
            .put(encryptedData)
            .array();
    }

    private static byte[] decrypt(byte[] encryptedDataWithNonce, SecretKey key) throws Exception {
        ByteBuffer buffer = ByteBuffer.wrap(encryptedDataWithNonce);
        byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTE];
        buffer.get(nonce);
        byte[] encryptedData = new byte[buffer.remaining()];
        buffer.get(encryptedData);

        Cipher cipher = Cipher.getInstance(CIPHER);
        cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH_BIT, nonce));
        return cipher.doFinal(encryptedData);
    }
}
```
<!--SNIPEND-->

This codec encrypts every Payload with the same active key.
To choose a key by Namespace, Workflow, or Activity, override `PayloadCodec.withContext(SerializationContext)` and return a codec that picks the key from the context.

**Set Data Converter to use custom Payload Codec**

Wrap the default Data Converter and your `PayloadCodec` in a `CodecDataConverter`, and set it in the `WorkflowClientOptions`.
Use the same options in your Worker process and in every Client that starts Workflow Executions or reads their results.

<!--SNIPSTART java-data-encryption-set-codec {"enable_code_dedenting": true}-->
[java-sandbox/best-practices/data-encryption/src/main/java/io/temporal/docs/bestpractices/dataencryption/Clients.java](https://github.com/temporalio/documentation-sdk-code-examples/blob/main/java-sandbox/best-practices/data-encryption/src/main/java/io/temporal/docs/bestpractices/dataencryption/Clients.java)
```java
WorkflowClientOptions options =
    WorkflowClientOptions.newBuilder()
        .setDataConverter(
            new CodecDataConverter(
                DefaultDataConverter.newDefaultInstance(),
                List.of(new EncryptionCodec(keys, activeKeyId)),
                // Also encrypt Failure messages and stack traces.
                true))
        .build();
```
<!--SNIPEND-->

By default, `CodecDataConverter` runs your codec only on Payloads, and Failure messages and stack traces stay in plain text in Event History.
Pass `true` as the third argument, as in the example, to encode them too.
The Temporal Service then stores a placeholder message, and your Client decodes the original message when it reads the Failure.

- Data **encoding** is performed by the Client using the converters and codecs provided by Temporal or your custom implementation when passing input to the Temporal Service. For example, plain text input is usually serialized into a JSON object, and can then be compressed or encrypted.
- Data **decoding** may be performed by your application logic during your Workflows or Activities as necessary, but decoded Workflow results are never persisted back to the Temporal Service. Instead, they are stored encoded on the Temporal Service, and you need to provide a [Codec Server](#using-a-codec-server) with the `--codec-endpoint` option of the [temporal workflow show](/cli/command-reference/workflow#show) command, or in the Web UI, to view them.

For reference, see the [Encryption](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/encryptedpayloads) sample.

### Using a Codec Server

A Codec Server is an HTTP server that uses your custom Codec logic to decode your data remotely.
The Codec Server is independent of the Temporal Service and decodes your encrypted payloads through predefined endpoints.
You create, operate, and manage access to your Codec Server in your own environment.
The Temporal CLI and the Web UI in turn provide built-in hooks to call the Codec Server to decode encrypted payloads on demand.
Refer to the [Codec Server](/production-deployment/data-encryption) documentation for information on how to design and deploy a Codec Server.

For reference, see the [Codec server](https://github.com/temporalio/sdk-java/tree/main/temporal-remote-data-encoder) sample.
