Local AES-GCM encryption provider

In-process AES-256-GCM provider implementing crypto_domain.EncryptionProvider. The 32-byte symmetric key lives in process memory. Encrypt and decrypt are pure CPU work with no network round-trips.

Overview

This is the simplest crypto provider Piko ships. A 256-bit key, AES-GCM authenticated encryption, no external service, no per-operation cost, no network latency. The provider is pure Go with no CGO and no build tags, so it behaves the same in compiled binaries and in interpreted dev mode.

The provider implements the same crypto_domain.EncryptionProvider port as the AWS KMS and GCP KMS providers. Switching providers is a one-line bootstrap change with no edit to call sites. It supports envelope encryption through GenerateDataKey, which returns a random 32-byte data key wrapped with the master key. That surface matches the KMS providers, so application code stays portable.

Reach for local AES-GCM in three cases. When you do not need HSM-backed audit. When a network dependency on KMS is unacceptable (offline workloads, air-gapped environments, edge nodes, development and test environments). When per-operation latency and cost dominate, the host CPU bounds throughput, not the network. Reach for AWS KMS or GCP KMS when compliance, audit, or HSM custody requirements forbid the master key from living on the application host.

The constructor takes the raw key as []byte. Load it from your secret store (environment variable, mounted secret file, secrets manager) at startup and pass it in. The provider does not read files itself. That is your job, deliberately, so you pick whichever loading mechanism your deployment provides. All public methods are safe for concurrent use.

Requirements

  • A 32-byte (256-bit) key, generated from a cryptographically secure RNG (crypto/rand). Anything else fails construction with ErrInvalidKeySize.
  • A safe place to keep that key, an environment variable, a mounted secret, or a secrets manager. Not a file checked into the repository.

Configuration

import (
    "piko.sh/piko/wdk/crypto/crypto_provider_local_aes_gcm"
)

key, err := loadKeyFromSecretStore() // your code; returns 32 bytes
if err != nil {
    return err
}

provider, err := crypto_provider_local_aes_gcm.NewProvider(crypto_provider_local_aes_gcm.Config{
    Key:   key,                  // required; must be exactly 32 bytes
    KeyID: "production-key-v1",  // optional; default "local-default"
})
if err != nil {
    return err
}

The constructor does not take a context.Context. Nothing to cancel.

A single provider instance holds exactly one AES key. KeyID is a label. The provider returns it on encrypt responses and through GetKeyInfo. It does not select among multiple keys.

Bootstrap

ssr := piko.New(
    piko.WithCryptoProvider("local", provider),
    piko.WithDefaultCryptoProvider("local"),
)

Piko owns the crypto service. It registers your provider, sets it as the default, wires the data-key cache, and registers the provider for graceful shutdown. You write the one option call and the key loader.

WithDefaultCryptoProvider switches the active provider by name. To move from local to KMS, register the KMS provider and change the default name. No call site changes.

Wire it from config with no Go code

Piko also creates this provider straight from config, the lightest-touch wiring it offers. Set security.encryptionKey to a base64-encoded 32-byte key and Piko selects local_aes_gcm with the key ID piko-default-key. Load the key from the PIKO_ENCRYPTION_KEY environment variable in production. Leave security.encryptionKey empty and Piko disables the crypto service.

Encryption at rest for uploads

When a crypto service exists alongside the storage service, Piko registers the crypto stream transformer against the storage pipeline. No extra step from you. The pipeline then encrypts uploads that flow through it. The bootstrap registers the crypto transformer at priority 100, the same as the compression default, so the default wiring does not guarantee that encryption runs after compression. The chain sorts by priority alone with a non-stable sort, so a tie at 100 leaves the relative order undefined. To ensure compress-then-encrypt, register your compression transformer at a priority below 100, or register the crypto transformer yourself at a priority above your compression transformer.

Tradeoffs

Key rotation in Piko runs at the crypto-service level, not on this provider. Call RotateKey(ctx, oldKeyID, newKeyID) on the crypto ServicePort (alias of crypto_domain.CryptoServicePort) to deprecate the old key and activate the new one. List retired keys in security.deprecatedKeyIds so ciphertext written under them stays decryptable. Cloud KMS rotates master keys for you and re-wraps data keys without re-encrypting bulk data. With local AES-GCM, you run an active provider per key and re-encrypt or migrate bulk data on your own schedule. For long-lived data with regulatory rotation requirements, that operational overhead can outweigh the simplicity. At that point AWS KMS or GCP KMS becomes the better choice.

The other tradeoff is audit. KMS gives you CloudTrail or Cloud Audit Logs for free. With local AES-GCM, every encrypt and decrypt is invisible outside your own logging. If "who decrypted what, and when" must be answerable from a tamper-evident audit trail, KMS is mandatory.

See also

Other crypto providers:

  • AWS KMS, HSM-backed envelope encryption on AWS. Mandatory for audit-heavy environments.
  • GCP KMS, HSM-backed envelope encryption on GCP.

Storage transformer (the typical consumer):

Framework docs:

External: