DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Newsletter
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Why SAP S/4HANA Landscape Design Impacts Cloud TCO More Than Compute Costs
  • We Went Multi-Cloud and Almost Drowned: Lessons From Running Across AWS, GCP, and Azure
  • Mastering Multi-Cloud Integration: SAFe 5.0, MuleSoft, and AWS - A Personal Journey
  • Cost Is a Distributed Systems Bug

Trending

  • Who Should Grade Your AI? Rules vs LLM Judges vs Humans
  • Builder Pattern + Faker in API Test Automation Using Playwright With TypeScript
  • When One Docker Swarm Replica Became Unreachable Over an Overlay Network: A Production Case With Traefik
  • AI-Era Cybersecurity, Part 2: Building the System Context, Governance and Agent Identity
  1. DZone
  2. Software Design and Architecture
  3. Cloud Architecture
  4. Google Cloud KMS vs. AWS KMS: Java Encryption Guide

Google Cloud KMS vs. AWS KMS: Java Encryption Guide

Compare Google Cloud KMS vs. AWS KMS in Java, including envelope encryption, key rotation, IAM, data keys, and audit logging.

By 
Sandeep Pal user avatar
Sandeep Pal
·
Oct. 22, 26 · Analysis
Likes (2)
Comment
Save
Tweet
Share
181 Views

Join the DZone community and get the full member experience.

Join For Free

Every system that stores sensitive data needs keys that never leave a hardened boundary, an audit trail of every use, and rotation that doesn't mean re-encrypting petabytes. On Google Cloud, that's Cloud KMS; on AWS, it's AWS KMS. Both store symmetric and asymmetric keys, both use HSMs, and both are built for envelope encryption. 

But AWS KMS vs. Google Cloud KMS comes down to the choices that show up in your code: how keys are named, what rotation means, where authorization lives, and whether the service hands you data keys. This article walks through Cloud KMS in Java and then maps it onto AWS KMS.

The Cloud KMS Resource Model

Cloud KMS organizes keys in a four-level hierarchy under a project:

  • Location. Where the key material lives: a region such as us-east1, a multi-region such as us, or global. Key material stays in the location you pick for its whole lifecycle.
  • Key ring. A named group of keys in one location. It exists for organization and IAM inheritance.
  • CryptoKey. The key you reference in code. It has a purpose (ENCRYPT_DECRYPT, ASYMMETRIC_SIGN, ASYMMETRIC_DECRYPT, MAC, and a few more) and a rotation schedule.
  • CryptoKeyVersion. The actual key material. A key has one or more versions, and for symmetric keys one of them is the primary, the version that new encryptions use.
Symmetric key with three versions
A symmetric key with three versions: new data uses version 3, data written under version 2 still decrypts, and version 1 is on its way out.


A key is identified only by its full resource name, projects/my-project/locations/us-east1/keyRings/payments/cryptoKeys/card-data, optionally followed by /cryptoKeyVersions/2. There are no aliases. Nothing can move after it's created: the location is fixed when you create the key ring, and key rings themselves can't be deleted.

Each key version also has a protection level:

  • SOFTWARE: keys are protected by Google's software crypto (BoringCrypto, FIPS 140-3 Level 1).
  • HSM: keys live in Cloud HSM, which is FIPS 140-2 Level 3 validated. HSM_SINGLE_TENANT gives you a dedicated HSM.
  • EXTERNAL and EXTERNAL_VPC: Cloud EKM keeps the key material in a supported external key manager that you operate, reached over the internet or a VPC. Google never holds the key.

Creating a Key That Rotates Itself

The Java client is KeyManagementServiceClient. It uses Application Default Credentials, so on GKE, Cloud Run, or GCE, the workload's service account authenticates with no keys in the code. This creates a key ring and an HSM-backed symmetric key that rotates every 90 days:

Java
 
try (KeyManagementServiceClient kms = KeyManagementServiceClient.create()) {
    KeyRing ring = kms.createKeyRing(
        LocationName.of("my-project", "us-east1"), "payments", KeyRing.getDefaultInstance());

    CryptoKey key = CryptoKey.newBuilder()
        .setPurpose(CryptoKeyPurpose.ENCRYPT_DECRYPT)
        .setVersionTemplate(CryptoKeyVersionTemplate.newBuilder()
            .setAlgorithm(CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION)
            .setProtectionLevel(ProtectionLevel.HSM))
        .setRotationPeriod(Duration.newBuilder().setSeconds(90L * 24 * 3600))   // every 90 days
        .setNextRotationTime(Timestamp.newBuilder()
            .setSeconds(Instant.now().plusSeconds(90L * 24 * 3600).getEpochSecond()))
        .build();

    kms.createCryptoKey(KeyRingName.parse(ring.getName()), "card-data", key);
}


The rotation period can be anywhere from one day to 100 years. When it fires, Cloud KMS creates a new version and makes it primary. Earlier versions stay enabled, so old ciphertext still decrypts, and nothing is re-encrypted. To rotate by hand, create a version with CreateCryptoKeyVersion and promote it with UpdateCryptoKeyPrimaryVersion. Automatic rotation applies only to symmetric keys; asymmetric keys are rotated manually. 

The AWS KMS key rotation process differs in how key material is managed, as described below.

Envelope Encryption

KMS keys aren't meant to encrypt your data directly. A single Encrypt call accepts at most 64 KiB of plaintext (8 KiB of plaintext plus AAD for HSM keys), and every call is a network round trip. The standard pattern is envelope encryption, also used with AWS KMS envelope encryption: encrypt the data locally with a data encryption key (DEK), then have KMS encrypt (wrap) the DEK with a key encryption key (KEK) that never leaves the service. You store the wrapped DEK next to the ciphertext.
Envelope encryption

Envelope encryption: The payload never goes to KMS; only the 32-byte DEK does.


Cloud KMS has no data-key API, so you generate the DEK yourself. Google's guidance is to generate a new DEK every time you write data, never share one across users, and prefer Tink, which packages this whole flow. Doing it by hand shows what's happening:

Java
 
CryptoKeyName kek = CryptoKeyName.of("my-project", "us-east1", "payments", "card-data");
ByteString aad = ByteString.copyFromUtf8("tenant=acme");   // must be supplied again to decrypt

try (KeyManagementServiceClient kms = KeyManagementServiceClient.create()) {
    // 1. Generate a fresh DEK locally. Cloud KMS has no GenerateDataKey call.
    KeyGenerator gen = KeyGenerator.getInstance("AES");
    gen.init(256);
    SecretKey dek = gen.generateKey();
    // ... encrypt the payload locally with dek (AES-GCM), then discard dek ...

    // 2. Wrap the DEK with the KEK, with CRC32C checks against corruption in transit.
    ByteString dekBytes = ByteString.copyFrom(dek.getEncoded());
    EncryptResponse wrapped = kms.encrypt(EncryptRequest.newBuilder()
        .setName(kek.toString())
        .setPlaintext(dekBytes)
        .setPlaintextCrc32C(crc32c(dekBytes))
        .setAdditionalAuthenticatedData(aad)
        .setAdditionalAuthenticatedDataCrc32C(crc32c(aad))
        .build());
    if (!wrapped.getVerifiedPlaintextCrc32C()
        || wrapped.getCiphertextCrc32C().getValue() != crc32c(wrapped.getCiphertext()).getValue()) {
        throw new IllegalStateException("integrity check failed");
    }
    ByteString wrappedDek = wrapped.getCiphertext();   // store next to the encrypted payload
    String usedVersion = wrapped.getName();            // the primary version that wrapped it

    // 3. Later: unwrap. Pass the key, not the version; Cloud KMS picks the version.
    ByteString unwrapped = kms.decrypt(DecryptRequest.newBuilder()
            .setName(kek.toString())
            .setCiphertext(wrappedDek)
            .setAdditionalAuthenticatedData(aad)
            .build())
        .getPlaintext();
    SecretKey restored = new SecretKeySpec(unwrapped.toByteArray(), "AES");
}

static Int64Value crc32c(ByteString data) {
    return Int64Value.of(Hashing.crc32c().hashBytes(data.toByteArray()).padToLong());
}


Encrypt uses the key's current primary version. Decrypt also takes the key, not a version: the ciphertext records which version produced it, so rotation is invisible to readers. The optional CRC32C fields let the server confirm it received your plaintext intact, and let you confirm the ciphertext you got back.

The AAD (tenant=acme) is bound to the ciphertext, so one tenant's wrapped DEK can't be swapped into another tenant's record.

Disabling, destroying, and deleting

Lifecycle operations act on versions, not keys. You disable a version (ENABLED to DISABLED) to stop it from being used, and re-enable it later. There's no switch that disables a whole key; you disable each version.

Destroying a version is deliberately slow. DestroyCryptoKeyVersion moves it to DESTROY_SCHEDULED for 30 days by default. The duration is set when the key is created, and an organization policy can enforce a minimum. During that window, RestoreCryptoKeyVersion brings it back in the DISABLED state. After the window, the material is gone and data encrypted by that version can't be recovered. Destroyed versions are no longer billed. Cloud KMS has also added permanent deletion: you can delete key versions and then the key itself, though a deleted key's name can't be reused.

Access control is just IAM

Cloud KMS has no policy language of its own. Access is ordinary IAM, granted at the organization, folder, project, key ring, or key level and inherited downward. The predefined roles split duties cleanly:

  • roles/cloudkms.admin manages keys but can't encrypt or decrypt.
  • roles/cloudkms.cryptoKeyEncrypterDecrypter, cryptoKeyEncrypter, and cryptoKeyDecrypter can use keys but not manage them.
  • roles/cloudkms.signerVerifier and publicKeyViewer cover asymmetric keys.

The operator who rotates keys never sees plaintext, and the service that decrypts can't destroy its key. Grant usage roles on the specific key, not the project.

Two more controls round this out. Cloud Audit Logs record admin operations (create, rotate, destroy) automatically, but Encrypt and Decrypt are Data Access logs, which are off by default. If you want a record of every key use, enable them for Cloud KMS. Key Access Justifications, used with Cloud EKM keys, attaches a reason code to every Google request for your key, and your external key manager can refuse it.

Bringing your own key

To import key material, you create an import job with a wrapping method, such as RSA_OAEP_3072_SHA256_AES_256 or one of the newer post-quantum HPKE methods. The job gives you a public key, and it expires after three days. You wrap your key material with that public key and import it as a new version of a key. For a key that must only ever hold imported material, set importOnly, which means rotation is manual: import new material and make it primary. If you destroy an imported version, you can re-import the same material later.

AWS KMS for comparison

AWS KMS covers the same ground with a flatter, version-less model:

  • Keys, not versions. A KMS key has a key ID (a UUID), an ARN, and optional aliases such as alias/card-data. There's no hierarchy above it beyond the account and Region.
  • Rotation is hidden. Automatic rotation is off by default for customer managed keys. When enabled, it runs every 365 days by default, configurable from 90 to 2,560 days, and RotateKeyOnDemand allows up to 25 extra rotations per key. The key ID never changes; AWS keeps old material internally and picks it on decrypt.
  • All keys in HSMs. Keys in the default key store live in HSMs validated at FIPS 140-3 Level 3. There's no software tier. CloudHSM key stores and external key stores (XKS) cover the dedicated and hold-your-own-key cases.
  • A data-key API. GenerateDataKey returns a fresh DEK in plaintext along with a copy wrapped under your key, so step 1 and step 2 above are one call.
  • Encryption context. The AWS equivalent of AAD is a map of key-value pairs. Unlike Cloud KMS AAD, it's recorded in CloudTrail and can be used in policy conditions such as kms:EncryptionContext:tenant.
  • Three authorization layers. Every key has exactly one key policy, a resource policy that supports Deny and conditions. IAM policies work only if the key policy enables them, and grants add temporary, programmatic permissions on top.
  • Whole-key lifecycle. DisableKey and ScheduleKeyDeletion act on the whole key. The deletion window is 7 to 30 days, and CancelKeyDeletion backs it out.
  • Replication. Multi-Region keys share the same key ID and material across Regions, and ReplicateKey copies one to a new Region. You choose this at creation; a single-Region key can't be converted.

Here's the same envelope flow on AWS:

Java
 
Map<String, String> context = Map.of("tenant", "acme");   // logged in CloudTrail, usable in policy

