Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cloud Kms Laravel Package

google/cloud-kms

Official Google Cloud PHP client for Cloud Key Management Service (KMS). Create and manage key rings and cryptographic keys, encrypt and decrypt data, sign and verify payloads, and control access with IAM policies from PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install

composer require google/cloud-kms

PHP 8.1+ and ext-grpc (optional but recommended — the client falls back to REST if absent).

2. Authenticate. The client uses Application Default Credentials. Locally:

gcloud auth application-default login
# or point at a service account
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa.json

The service account needs roles/cloudkms.cryptoKeyEncrypterDecrypter (add ...SignerVerifier for signing, ...Viewer for reading metadata). On Cloud Run / GKE / App Engine, the attached service account just works — no file needed.

3. Create the key ring and key once, via CLI (much faster than doing it in PHP):

gcloud kms keyrings create app --location=global
gcloud kms keys create general --location=global --keyring=app \
  --purpose=encryption --rotation-period=90d --next-rotation-time="..."

4. First use case — encrypt a small secret. This is the 90% case for a Laravel app: encrypting an OAuth refresh token, a third-party API key, or a TOTP seed before it goes into MySQL.

use Google\Cloud\Kms\V1\Client\KeyManagementServiceClient;

$kms  = new KeyManagementServiceClient();               // singleton, see below
$name = KeyManagementServiceClient::cryptoKeyName(
    'my-project', 'global', 'app', 'general'
);

$res        = $kms->encrypt($name, 'sk_live_abc123');
$ciphertext = $res->getCiphertext();                     // BINARY string

$plain = $kms->decrypt($name, $ciphertext)->getPlaintext();

Where to look first: the Google\Cloud\Kms\V1\Client\KeyManagementServiceClient class — every method you need (encrypt, decrypt, asymmetricSign, macSign, getPublicKey, rawEncrypt) lives there, plus the static *Name() helpers that build resource paths and save you from string-typos. The V1 namespace is the only one you should touch; ignore older aliases like CloudKmsClient.

Implementation Patterns

Bind the client as a singleton. Every new KeyManagementServiceClient() opens a gRPC channel and runs ADC resolution. Do it once:

// app/Providers/KmsServiceProvider.php
$this->app->singleton(KeyManagementServiceClient::class, fn () => new KeyManagementServiceClient([
    'credentials' => config('kms.credentials'), // optional: path to JSON
]));

$this->app->singleton(KmsEncrypter::class, fn ($app) => new KmsEncrypter(
    $app->make(KeyManagementServiceClient::class),
    config('kms.key_name'),
));

Put project, location, key_ring, key in config/kms.php, driven by env. This makes it trivial to use different keys per environment (which you should — never share a key ring between staging and prod).

Wrap it behind a tiny interface. Don't inject the GAPIC client into controllers. A two-method interface (encrypt(string): string, decrypt(string): string) that returns base64-encoded strings keeps the rest of your app ignorant of KMS, makes testing trivial, and lets you swap in a local openssl implementation for dev.

Encrypt attributes with a custom cast.

class EncryptedCast implements CastsAttributes
{
    public function get($model, $key, $value, $attributes) {
        return $value === null ? null : app(KmsEncrypter::class)->decrypt($value);
    }
    public function set($model, $key, $value, $attributes) {
        return $value === null ? null : app(KmsEncrypter::class)->encrypt($value);
    }
}

This is the highest-leverage pattern: protected $casts = ['refresh_token' => EncryptedCast::class]; and you're done. Use it for anything you'd otherwise be tempted to encrypt with Crypt:: using a key in .env.

Envelope encryption for anything over 64 KiB. KMS is not a bulk cipher. The correct pattern:

$dek        = random_bytes(32);
$iv         = random_bytes(12);
$ct         = openssl_encrypt($payload, 'aes-256-gcm', $dek, OPENSSL_RAW_DATA, $iv, $tag);
$wrappedDek = $kms->encrypt($keyName, $dek)->getCiphertext();
// store: base64($wrappedDek) . base64($iv) . base64($tag) . base64($ct)

Decrypt reverses it: unwrap the DEK with one KMS call, then decrypt the (potentially multi-megabyte) payload locally. This is how you should handle uploaded documents, exports, PDFs, backup blobs. One KMS call per file, not per byte.

Signing and verification without secrets. For webhook signing, download tokens, or license keys, asymmetricSign / asymmetricVerify with an ASYMMETRIC_SIGN key beats HMAC because the verifying side only needs the public key (getPublicKey) — no shared secret to leak. Use macSign/macVerify if you genuinely need symmetric HMAC and want the key to never leave Google.

Caching and cost. KMS bills per operation (~$0.03/10k). If you decrypt the same config secret on every request, wrap the decrypt in Cache::rememberForever() and bust it on rotation. For request-path encryption of transient data, consider whether you actually need KMS at all versus Laravel's Crypt with an APP_KEY that lives in Secret Manager.

Queues and Octane. Never serialize the client into a job payload — resolve it inside handle() from the container. Under Octane the singleton survives across requests, which is exactly what you want; the underlying auth library refreshes tokens automatically, so no manual lifecycle management.

Testing. Mock your own KmsEncrypter interface in feature tests (a pass-through or base64_encode fake). For genuine integration coverage, hit a dedicated test key ring in a sandbox project — record/replay fixtures of ciphertext are brittle because ciphertext is non-deterministic.

Gotchas and Tips

The 64 KiB plaintext ceiling. encrypt rejects anything larger with INVALID_ARGUMENT. The error message is not obvious about size. If you hit it, you needed envelope encryption, not a bigger key.

Ciphertext is binary and grows. getCiphertext() returns a raw byte string, not base64. Base64-encode before persisting or the column will mangle it. Expect roughly 100–200 bytes of overhead on top of your plaintext, so a VARCHAR(255) for a 40-char token is asking for trouble — use TEXT or size deliberately.

IAM grants take a minute or two to propagate. A freshly created service account with the right role will still throw PERMISSION_DENIED for the first few calls. Don't chase this as a code bug; wait and retry.

Transport matters. Without ext-grpc the client silently uses REST, which is noticeably slower per call. If you're doing KMS work inside a request, install the extension. You can also force it: new KeyManagementServiceClient(['transport' => 'rest']) for environments where gRPC is problematic (some shared hosts, certain proxies).

Handle failures explicitly. Catch Google\ApiCore\ApiException and branch on $e->getStatus():

  • PERMISSION_DENIED — IAM, or wrong project/key name.
  • NOT_FOUND — resource name typo (usually a missing cryptoKeys/ segment).
  • FAILED_PRECONDITION — key version is DISABLED/DESTROYED, or you're trying to encrypt with an asymmetric key.
  • UNAVAILABLE — transient; the client retries by default, but don't put a raw decrypt in a tight loop without a timeout.

Rotation is free — don't manage versions manually. When a key rotates, encrypt automatically uses the new primary version and decrypt transparently handles ciphertext from old versions. There is no version bookkeeping to do. Never call destroyCryptoKeyVersion on anything you might still need to decrypt; it's irreversible after the 24-hour window.

Use the static name helpers. KeyManagementServiceClient::cryptoKeyName($project, $location, $ring, $key) returns the correct projects/.../cryptoKeys/... string. Hand-building these is the single most common source of NOT_FOUND. Pass 'global' unless you have data-residency requirements.

Don't log the plaintext, and don't log the ciphertext either — logging ciphertext plus a leaked key is game over. Add the field names to whatever scrubber you use for exception reporting.

Envelope-encryption key ordering. Store the wrapped DEK, IV, and auth tag in a single structured column (JSON or a delimited string) — if you split them across columns, a partial write leaves you with undecryptable data. Write the whole blob in one UPDATE.

Local development friction. GOOGLE_APPLICATION_CREDENTIALS in .env is fine for solo dev, but never commit the JSON, and remember php artisan config:cache will bake the path in — re-run it after changing credentials. A cleaner setup is a NullEncrypter binding in local and testing environments so the app boots without Google credentials at all.

Small ecosystem, official maintenance. With ~12 stars this package has essentially no community surface area — no Laravel wrapper, few blog posts, few StackOverflow answers. In exchange it's Google-generated from the KMS protobuf and released on a regular cadence (recent releases land well into 2026), so it will keep working and the method signatures mirror the REST/gRPC API docs exactly. When you're stuck, read the Cloud KMS REST reference rather than searching for PHP examples — the concepts map one-to-one, and the generated client is a thin veneer over it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager