Skip to main content
S2 supports stream encryption with a key that you supply at request time. Configure a basin with a cipher for new streams, then provide the same key on append and read. S2 stores the stream’s algorithm in metadata, but not the key material itself.

Determined at stream creation

A stream captures its cipher when it is created, through its basin’s configuration.

Key supplied on data plane requests

Encrypted streams require the s2-encryption-key header on REST requests, or pass it using the corresponding CLI flags.

Key custody stays with you

S2 does not persist or log the encryption key.

How it works

New streams inherit the stream_cipher configured on the basin at the moment they are created. Reconfiguring the basin later only affects streams created after that change.

Supported ciphers

CipherAPI / CLI valueKey length
AEGIS-256 (recommended)aegis-25632 bytes
AES-256-GCM (NIST-approved)aes-256-gcm32 bytes
Keys are base64-encoded when provided in the s2-encryption-key header or to the CLI. SDKs allow providing either a string or bytes.
A stream configured with aes-256-gcm can have up to 2^32 records (~4.3 billion), and subsequent appends will be rejected. Otherwise, a stream can have up to 2^64 records (effectively unlimited) – so aegis-256 is generally preferable.

Where keys are required

OperationKey required?Notes
Create or reconfigure basinNoSet stream_cipher here for future streams.
Create streamNoThe stream inherits the basin’s current stream_cipher.
AppendYes, if the stream has a cipherProvide the key on every request or session.
ReadYes, if the stream has a cipherUse the same key that encrypted the records you want to read.
Check tailNoTail only returns positions, not payloads.
List streamsNoEach stream’s cipher is returned in metadata.

Generate a key

openssl rand -base64 32
Store the resulting value as a secret to be used for one or more streams. Maintain a mapping of which key was used for which stream, so that it may be specified consistently for appends and reads.

Configure encryption for new streams

s2 create-basin secure-events \
  --stream-cipher aegis-256

# or change the default for streams created later
s2 reconfigure-basin secure-events \
  --stream-cipher aes-256-gcm
Stream create endpoints do not take a per-stream encryption field. A stream inherits the basin default when it is created, and that cipher is immutable for the lifetime of the stream.

Append and read with the key

export S2_ENCRYPTION_KEY="$(openssl rand -base64 32)"

printf 'top secret\n' | s2 append s2://secure-events/audit-log
s2 read s2://secure-events/audit-log -s 0 -n 1
# Alternatively, read the key from a file
s2 read s2://secure-events/audit-log \
  --encryption-key-file ./stream-key.b64 \
  -s 0 -n 10
The same keying pattern applies to unary operations, append/read sessions, SSE reads, and S2S sessions. The encryption key is just an HTTP header on the wire.

Inspect cipher config

Key rotation and errors

If you need to rotate the key, create a new stream and cut writers and readers over deliberately. Similarly, to change the cipher used, update the basin configuration and then create a new stream.
Do not rotate a stream key in place by simply changing the request header. S2 will accept the append, but later reads with only one key will fail once they reach records encrypted under a different key.
SituationResult
Missing key on an encrypted stream422 invalid
Key is not valid base64 or not 32 bytes422 invalid
Wrong key400 decryption_failed
Encrypted record payloads are authenticated to the stream they were written to.