Chapter 14
Cloud KMS and Cryptographic Controls
Scope. This chapter covers Cloud KMS end to end: key rings, keys, key versions, symmetric and asymmetric operations, signing, protection levels (software, HSM, external), customer-managed and customer-supplied encryption keys, rotation, key separation, envelope encryption, crypto-shredding, and destruction. It is the single owner of cryptographic control material for this book; every other chapter that wires a service to a key cross-references it rather than repeating it. Prerequisites. §2.21 Security Projects (key placement), §2.30 Organization Policy, §3.9 IAM Roles, §3.17 Deny Policies. Verified against. GCP console and API surface as of 2026-09, Google Cloud SDK 583.0.0; see sources at end.
Every other chapter in this book that touches encryption at rest ends the same way: a
--kms-key flag, a kms_key_name Terraform argument, and a grant to a service agent. None
of them explain what the key actually protects, what happens when it rotates, or what it
takes to make destroying it mean something. That material lives here, once, so that Chapters
8 through 13 and 15 can cite a single page instead of restating it with small, dangerous
variations.
Cloud KMS is Google Cloud's managed service for creating and controlling cryptographic keys without handling key material directly. It backs three related but distinct controls covered in this chapter: customer-managed encryption keys (CMEK), where Google's infrastructure still performs the cryptographic operation but under a key you administer; customer-supplied encryption keys (CSEK), where you supply key material per request and Google never persists it; and External Key Manager (EKM), where the key never leaves a system you control at all. Each trades operational simplicity for a different degree of control, and §14.15 gives the decision between them.
A Cloud KMS key is not a secret. Secret Manager (§13.1) stores and serves opaque values — passwords, API tokens, TLS private keys — that an application reads and uses directly. A Cloud KMS key never leaves the service: callers send plaintext or ciphertext and get back the other half, and the key material itself is never returned by any API. If your workload needs to read a value in the clear, it belongs in Secret Manager; if it needs to prove control over a resource by encrypting or signing without ever holding the key, it belongs in Cloud KMS.
14.1 Cloud KMS §
Cloud KMS is the managed keystore for symmetric encryption, asymmetric encryption, asymmetric
signing, and MAC keys, reached through gcloud kms, the cloudkms.googleapis.com REST API,
and the google_kms_* Terraform resources. It underlies CMEK for every service listed in
§14.8, and every per-service CMEK example elsewhere in this book eventually calls it.
The defining property is that key material never leaves the service. You send Cloud KMS data to encrypt, or a digest to sign, and it returns the result — you never hold the key. That is the line between Cloud KMS and Secret Manager (§13.1): Secret Manager gives you back the thing you stored; Cloud KMS never does. A credential your application must read is a secret. A key your application must use but must never possess is a KMS key. Storing key material as a Secret Manager secret discards the entire guarantee.
The hierarchy is fixed and every level is a location decision:
project → location → key ring → key → key version (the actual material)
Administration and use are separate role families by design (§14.13, and the principle in Chapter 3):
| Family | Roles | Held by |
|---|---|---|
| Administration | roles/cloudkms.admin | gcp-security-admins@rickcollette.domain only |
| Use | roles/cloudkms.cryptoKeyEncrypterDecrypter, roles/cloudkms.cryptoKeyEncrypter, roles/cloudkms.cryptoKeyDecrypter, roles/cloudkms.signer | Workloads and service agents, on one key |
| Audit | roles/cloudkms.viewer | gcp-security-viewers@rickcollette.domain |
The group that administers a key ring never holds a use role on the keys inside it, and no
workload or service agent ever holds roles/cloudkms.admin. All key rings in this book's SaaS
estate live in rc-saas-shared-sec-01; §2.21 owns why keys are never colocated with the
workloads they protect.
14.2 Key Rings §
A key ring is a named, regional grouping of keys and nothing else — no configuration, no policy beyond IAM, no lifecycle state. It has exactly two properties, and both are permanent for its entire existence: its location, chosen at creation, and its name.
"Key rings can't be deleted."
Neither can keys. And the name of a deleted key cannot be reused inside its ring. A key ring name is therefore a permanent decision in a project you cannot tidy up, which has one practical consequence worth internalizing: a naming experiment in a production security project is forever, and so is the clutter from one.
This book's convention: regional key rings, one per region per purpose, named
kr-<region>-<purpose>, in rc-saas-shared-sec-01.
gcloud kms keyrings create kr-us-central1-data \
--location=us-central1 \
--project=rc-saas-shared-sec-01
Never a global key ring for data-at-rest CMEK. The key belongs in the same region as the storage it protects: a global ring buys no locality and couples every consumer in every region to one dependency, so a control-plane problem in the ring's location becomes an availability problem for data that had nothing to do with it.
Pitfall. Because key rings and keys are undeletable, a test key ring created in the security project during an evaluation stays visible in every inventory and every audit forever, and cannot be cleaned up. Do key-management experiments in a sandbox project you can delete outright, never in the real security project.
14.3 Keys §
A key is a named, purpose-typed container for key versions. gcloud kms keys create requires
--keyring, --location, and --purpose.
Purpose is fixed for the life of the key — a symmetric encryption key can never later hold a signing version — so it is the one creation-time choice you cannot correct:
--purpose | Used for |
|---|---|
encryption | Symmetric encrypt/decrypt, and every CMEK integration (§14.8) |
asymmetric-encryption | RSA key pairs where a third party encrypts to you (§14.6) |
asymmetric-signing | Detached signatures over a digest (§14.7) |
mac | Message authentication codes |
raw-encryption, aes-wrapping, key-encapsulation | Specialized wrapping and KEM uses |
gcloud kms keys create k-storage-uploads \
--keyring=kr-us-central1-data \
--location=us-central1 \
--purpose=encryption \
--rotation-period=90d \
--next-rotation-time=2026-12-01T00:00:00Z \
--project=rc-saas-shared-sec-01
This book names keys k-<service>-<purpose> — k-storage-uploads, k-sql-prod.
Cloud KMS Autokey provisions a key ring and key automatically on first use. It is generally available, and it now comes in two storage shapes, renamed in 2026: Autokey with dedicated-project key storage (formerly "centralized key management") puts the keys in a separate key project, which is the arrangement this book uses because it keeps key administration in the security project (§2.21); and Autokey with same-project key storage (formerly "delegated key management"), which the release notes record as generally available on 2026-07-29 and which can be used on its own or alongside the dedicated-project form.
Pitfall. --rotation-period without --next-rotation-time starts the clock from creation,
which is rarely the schedule you intended and produces rotations at an arbitrary hour. Set
both, and read §14.12 first — rotation does considerably less than its name suggests.
14.4 Key Versions §
The key version, not the key, holds the actual cryptographic material. A key holds many versions; exactly one is the primary version, used by default for new encrypt and sign operations.
| State | Meaning |
|---|---|
ENABLED | Usable; the primary version is one of these |
DISABLED | Blocked immediately, and reversibly |
| Scheduled for destruction | In the waiting period, still restorable (§14.18) |
DESTROYED | Material gone, irreversibly |
Every ciphertext is bound to the version that produced it, and that version must stay enabled to decrypt it. This single fact drives two later sections: rotation (§14.12) never removes an old version while ciphertext under it still exists, and crypto-shredding (§14.17) targets a specific version, never the key as a whole.
gcloud kms keys versions list \
--key=k-storage-uploads --keyring=kr-us-central1-data \
--location=us-central1 --project=rc-saas-shared-sec-01
Pitfall. Disabling a version to "test whether anything still uses it" breaks decryption of every ciphertext produced under it, instantly and across every consumer. It is reversible, but the outage is real. Determine what a version still protects from audit logs (§14.19) before touching its state, not by experiment.
14.5 Symmetric Encryption §
gcloud kms encrypt and gcloud kms decrypt call the AEAD symmetric primitive directly, with
an optional --additional-authenticated-data-file that binds context: the AAD is verified but
not encrypted, so a ciphertext encrypted for tenant A fails to decrypt if presented as
tenant B's.
The plaintext limit is hard, and it is the reason envelope encryption exists:
"The plaintext file must not be larger than 64KiB."
Cloud KMS is a key-management API, not a bulk-data encryption service. Anything larger goes through envelope encryption (§14.16) — encrypt the data locally with a data encryption key, then send only that key to Cloud KMS to be wrapped.
gcloud kms encrypt \
--key=k-storage-uploads --keyring=kr-us-central1-data \
--location=us-central1 --project=rc-saas-shared-sec-01 \
--plaintext-file=config.yaml --ciphertext-file=config.yaml.enc
Pitfall. A caller holding roles/cloudkms.cryptoKeyEncrypterDecrypter can decrypt every
ciphertext ever produced under that key, including ones it never created. CMEK does not
protect data from a compromised principal that can call decrypt — it protects against loss of
administrative control over the key. Say that plainly in threat models rather than letting
"encrypted with CMEK" stand in for an access control.
14.6 Asymmetric Encryption §
An asymmetric-encryption key holds an RSA key pair, and only the private half ever exists
inside Cloud KMS. Callers fetch the public key and encrypt locally; only decryption calls
the service and requires roles/cloudkms.cryptoKeyDecrypter. Supported algorithms are the
rsa-decrypt-oaep-* family across 2048-, 3072-, and 4096-bit moduli, each paired with a SHA
digest.
gcloud kms keys versions get-public-key 1 \
--key=k-partner-inbound --keyring=kr-us-central1-data \
--location=us-central1 --project=rc-saas-shared-sec-01 \
--output-file=partner-inbound-pub.pem
Use it when the encrypting party must not be trusted with decryption. A partner system that sends you data needs only your public key — and therefore needs no Google Cloud identity and no IAM grant at all, which is the entire point. That is a materially smaller integration surface than granting an external party any role.
For everything else, prefer symmetric encryption (§14.5) or envelope encryption (§14.16): asymmetric operations are markedly slower and are capped at small payloads by the RSA modulus.
Pitfall. RSA-OAEP payload capacity is a few hundred bytes, not a few kilobytes, and shrinks as the digest grows. A design that "encrypts the record with the partner's public key" works in testing on a short record and fails in production on a long one. Encrypt a data encryption key with RSA and the record with that key (§14.16).
14.7 Signing §
An asymmetric-signing key produces a detached signature over a digest computed locally,
never over the file itself — which is why signing a multi-gigabyte artifact costs one small
API call.
gcloud kms asymmetric-sign \
--key=k-artifact-signing --keyring=kr-us-central1-data \
--location=us-central1 --project=rc-saas-shared-sec-01 \
--version=1 --digest-algorithm=sha256 \
--input-file=release.tar.gz.sha256 --signature-file=release.tar.gz.sig
Verification never calls Cloud KMS. Fetch the public key once and verify locally, so verifiers — including every consumer outside your organization — need no KMS grant. This is what makes KMS-backed signing workable as a supply-chain control (Chapter 37): the signing capability is tightly held, and the verifying capability is free and universal.
Grant the narrowest of the three roles. A build pipeline that signs needs
roles/cloudkms.signer alone, not roles/cloudkms.signerVerifier and not
roles/cloudkms.verifier.
Prefer elliptic-curve algorithms (ec-sign-p256-sha256, ec-sign-p384-sha384,
ec-sign-ed25519) for new signing keys: smaller signatures at lower cost for the same
security margin, unless a consumer requires RSA compatibility.
Pitfall. The signature covers the digest you supplied, and Cloud KMS cannot tell whether that digest is of the artifact you think it is. A pipeline that computes the digest from a mutable location, or signs before a final build step rewrites the artifact, produces a valid signature over the wrong bytes — and it verifies cleanly forever.
14.8 Customer-Managed Encryption Keys §
CMEK is the pattern behind every --kms-key flag and kms_key_name argument in this book.
The service performs the encryption — it still calls Cloud KMS's symmetric encrypt operation
on your behalf, and your data still passes through Google's infrastructure in the clear
during processing — but the key that operation uses is one you created, administer, and can
revoke or destroy independently of the service and the data it protects. That independence,
not secrecy of the key from Google, is what CMEK buys: without it, Google Default Encryption
already encrypts every byte at rest, but you have no lever to cut a specific resource off from
its own ciphertext.
Every CMEK-consuming service performs encrypt and decrypt calls through a Google-managed
service agent, not through the workload's own service account, and that agent needs
roles/cloudkms.cryptoKeyEncrypterDecrypter granted on the specific key, never on the key
ring or project. Grant it once per service, and never grant the agent roles/cloudkms.admin
— administration of the key stays with gcp-security-admins@rickcollette.domain, use stays
with the service agent, per §3.9's separation of administration from use.
| Service | Service agent address form |
|---|---|
| Compute Engine (disks) | service-PROJECT_NUMBER@compute-system.iam.gserviceaccount.com |
| GKE (Kubernetes Engine) | service-PROJECT_NUMBER@compute-system.iam.gserviceaccount.com |
| Cloud SQL | service-PROJECT_NUMBER@gcp-sa-cloud-sql.iam.gserviceaccount.com |
| BigQuery | bq-PROJECT_NUMBER@bigquery-encryption.iam.gserviceaccount.com |
| Pub/Sub | service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com |
| Secret Manager | service-PROJECT_NUMBER@gcp-sa-secretmanager.iam.gserviceaccount.com |
| Cloud Storage | retrieved dynamically per bucket; see §11.19 |
PROJECT_NUMBER is the numeric project number of the project that owns the resource, not
the security project that owns the key — a Cloud SQL instance in rc-saas-prod-data-01
grants its own project's service agent on a key living in rc-saas-shared-sec-01. The grant
is a single cross-project IAM binding on the key resource:
gcloud kms keys add-iam-policy-binding k-sql-prod \
--keyring=kr-us-central1-data --location=us-central1 \
--project=rc-saas-shared-sec-01 \
--member="serviceAccount:service-135792468@gcp-sa-cloud-sql.iam.gserviceaccount.com" \
--role="roles/cloudkms.cryptoKeyEncrypterDecrypter"
Pitfall. CMEK does not protect against a principal that already holds
roles/cloudkms.cryptoKeyEncrypterDecrypter and legitimate access to the encrypted resource —
that principal can read the plaintext exactly as it could under Google Default Encryption.
CMEK's value is administrative: the ability to sever access by disabling or destroying the
key, and the audit trail of every cryptographic operation, neither of which Google Default
Encryption offers. constraints/gcp.restrictNonCmekServices (§2.30) enforces that specified
services may not create resources without a customer-managed key at all, and
constraints/gcp.restrictCmekCryptoKeyProjects restricts which projects may host the keys
those services are allowed to use — set it to allow only rc-saas-shared-sec-01.
14.9 Customer-Supplied Encryption Keys §
CSEK inverts CMEK's trust model: you supply raw key material with every API request, and Google never stores it.
"Google does not store your keys on its servers and cannot access your protected data unless you provide the key."
For Cloud Storage the guarantee is stated more narrowly still — Google keeps only a hash, to validate future requests:
"Cloud Storage stores only a cryptographic hash of the key so that future requests can be validated against the hash."
Only two services accept a customer-supplied key: Compute Engine, for persistent disk encryption, and Cloud Storage, for object encryption. Nothing else in this book's scope does.
Losing the key permanently loses the data. There is no recovery path, no support escalation, and no version history to fall back on, because Google never had a copy. That is not a caveat to the model — it is the model, and it is the reason CSEK belongs only to estates that already run a tested external key lifecycle. For everyone else, CMEK's administered-but-recoverable model (§14.8) is the safer default with nearly all of the control.
Pitfall. CSEK's key must be supplied on every subsequent operation, including ones a team does not think of as operations on the data — creating a snapshot of a CSEK-encrypted disk, creating an image from it, or attaching it to a new instance. An automated backup job that does not carry the key silently stops producing usable backups.
14.10 Cloud HSM §
--protection-level=hsm performs every cryptographic operation for that key inside a
hardware security module rather than in software. The certification is exact, and must not
be paraphrased:
"FIPS 140-2 Level 3"
gcloud kms keys create k-payments-pan \
--keyring=kr-us-central1-data --location=us-central1 \
--purpose=encryption --protection-level=hsm \
--project=rc-saas-shared-sec-01
Choose HSM when a framework or contract names it, not as a general upgrade. Software protection already meets most estates' bar; HSM costs more per key version and per operation, and buys nothing an auditor will credit unless the requirement specifically calls for hardware-backed key storage. PCI DSS and several government frameworks do name it.
constraints/cloudkms.allowedProtectionLevels (§2.30) enforces hsm across a regulated
folder, which is the right way to apply it — as an inherited property of where a project
sits, not a per-key decision someone has to remember.
Pitfall. Protection level is fixed at key creation and cannot be raised later. Discovering during an audit that a key holding regulated data is software-protected means creating a new HSM key and re-encrypting everything under it — rotation (§14.12) will not do this, because rotation never re-encrypts.
14.11 External Key Manager §
EKM moves the key entirely outside Google's infrastructure. Cloud KMS holds only a reference
and calls an external manager — Fortanix, Futurex, and Thales are the supported partners — for
every operation, over the internet (--protection-level=external) or a private VPC path
(--protection-level=external-vpc). Chapter 20 covers the network posture for the VPC
variant.
The trade-off is availability, not confidentiality:
"Communicating with an external service over the internet can lead to problems with reliability, availability, and latency."
An unreachable external key manager makes the data unavailable, not slow. Every operation
returns FAILED_PRECONDITION until connectivity returns, and the downstream consequences can
be permanent: Spanner automatically deletes databases whose encryption key stays unavailable
for more than 30 consecutive days.
Adopt EKM only where a regulation or contract requires that the key never reside in Google's infrastructure at all. For every other estate, CMEK (§14.8) delivers equivalent administrative control — you can revoke and destroy — without making an external system a single point of failure for data availability. This is a case where the stronger-sounding control is the wrong default.
Pitfall. The external key manager becomes a tier-0 dependency of every service encrypting under it, and it is usually run by a team that does not know that. Its maintenance windows, its certificate expiries, and its own availability target now bound your data plane. Confirm the operating agreement before the architecture decision, not after.
14.12 Key Rotation §
gcloud kms keys set-rotation-schedule or keys create --rotation-period schedules automatic
rotation, and --next-rotation-time sets the first occurrence. Rotation on schedule performs
exactly one action:
"The schedule automatically creates a new primary version for the key according to the
--next-rotation-timeand--rotation-periodflags."
Critically, that is all it does:
"Rotating keys creates new active key versions, but doesn't re-encrypt your data and doesn't disable or delete previous key versions."
New encrypt calls use the new primary version; every ciphertext already produced under the previous version still needs that version enabled to decrypt. This is why rotation is not a remediation for a compromised key — a rotated key still has an enabled version that a holder of the old key material (or a principal who retained decrypt access to it) can use against already-encrypted data. Destruction, in §14.17 and §14.18, is the actual remediation; rotation only limits how much future ciphertext a given version protects.
gcloud kms keys set-rotation-schedule k-storage-uploads \
--keyring=kr-us-central1-data --location=us-central1 \
--project=rc-saas-shared-sec-01 \
--rotation-period=90d --next-rotation-time=2026-12-01T00:00:00Z
Every enabled version of a rotated key is a billed resource for as long as it stays enabled; only a destroyed version stops billing (§14.18). A key rotated quarterly for years and never pruned accumulates dozens of billed, unused versions — disable versions no longer needed to decrypt live data once you have confirmed nothing still depends on them.
14.13 Key Separation §
§2.21 settled where keys live: a dedicated security project, administered separately, with destruction delayed and alerted. What it does not settle is how many keys to create and along which axes — and that choice, not the placement, determines the blast radius when one key is compromised or destroyed.
| Axis | Separate so that… | Bounds |
|---|---|---|
| Environment | A production key cannot decrypt staging ciphertext, and retiring an environment cannot touch a live one | A leaked credential |
| Data class | Regulated payment data and application logs never share a key | A compliance scope |
| Tenant | One tenant's data can be erased without touching another's | An erasure request |
| Region | A regional key never becomes a dependency for a workload elsewhere | A jurisdictional or availability incident |
Tenant separation is the one that is impossible to retrofit. Crypto-shredding (§14.17) erases a tenant only if that tenant's data is under a key no other tenant shares — a shared key makes destruction all-or-nothing across everyone it protects. A multi-tenant estate that starts with one key and later accepts a contractual erasure obligation must re-encrypt every tenant's data under a new per-tenant key to meet it, and rotation will not do that (§14.12). Decide the tenant axis before the first production tenant, not at the first deletion request.
Each axis answers a different question when something goes wrong. A single key used across all four answers none of them.
Pitfall. The opposite failure is real too. A key per tenant per environment per data class per region multiplies into thousands of undeletable keys (§14.2), each with its own rotation schedule, IAM bindings, and billing line — and an inventory nobody can audit is its own control failure. Separate along the axes you have an actual obligation to bound, and no further.
14.14 Key Access Justifications §
Key Access Justifications (KAJ) attaches a justification code to every cryptographic
operation Google's systems perform against an enrolled key, so that access can be
programmatically approved or denied based on why it is being requested rather than only who
is requesting it. It works alongside Access Approval and Access Transparency (§3.29–§3.30)
to make Google's own access to CMEK-protected data visible and, where policy allows,
conditional. --allowed-access-reasons on gcloud kms keys create or keys update enrolls
a key, accepting values such as customer-initiated-access, google-initiated-review,
google-initiated-system-operation, and third-party-data-request.
KAJ can only be used inside an Assured Workloads folder, and its production readiness is per-service, not global: Google states plainly that a service's Preview-status integration must not be used in production, and that reliable justification generation requires every service involved in a request to have reached GA status for KAJ specifically.
No list of KAJ-supported services appears in this book. The set is per-service, changes as integrations reach GA, and is gated behind Assured Workloads, so any list printed here would be a snapshot with no expiry date on it. Read the current one from the Key Access Justifications documentation for the regime you are in.
Treat any service not confirmed GA for KAJ as a Preview dependency: do not build a production control around it, and re-verify the service's KAJ status before relying on it.
gcloud kms keys create k-regulated-data \
--keyring=kr-us-central1-data --location=us-central1 \
--purpose=encryption --project=rc-saas-shared-sec-01 \
--allowed-access-reasons=customer-initiated-access,google-initiated-review
14.15 Encryption Architecture §
Three layers of at-rest encryption control are available, in increasing order of administrative effort and decreasing order of Google's operational involvement.
Google Default Encryption requires no configuration: every object, disk, and database in Google Cloud is encrypted at rest with Google-managed keys the moment it is written. It protects against physical media theft and against another customer's workload ever reaching your bytes. It gives you no lever over the key at all — no rotation you control, no destruction you can trigger, no audit trail of key use distinct from the resource's own access logs.
CMEK (§14.8) adds a key you administer in front of that same infrastructure-performed encryption. It protects against loss of administrative control over data: you can revoke a service agent's grant or destroy a key version to make a resource's ciphertext permanently unreadable, independent of whoever still holds access to the resource itself. It does not protect against a principal that already holds both resource access and decrypt access on the key — that principal's read path is unchanged.
CSEK and EKM (§14.9, §14.11) go further: the key material is never held by Google at all, either because you supply it per-request and it is discarded after use (CSEK) or because it lives permanently in an external system Cloud KMS only calls out to (EKM). Both trade Google's operational resilience for a stronger control guarantee, and both fail in the same direction — losing the key, or losing reachability to it, makes the data permanently or temporarily unavailable rather than merely less protected.
| Layer | You control | Fails as | Typical driver |
|---|---|---|---|
| Google Default | nothing | never (Google-managed) | default; no action required |
| CMEK | key lifecycle, IAM, destruction | unavailable if key destroyed/revoked | independent revocation, audit |
| CSEK | key material per request | unrecoverable if key lost | Compute Engine, Storage only |
| EKM | key location and reachability | unavailable if unreachable | key must never leave your control |
Default to CMEK for anything that needs an administrative kill switch — which in this book's estate means everything landing in a security-boundary project. Reserve CSEK and EKM for a specific, named requirement that CMEK cannot satisfy, because both convert a key-management problem into an availability dependency the workload owner must now operate.
14.16 Envelope Encryption §
Envelope encryption is how every application in this book encrypts data larger than the 64 KiB direct-encrypt limit (§14.5), and how tokenization protects its wrapped key (§15.5).
The pattern, in four steps:
- Generate a data encryption key (DEK) locally.
- Encrypt the payload with the DEK using a fast local AEAD cipher — the payload never touches Cloud KMS.
- Send only the small DEK to Cloud KMS to be wrapped by the key encryption key (KEK).
- Store the wrapped DEK alongside the ciphertext.

