Crypto storage transformer
Stream transformer that wraps the storage pipeline with chunked AES-256-GCM encryption, delegating key management to a registered Piko crypto service.
Overview
The crypto transformer plugs into the storage pipeline as a StreamTransformerPort. On writes it encrypts the byte stream before it reaches the provider. On reads it decrypts the stream coming back. Encryption uses chunked AES-256-GCM with 64 KB chunks, so memory stays constant regardless of file size. A multi-gigabyte file encrypts in steady-state memory. The transformer owns no keys. It delegates every cryptographic call to the configured crypto service, so key rotation, envelope encryption with cloud KMS, and audit logging all happen at the crypto-service layer.
Internally the transformer bridges the crypto service's WriteCloser stream to the port's io.Reader through an io.Pipe and a goroutine. Encryption runs as the caller drains the reader, so the pipeline never buffers a whole object.
Reach for the crypto transformer when you need objects encrypted at rest. The encryption survives a storage compromise, a leaked S3 credential, a stolen disk image, or a mis-shared bucket. The encrypted blobs are useless without the crypto service that holds (or wraps) the key. Pair with AWS KMS or GCP KMS for HSM-backed envelope encryption, or with Local AES-GCM for development.
It is a clean hexagonal fit. The wdk facade aliases the internal adapter and implements the same StreamTransformerPort as the compression transformers, so it composes in the same priority-ordered chain with no special-casing.
Requirements
A registered crypto service (crypto_domain.CryptoServicePort) provides the keys. In a Piko application, this is the service the framework builds when you wire any of the crypto providers. Outside Piko, construct one with crypto.NewService(ctx, cacheService, config, opts...), then register your provider with RegisterProvider and SetDefaultProvider.
Configuration
import (
"piko.sh/piko/wdk/storage"
"piko.sh/piko/wdk/storage/storage_transformer_crypto"
)
transformer := storage_transformer_crypto.New(
cryptoService, // required: a crypto_domain.CryptoServicePort
"crypto-service", // name: optional, empty becomes "crypto-service"
250, // priority: optional, zero becomes 250
)
The constructor needs only cryptoService. An empty name becomes "crypto-service". A zero priority becomes 250. The chain sorts transformers by ascending priority and applies them in that order on writes, so a higher priority runs later. Encryption at 250 therefore runs after compression at 100. You compress first, then encrypt the smaller output. Reads apply the chain in reverse.
New returns a storage.StreamTransformerPort directly. It does not return an error.
Bootstrap
If you wire both a crypto service and a storage service, the framework registers the transformer for you. The bootstrap calls RegisterTransformer automatically when a crypto service is present, so you write no wiring code.
The bootstrap registers crypto at priority 100, the same priority as the compression default. Equal priorities give no deterministic compress-then-encrypt order. To guarantee that compression runs first, register the crypto transformer yourself at a higher priority than your compression transformers. RegisterTransformer is the only wiring path. There is no functional-option form.
if err := service.RegisterTransformer(ctx, transformer); err != nil {
return err
}
Tradeoffs
Encrypting before storage means presigned URLs become useless to direct browser clients. The bytes a client uploads or downloads via a presigned URL bypass the transformer pipeline. You would store or retrieve raw plaintext on the encrypted side, breaking the round-trip. Either run uploads and downloads through the application (so the pipeline sees them), or accept that presigned-URL paths skip encryption and apply encryption only to server-side operations.
See also
Other storage transformers:
- Gzip transformer, gzip compression; combine with crypto for "compress then encrypt".
- Zstd transformer, Zstandard compression; combine with crypto for "compress then encrypt".
Crypto providers (the keying material):
- AWS KMS, HSM-backed envelope encryption on AWS.
- GCP KMS, HSM-backed envelope encryption on GCP.
- Local AES-GCM, in-process AES-256-GCM with a local key file.
Storage providers:
- Amazon S3, GCS, Cloudflare R2, Disk, any provider works; the transformer is provider-agnostic.
Framework docs:
- How to handle file storage, storage pipeline overview including transformers.
- How to encrypt and decrypt data, the crypto service end-to-end.
- Storage API reference,
StreamTransformerPort,RegisterTransformer, transformer priorities. - Crypto API reference,
CryptoServicePortand provider registration.