Crypto cache transformer

Cache value encryption transformer implementing cache.TransformerPort. It hands every payload to a Piko crypto service that encrypts on write and decrypts on read. Cached payloads stay unreadable at rest in Redis, Valkey, or any other shared backend.

Overview

The crypto transformer wraps a crypto_domain.CryptoServicePort and runs it as a Piko cache transformer. The crypto service encrypts every value before it leaves the process, then decrypts every value after it arrives. The transformer holds no key material of its own. It delegates to the central crypto service, so the cache inherits the same provider configuration and key rotation as the rest of the app. The shippable providers are local AES-GCM, AWS KMS, and GCP KMS.

Reach for the crypto transformer when cached values contain user PII, secrets, or anything you do not want stored as plaintext in a shared cache. That covers on-disk snapshots and replica logs. Skip it when the cache is in-process Otter only. The values never leave the process anyway, and the transformer only costs CPU. The transformer pairs with the Zstd transformer. Compress first (priority 100), encrypt second (priority 250 default), so the entropy of the ciphertext does not defeat compression.

The default priority of 250 puts crypto after compression in the write pipeline. On read, transformers run in reverse priority order, so decryption happens before decompression. If name is empty the transformer registers as "crypto-service". Empty payloads pass through unchanged, so a zero-length value is not encrypted.

Requirements

Bootstrap

There is no piko.With* option for cache transformers. Importing the cache_transformer_crypto package registers the crypto-service blueprint through init(), so the builder's Encryption() method resolves it. You do not register anything by hand.

Attach the transformer to a cache through the cache builder when you create a namespace. NewCacheBuilder returns a builder and an error, so handle the error before chaining:

import (
    "piko.sh/piko/wdk/cache"
    _ "piko.sh/piko/wdk/cache/cache_transformer_crypto"
)

builder, err := cache.NewCacheBuilder[string, User](service)
if err != nil {
    return err
}

userCache, err := builder.
    Provider("redis").
    Namespace("users").
    Encryption(). // resolves the registered crypto-service transformer
    Build(ctx)

Encryption() pulls the global crypto service from bootstrap at build time. For tests or a non-default crypto service, use builder.EncryptionWithService(service) to inject the service explicitly.

Configuration

To construct the transformer directly, for example to set an explicit name or priority, call New:

import (
    "piko.sh/piko/wdk/crypto"
    "piko.sh/piko/wdk/cache/cache_transformer_crypto"
)

cryptoService, err := crypto.GetDefaultService()
if err != nil {
    return err
}

// name="" defaults to "crypto-service"; priority=0 defaults to 250.
transformer := cache_transformer_crypto.New(cryptoService, "", 0)

The constructor takes positional arguments instead of a Config struct: (cryptoService, name, priority). It returns a cache.TransformerPort. Pass a non-empty name and a non-zero priority to override the defaults.

Transformer limitations

Attaching any transformer, including encryption, stores encoded bytes instead of decoded values. That disables features that need the decoded value:

  • Searchable and Query. Search() and Query() return ErrSearchNotSupported.
  • Custom Weigher. The builder ignores a Weigher function.
  • Custom ExpiryCalculators. Use WriteExpiration or AccessExpiration for time-to-live instead.
  • Deletion callbacks. The builder ignores OnDeletion and OnAtomicDeletion.
  • RefreshCalculator. The builder ignores RefreshCalculator.

The builder logs a warning at build time when you set one of these alongside a transformer.

See also

Other cache transformers and encoders:

Crypto providers (the underlying service):

Framework docs: