Skip to main content

Envelope encryption

Cryptographic operations are API only. Encrypt, decrypt, generate-data-key, sign and verify have no console controls. The console creates keys, inspects them and turns them off; the operations that use a key run from your application, next to the plaintext they act on.
POST /v1/keys/{key_id}/encrypt sends your plaintext to KMS and gets ciphertext back. That is fine for something small and rare — a config value, an API token. It is the wrong shape for anything else, because every byte crosses the network twice and every operation costs a round trip. The alternative is a data key: KMS mints a fresh random key, hands you two copies of it, and never stores it.
You encrypt your data locally with plaintext, then throw plaintext away and store ciphertext beside the data it protects. To read the data back, send ciphertext to POST /v1/keys/{key_id}/decrypt and you have the data key again. Your bulk data never leaves your process, one KMS call covers a whole batch, and the KMS key stays a key-encrypting key — the only thing it ever wraps is other keys.
Never persist the plaintext data key. Storing it next to ciphertext defeats the entire arrangement: anyone who reaches your storage then has both the lock and the key, and revoking the KMS key no longer protects anything.
number_of_bytes accepts 16, 32 or 64 and nothing else — 16 for AES-128, 32 for AES-256 (the default), 64 for HMAC-SHA512. Any other value fails.

When direct encrypt runs out

An RSA key cannot encrypt more than a few hundred bytes. RSA-OAEP can only carry a message smaller than the modulus: with SHA-256 that is k - 2·32 - 2 bytes, so 190 bytes for rsa-2048 and 446 bytes for rsa-4096 (RFC 8017 §7.1.1). There is no chunking behind the API. Past that size the operation fails and a data key is the only route.
A symmetric key has no comparable algorithmic ceiling, but the request body still has to fit in one HTTP call and you still pay a round trip per operation. Treat direct encrypt as a convenience for small, infrequent values, and reach for a data key for everything else.

Encryption context

aad is optional additional authenticated data. It is bound into the AES-GCM tag, so a ciphertext will only open if the same context is presented again — useful for pinning a blob to the thing it belongs to, so a stolen ciphertext cannot be replayed against a different record.
The context must match at decrypt exactly, including its absence. Supplying aad to decrypt a ciphertext that was sealed without one is refused rather than ignored — a context that is only sometimes checked is not a check. Encrypting without aad and decrypting with it fails with 400 INVALID_INPUT.
Only a symmetric key can bind a context. Sending aad to encrypt under an RSA key is refused with 400 KMS_INVALID_KEY_SPEC — RSA-OAEP has nowhere to carry one, so accepting it would drop the binding while you went on treating it as an integrity check. The refusal happens at the seal, where you can still pick a different key.