try (KmsClient kms = KmsClient.create()) {
    GenerateDataKeyResponse dataKey = kms.generateDataKey(GenerateDataKeyRequest.builder()
        .keyId("alias/card-data")
        .keySpec(DataKeySpec.AES_256)
        .encryptionContext(context)
        .build());
    byte[] dek = dataKey.plaintext().asByteArray();         // encrypt locally, then discard
    SdkBytes wrappedDek = dataKey.ciphertextBlob();         // store next to the payload

    byte[] unwrapped = kms.decrypt(DecryptRequest.builder()
            .keyId("alias/card-data")                       // optional for symmetric keys
            .ciphertextBlob(wrappedDek)
            .encryptionContext(context)
            .build())
        .plaintext().asByteArray();
}


KeyId is optional on Decrypt for symmetric keys because the ciphertext blob identifies the key, but AWS recommends passing it so you don't decrypt with a key you didn't expect. Cloud KMS requires the key name on every Decrypt.

Side by side

Concept Google Cloud KMS AWS KMS
Key identifier Resource name projects/…/cryptoKeys/k, no aliases Key ID, ARN, aliases
Versions First-class CryptoKeyVersion with its own state Hidden behind the key ID
Protection SOFTWARE, HSM (FIPS 140-2 L3), single-tenant HSM, EXTERNAL, EXTERNAL_VPC All HSM-backed (FIPS 140-3 L3), plus CloudHSM and external key stores
Max plaintext per call 64 KiB (8 KiB with AAD for HSM keys) 4,096 bytes
Data keys Generate locally (or with Tink) GenerateDataKey
Authenticated data AAD bytes, not logged Encryption context map, logged and usable in policies
Auto-rotation 1 day to 100 years 90 to 2,560 days, off by default, plus 25 on-demand
Disable Per version Whole key
Destroy Per version, 30 days scheduled by default, restorable Whole key, 7–30 days, cancellable
Access control IAM roles on the hierarchy, admin and usage split Key policy (with Deny) + IAM + grants
Usage logging Data Access audit logs, off by default CloudTrail management events
Multi-region Choose a multi-region location for the key ring Multi-Region keys, ReplicateKey
BYOK Import job, expires in 3 days GetParametersForImport, token valid 24 hours
Throughput quota (default) Token-based per region; about 60,000 software operations a minute 10,000 to 100,000 symmetric operations a second, by Region


What the AWS KMS vs. Google Cloud KMS encryption comparison means in practice:

  • Cloud KMS is simpler to reason about for access. It's one IAM model with roles that separate managing keys from using them. AWS is more expressive: a key policy can Deny, and conditions on encryption context can tie a key's use to a tenant. That power comes with the risk of a key policy that locks out its own administrators.
  • Versions are explicit on Google Cloud. You can disable or destroy just the key material that was compromised and leave the rest working. On AWS you disable or delete the whole key, or rotate and keep the old material.
  • AWS logs every use by default. On Google Cloud, enable Data Access logs for Cloud KMS on day one, or you won't have a record of who decrypted what.
  • Plan for quotas. AWS gives each account and Region a large shared pool of cryptographic operations. Cloud KMS's default is far smaller per project and region, so cache unwrapped DEKs for hot reads where your threat model allows, and request a quota increase before launch. Both quotas are adjustable.
  • Both want envelope encryption. Neither service is built to encrypt bulk data directly. On Google Cloud, Tink makes the local half trivial. On AWS, the AWS Encryption SDK plays the same role.

Conclusion

Google Cloud KMS treats keys as resources in your project hierarchy: versions are visible, protection level is chosen per key, and IAM is the whole authorization story. That's easy to audit and gives fine-grained control over key material, as long as you turn on Data Access logs and size your quotas. AWS KMS hides versions and puts authorization in a per-key policy language that's more expressive, especially with encryption context, and its data-key API saves a step. 

These differences are worth considering when reviewing AWS KMS best practices alongside Google Cloud KMS key management. On both clouds, the envelope pattern is the common ground: generate a DEK, wrap it, store it next to the data, and let KMS guard only the key that guards everything else.

AWS Cloud

Opinions expressed by DZone contributors are their own.

Related

  • Why SAP S/4HANA Landscape Design Impacts Cloud TCO More Than Compute Costs
  • We Went Multi-Cloud and Almost Drowned: Lessons From Running Across AWS, GCP, and Azure
  • Mastering Multi-Cloud Integration: SAFe 5.0, MuleSoft, and AWS - A Personal Journey
  • Cost Is a Distributed Systems Bug

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook