Cloud KMS¶
Cloudbox emulates the Cloud KMS REST API (v1). The google-cloud-kms Python SDK works
against it without modification. Cryptographic operations use real AES-256-GCM encryption
via the cryptography library.
Connection¶
Port: 8092 (override with CLOUDBOX_KMS_PORT)
from google.api_core.client_options import ClientOptions
from google.auth.credentials import AnonymousCredentials
from google.cloud import kms
client = kms.KeyManagementServiceClient(
credentials=AnonymousCredentials(),
client_options=ClientOptions(api_endpoint="http://localhost:8092"),
)
Resource hierarchy¶
projects/{project}/locations/{location}
└── keyRings/{key_ring_id}
└── cryptoKeys/{crypto_key_id}
└── cryptoKeyVersions/{version_id}
Key rings¶
Create key ring¶
Body may be {}. Returns the key ring resource. 409 if it already exists.
Get key ring¶
List key rings¶
Returns { "keyRings": [...] }.
Crypto keys¶
Create crypto key¶
{
"purpose": "ENCRYPT_DECRYPT",
"versionTemplate": {
"algorithm": "GOOGLE_SYMMETRIC_ENCRYPTION",
"protectionLevel": "SOFTWARE"
}
}
Supported purposes: ENCRYPT_DECRYPT, ASYMMETRIC_SIGN, and ASYMMETRIC_DECRYPT.
For asymmetric keys, specify the algorithm in versionTemplate.algorithm
(e.g. EC_SIGN_P256_SHA256, RSA_SIGN_PSS_2048_SHA256, RSA_DECRYPT_OAEP_2048_SHA256).
Creating a key automatically creates version 1 as the primary version. Returns the key
resource including the primary version pointer.
Get crypto key¶
Returns the key resource with its current primary version.
List crypto keys¶
Returns { "cryptoKeys": [...] }.
Patch crypto key¶
Updates labels. Returns the updated key resource.
Encrypt / decrypt¶
Encrypt¶
Both plaintext and additionalAuthenticatedData must be base64-encoded.
additionalAuthenticatedData (AAD) is optional — when supplied it is bound to the
ciphertext and must be provided identically at decrypt time.
Returns:
{
"name": "projects/.../cryptoKeyVersions/1",
"ciphertext": "<base64-encoded-ciphertext>",
"ciphertextCrc32c": "<crc32c>"
}
Encryption always uses the current primary version. The version name is embedded in the ciphertext, so the correct version key is automatically selected at decrypt time even after key rotation.
Decrypt¶
Returns:
Failure cases:
| Condition | Status |
|---|---|
| Wrong key (ciphertext from a different key) | 400 |
| AAD mismatch | 400 |
| Version disabled | 400 |
| Version destroyed | 400 |
Key versions¶
Add version (rotation)¶
Body: {}. Creates the next numbered version and immediately promotes it to primary.
The old version remains active for decrypting existing ciphertexts.
Get version¶
List versions¶
Returns { "cryptoKeyVersions": [...] }.
Patch version state¶
Sets the version state. Supported values: ENABLED, DISABLED. After a state change,
the primary pointer on the parent key is updated to the highest-numbered enabled version.
Schedule destruction¶
Sets state to DESTROY_SCHEDULED. Returns the updated version resource.
Restore version¶
Restores a DESTROY_SCHEDULED version to DISABLED. Returns the updated version
resource.
Key version states¶
| State | Can encrypt? | Can decrypt? |
|---|---|---|
ENABLED |
Yes (if primary) | Yes |
DISABLED |
No | No |
DESTROY_SCHEDULED |
No | No |
DESTROYED |
No | No |
Primary selection: when the primary version is disabled or scheduled for destruction,
the key's primary pointer automatically advances to the next-highest enabled version.
If no enabled versions remain, primary is cleared.
Key rotation flow¶
- Encrypt data — uses primary version
1. - Call
POST .../cryptoKeyVersions— creates version2, version2becomes primary. - Encrypt new data — uses version
2. - Old ciphertext — still decrypts via version
1(version name embedded in ciphertext). - Disable version
1— old ciphertexts can no longer be decrypted. - Schedule version
1for destruction.
Ciphertext format¶
Ciphertexts are opaque base64-encoded blobs. Internally they encode:
The version name is embedded so that decryption automatically selects the correct key material, enabling transparent cross-version decryption after rotation.
Asymmetric operations¶
Get public key¶
GET /v1/projects/{project}/locations/{location}/keyRings/{ring_id}/cryptoKeys/{key_id}/cryptoKeyVersions/{version_id}/publicKey
Returns the PEM-encoded public key for an ASYMMETRIC_SIGN or ASYMMETRIC_DECRYPT version:
{
"pem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n",
"algorithm": "EC_SIGN_P256_SHA256",
"name": "projects/.../cryptoKeyVersions/1",
"protectionLevel": "SOFTWARE"
}
Asymmetric sign¶
POST /v1/projects/{project}/locations/{location}/keyRings/{ring_id}/cryptoKeys/{key_id}/cryptoKeyVersions/{version_id}:asymmetricSign
Signs a pre-computed digest (not the raw message). The request body must contain
exactly one of sha256, sha384, or sha512 inside the digest field:
Response:
Supported algorithms:
| Algorithm | Key type | Digest |
|---|---|---|
EC_SIGN_P256_SHA256 |
EC P-256 | SHA-256 |
EC_SIGN_P384_SHA384 |
EC P-384 | SHA-384 |
RSA_SIGN_PSS_2048_SHA256 |
RSA 2048 | SHA-256 |
RSA_SIGN_PSS_3072_SHA256 |
RSA 3072 | SHA-256 |
RSA_SIGN_PSS_4096_SHA256 |
RSA 4096 | SHA-256 |
Asymmetric decrypt¶
POST /v1/projects/{project}/locations/{location}/keyRings/{ring_id}/cryptoKeys/{key_id}/cryptoKeyVersions/{version_id}:asymmetricDecrypt
Decrypts data encrypted with the corresponding public key using RSA-OAEP.
Requires an ASYMMETRIC_DECRYPT key (e.g. RSA_DECRYPT_OAEP_2048_SHA256).
Response:
MAC keys (HMAC-SHA256)¶
Create a MAC key with purpose: "MAC" and versionTemplate.algorithm: "HMAC_SHA256".
macSign¶
Response:
macVerify¶
Response:
success is true when the HMAC tag is valid; false otherwise (always HTTP 200).
Known limitations¶
| Feature | Notes |
|---|---|
| Key import | ImportJob endpoints not implemented |
| Key deletion | GCP does not allow key ring or key deletion; DELETE is not implemented |
| IAM | Not enforced |
Examples¶
| Example | What it demonstrates |
|---|---|
encrypt_decrypt.py |
Key ring and key creation; encrypt/decrypt round-trip; AAD binding; cross-key rejection |
key_rotation.py |
Adding a version; version promotion; decrypting old ciphertexts after rotation; disable/enable/destroy/restore lifecycle |