Decryption reverses it: send the wrapped DEK back to be unwrapped, then decrypt locally.
"The KEK never leaves Cloud KMS."
That is the whole benefit. Payload size is unbounded because the payload never transits the service, and the DEK is in the clear only in application memory, for the duration of a single operation.
Use Tink rather than composing this by hand.
"Tink is an open source cryptography library written by cryptographers and security engineers at Google."
Hand-rolled envelope encryption fails in ways that pass testing and fail in review — nonce reuse, missing authentication, the wrong key-wrapping mode. Tink's KMS-envelope AEAD primitives make those states structurally hard to reach, which is a better guarantee than care.
gcloud kms keys create k-app-envelope \
--keyring=kr-us-central1-data --location=us-central1 \
--purpose=encryption --project=rc-saas-shared-sec-01
Anti-pattern. The google_kms_secret and google_kms_secret_ciphertext Terraform data
sources decrypt or embed ciphertext at plan time, putting recoverable material into state —
which §13.13 establishes is itself a secret store. Wrap and unwrap in application code or
Tink, never in a Terraform data source.
Pitfall. Storing the wrapped DEK somewhere other than beside its ciphertext — a separate table, a different bucket, a config file — creates two objects with independent lifecycles and backup schedules. Restore one without the other and the data is unrecoverable even though nothing was destroyed and the key is perfectly healthy.
14.17 Crypto-Shredding §
Crypto-shredding destroys a key version to make every ciphertext under it permanently unrecoverable — an erasure mechanism for when deleting the underlying copies directly is impractical, and the usual answer to a right-to-erasure request in a multi-tenant estate.
It is valid only when all three of these hold, and each is easy to violate silently:
- Every copy is under that version — primary storage, backups, snapshots, read replicas, exports, and search or cache indexes — and under no other version or key.
- No plaintext exists outside those copies. Destroying a key does nothing to a value already written to a log line, an application cache, or an analytics extract.
- The key was never shared across tenants, environments, or data classes (§14.13).
The third condition is the one that has to be designed in advance, and §14.13 explains why: a key protecting more than what you intend to erase turns destruction into an all-or-nothing action across everything under it. That is a data-loss incident, not a scoped erasure. Per-tenant key separation is what makes crypto-shredding a designed capability rather than a lucky accident.
Verify all three before treating a destruction as satisfying an erasure obligation. A version destruction that misses one backup still under the same key has erased nothing, while producing a confident audit record saying it did — which is worse than not having tried, because it stops anyone looking further.
Pitfall. Backups are where this fails in practice. A backup taken before a per-tenant key migration is encrypted under the old shared key, so destroying the new per-tenant key leaves the tenant's data fully recoverable from that backup. Erasure scope must include every backup generation still within retention, or retention must expire first.
14.18 Key Destruction §
Keys and key rings can never be deleted:
"Key rings can't be deleted. You can delete keys and key versions in some circumstances, but names of deleted keys can't be reused."
Only key versions can be destroyed, and destruction is deliberately slow. Scheduling destruction moves the version into a waiting period during which it can still be restored:
"The default scheduled for destruction duration is 30 days."
That duration is set per key with --destroy-scheduled-duration at creation or update, and
floored organization-wide by
constraints/cloudkms.minimumDestroyScheduledDuration (§2.30) so that no key's owner can set
an effectively-immediate destruction window. constraints/cloudkms.disableBeforeDestroy can
additionally require a version to sit disabled before it becomes eligible for destruction.
gcloud kms keys versions destroy 3 \
--key=k-storage-uploads --keyring=kr-us-central1-data \
--location=us-central1 --project=rc-saas-shared-sec-01
Once the waiting period elapses, destruction is irreversible and the key material is
unrecoverable — including by Google. §2.21 already establishes that
DestroyCryptoKeyVersion must be alerted on; this section adds the cost dimension:
"Destroyed key versions are not billed resources."
An enabled version, rotated or not, is billed for as long as it stays enabled — the same fact raised in §14.12 from the rotation-hygiene angle. Destroying versions with no remaining dependent ciphertext is therefore both a security practice and a cost-control one.
In Terraform, protect the key ring itself from accidental removal from state — recall that
Cloud KMS will refuse the delete regardless, but prevent_destroy stops Terraform from even
attempting an operation that would orphan the resource from management:
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 8.0"
}
}
}
resource "google_kms_key_ring" "data" {
name = "kr-us-central1-data"
location = "us-central1"
project = "rc-saas-shared-sec-01"
lifecycle {
prevent_destroy = true
}
}
resource "google_kms_crypto_key" "storage_uploads" {
name = "k-storage-uploads"
key_ring = google_kms_key_ring.data.id
purpose = "ENCRYPT_DECRYPT"
rotation_period = "7776000s"
version_template {
algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION"
protection_level = "SOFTWARE"
}
lifecycle {
prevent_destroy = true
}
}
prevent_destroy on the key mirrors the API's own refusal and documents intent for anyone
reading the configuration, since the key ring's is already enforced server-side regardless.
14.19 Compliance Considerations §
Several regulatory and contractual frameworks name specific Cloud KMS controls directly
rather than accepting Google Default Encryption as sufficient. PCI DSS and a number of
government and financial frameworks require hardware-backed key storage, which
--protection-level=hsm (§14.10) satisfies with its FIPS 140-2 Level 3 certification —
cite the certification level exactly in any compliance evidence; do not round it to a general
"FIPS-compliant" claim. Data-residency and sovereignty obligations, owned by §15.8–§15.9, are
satisfied at the key layer by choosing a key ring's location to match the required
jurisdiction and never allowing a global ring for that data (§14.2).
Auditors most often ask three questions this chapter answers directly: which principals can
administer versus use each key (§14.8, verified against the actual IAM policy on the key, not
a role name in documentation), whether destroyed-version audit log entries
(DestroyCryptoKeyVersion) are retained and alerted (§2.21), and whether rotation evidence
exists without conflating "rotated" with "old data re-encrypted" (§14.12) — a rotation
schedule alone does not demonstrate that data encrypted years ago is protected by a
currently-reviewed key version. Build evidence exports from gcloud kms keys versions list
and Cloud Audit Logs rather than from the rotation schedule configuration alone.
Chapter Summary §
- Cloud KMS manages keys that never leave the service; Secret Manager (§13.1) stores values applications read directly — the two are not interchangeable.
- Key rings and keys can never be deleted, and a key ring's location and name are permanent decisions; only key versions can be scheduled for destruction.
- The direct symmetric encrypt/decrypt plaintext limit is 64 KiB, which is why envelope encryption (§14.16) exists for anything larger.
- CMEK gives administrative control over already-Google-encrypted data — the ability to revoke or destroy a key independent of who holds resource access — not protection against a principal already holding both resource and decrypt access.
- §14.8's service-agent table is load-bearing for every sibling chapter's CMEK example; grant
the agent only
roles/cloudkms.cryptoKeyEncrypterDecrypteron the specific key, never admin. - Rotation creates a new primary version and never re-encrypts existing ciphertext; old versions must stay enabled for as long as data under them exists, so rotation alone is not a remediation for key compromise.
- CSEK is supported only for Compute Engine and Cloud Storage; Google stores no key material, only a validation hash, so a lost CSEK key permanently loses the data it protected.
- Cloud HSM is certified FIPS 140-2 Level 3; EKM makes external reachability an availability dependency, with Spanner's 30-day automatic deletion as a concrete failure consequence.
- Key separation by environment, data class, tenant, and region bounds a different failure each; per-tenant separation specifically is the precondition for crypto-shredding.
- Crypto-shredding erases data only if every copy — backups, snapshots, replicas, exports, cached plaintext — is under the destroyed version and no other; verify all before relying on it as an erasure mechanism.
- Key Access Justifications requires an Assured Workloads folder and has per-service GA status; a Preview-status service integration must not be treated as production-ready.
- Cloud KMS Autokey's GA status could not be confirmed from documentation; treat it as unconfirmed rather than production-ready.
- Destroyed key versions stop being billed; enabled versions of a rotated key continue to be billed indefinitely, which makes pruning unused versions both a security and cost practice.
Security Checklist §
| Control | Why it matters | How to verify |
|---|---|---|
Key rings and keys live only in rc-saas-shared-sec-01, never a workload project | Enforces independent administrative control (§2.21, §14.8) | gcloud kms keyrings list --location=us-central1 --project=rc-saas-shared-sec-01 |
| No global key ring backs data-at-rest CMEK | A global dependency for a regional workload with no locality benefit | gcloud kms keyrings list --location=global --project=rc-saas-shared-sec-01 |
Service agents hold only roles/cloudkms.cryptoKeyEncrypterDecrypter, never roles/cloudkms.admin | Separates use from administration | gcloud kms keys get-iam-policy KEY --keyring=RING --location=REGION |
gcp-security-admins@rickcollette.domain administers keys; no workload principal does | Administration stays separate from use (§3.9) | inspect the same IAM policy for admin-role members |
constraints/gcp.restrictNonCmekServices set for regulated data services | Blocks Google-managed-key-only resource creation | gcloud org-policies describe constraints/gcp.restrictNonCmekServices --project=PROJECT_ID |
constraints/gcp.restrictCmekCryptoKeyProjects allows only the security project | Prevents CMEK keys sourced from an unapproved project | same command against that constraint |
constraints/cloudkms.minimumDestroyScheduledDuration floors the destroy window | Stops an effectively-immediate key destruction | same command against that constraint |
DestroyCryptoKeyVersion and SetIamPolicy on keys are alerted | Detects both destruction and access-scope changes | see the logging query in §2.21 |
| Old key versions disabled or destroyed once no ciphertext depends on them | Unused enabled versions are both an attack surface and a cost | gcloud kms keys versions list --key=KEY --keyring=RING --location=REGION |
| Per-tenant keys exist wherever crypto-shredding is a designed erasure path | A shared key makes destruction all-or-nothing | review key-to-tenant mapping against §14.13's separation axes |
| KAJ-enrolled keys used only for services confirmed GA for KAJ | A Preview integration cannot reliably generate justifications | check the current supported-services list before relying on a key's enrollment |
| Autokey, if used at all, treated as non-production pending GA confirmation | Undocumented release stage means no support commitment | re-check release notes before any production adoption |
Sources §
- Cloud KMS key management overview — https://cloud.google.com/kms/docs/key-management (last validated 2026-09-03)
- Destroying and restoring key versions — https://cloud.google.com/kms/docs/destroy-restore (last validated 2026-09-03)
- Key rotation — https://cloud.google.com/kms/docs/key-rotation (last validated 2026-09-03)
- Customer-managed encryption keys (CMEK) — https://cloud.google.com/kms/docs/cmek (last validated 2026-09-03)
- Customer-supplied encryption keys, Compute Engine — https://cloud.google.com/compute/docs/disks/customer-supplied-encryption (last validated 2026-09-03)
- Customer-supplied encryption keys, Cloud Storage — https://cloud.google.com/storage/docs/encryption/customer-supplied-keys (last validated 2026-09-03)
- Cloud HSM — https://cloud.google.com/kms/docs/hsm (last validated 2026-09-03)
- Cloud EKM overview — https://cloud.google.com/kms/docs/ekm (last validated 2026-09-03)
- Key Access Justifications overview — https://docs.cloud.google.com/assured-workloads/key-access-justifications/docs/overview (last validated 2026-09-03)
- Key Access Justifications supported services — https://docs.cloud.google.com/assured-workloads/key-access-justifications/docs/supported-services (last validated 2026-09-03)
- Cloud KMS Autokey overview — https://cloud.google.com/kms/docs/autokey-overview (last validated 2026-09-03)
- Envelope encryption — https://cloud.google.com/kms/docs/envelope-encryption (last validated 2026-09-03)
- Client-side encryption with Tink — https://cloud.google.com/kms/docs/client-side-encryption (last validated 2026-09-03)
- CMEK for Compute Engine disks — https://cloud.google.com/compute/docs/disks/customer-managed-encryption (last validated 2026-09-03)
- CMEK for BigQuery — https://cloud.google.com/bigquery/docs/customer-managed-encryption (last validated 2026-09-03)
- CMEK for Cloud SQL — https://cloud.google.com/sql/docs/mysql/configure-cmek (last validated 2026-09-03)
- CMEK for GKE — https://cloud.google.com/kubernetes-engine/docs/how-to/using-cmek (last validated 2026-09-03)
- CMEK for Pub/Sub — https://cloud.google.com/pubsub/docs/cmek (last validated 2026-09-03)
- CMEK for Secret Manager — https://cloud.google.com/secret-manager/docs/configuring-secret-manager/configure-cmek (last validated 2026-09-03)
- Cloud SDK command surface resolved offline against Google Cloud SDK 583.0.0 (core 2026.08.31), 2026-09-03