geekcell/sodium-bundle
GeekCell Sodium Bundle integrates PHP's Sodium extension with Symfony, providing configurable services for authenticated encryption, decryption, password hashing, and secure random bytes. It simplifies using libsodium's modern cryptography in your applications.
Because the bundle is archived and has no dependents, treat it as a small, auditable wrapper you vendor into a project rather than a long-lived framework dependency. As of v1.0.3 (2024‑05‑28) the baseline moved: PHP 8.2 is the minimum, and the Symfony constraint is ^6.0 | ^7.0. Note this is a Symfony bundle — in a Laravel app you will typically be borrowing its classes (or calling sodium directly), not registering the bundle. Confirm each step against vendor/geekcell/sodium-bundle, since docs are thin.
sodium extension, not paragonie/sodium_compat. Check php -m | grep sodium in every environment (CLI, FPM, workers) and confirm php -v reports 8.2+ — 1.0.3 will not install on 8.1. If any environment lacks the extension, fix that before proceeding — the bundle will not silently fall back.config/bundles.php:
return [
// ...
GeekCell\SodiumBundle\GeekCellSodiumBundle::class => ['all' => true],
];
In a Laravel app there is no bundles.php; pull in the classes you need and bind them in a service provider (see the service-wrapper pattern below).bin/console debug:container --show-private | grep -i sodium
bin/console config:dump-reference geek_cell_sodium 2>/dev/null
Look for the encryptor service ID and its constructor arguments. This tells you whether keys come from bundle config, environment variables, or are injected manually — the single most important thing to confirm given the sparse documentation. Note that 1.0.3 includes "fixed command / bundle to respect symfony 7.0 changes," so the service wiring and any console command signatures may differ from what you'd infer from 1.0.2 blog posts or older vendor code.php -r 'echo base64_encode(sodium_crypto_secretbox_keygen()), PHP_EOL;'
php -r '$kp = sodium_crypto_box_keypair(); echo base64_encode(sodium_crypto_box_publickey($kp)), PHP_EOL, base64_encode(sodium_crypto_box_secretkey($kp)), PHP_EOL;'
Store the base64 values in your secrets manager and expose them as environment variables. Never commit them.If debug:container shows no service, the bundle may only ship classes (not autowired services); in that case instantiate them directly and register aliases in config/services.yaml.
Wrap the bundle in your own service. Do not let framework or domain code depend on the bundle's API directly. Define an EncryptionInterface in your app with encrypt(string): string and decrypt(string): string, and implement it using the bundle's service. This buys you a one-file migration path if you replace the bundle (likely, given it is archived) and keeps your tests mockable. It also absorbs the cost of future Symfony/PHP constraint bumps, which is the only kind of change 1.0.3 delivered.
Encode ciphertext before persistence. Sodium produces raw binary. Any ciphertext that touches a database, cache, message queue, or HTTP header must be encoded. Use base64url without padding and prefix the encoding and key ID so you can rotate later:
v1.<keyId>.<base64url(nonce || ciphertext)>
Storing the nonce alongside the ciphertext is correct and expected — nonces are public. Do not invent your own nonce derivation.
Nonce handling is the one thing you cannot get wrong. For sodium_crypto_secretbox (and any nonce-based primitive the bundle exposes), generate a fresh random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES) for every single encryption. Reusing a nonce with the same key is a catastrophic failure that breaks confidentiality entirely. If the bundle's API does not generate the nonce internally, generate it in your wrapper and never accept a caller-supplied nonce.
Prefer sealed boxes for one-way data. If you only need to encrypt (e.g., a webhook writes encrypted data into a queue that another service reads), use sodium_crypto_box_seal / sodium_crypto_box_seal_open. There is no nonce to manage and only the public key is needed on the write side, which means the write service never holds a decryption key. This is the pattern to reach for by default in multi-service setups.
Doctrine integration for encrypted columns. Implement a custom Doctrine type or a postLoad/prePersist listener rather than encrypting in controllers. A custom Doctrine\DBAL\Types\Type mapping to TEXT/BLOB keeps the encryption concern in one place, works with migrations, and survives refactors. Note that encrypted columns cannot be searched or sorted server-side — plan for a blind index (HMAC of the plaintext with a separate key) if you need lookups.
Key rotation with key IDs. Even with a single key today, store a key identifier in the ciphertext envelope. Implement decryption as "read key ID → fetch that key → decrypt," and encryption as "always use the current key." This makes rotation a background re-encryption job rather than a big-bang migration.
Messenger and HTTP payloads. Use the bundle for signing or encrypting message bodies crossing trust boundaries, but keep the envelope small and versioned. Encrypt the payload, not the entire serialized Envelope, so routing metadata remains inspectable and debuggable.
Testing. Generate ephemeral keys in test setup (sodium_crypto_secretbox_keygen() in setUp) and inject them, rather than relying on environment variables in CI. Add one test that asserts a tampered ciphertext fails decryption — this catches accidental use of unauthenticated primitives and encoding bugs. If you fork the package, note 1.0.3 migrated the PHPUnit config and removed deprecated PHPStan configuration; older config snippets you find in the wild will emit warnings or not run.
composer.json, and read the entire source before adoption — it is small enough to audit in an hour. If it does not meet your needs, sodium is directly callable from PHP; a 40-line service of your own may be a better long-term bet.false on authentication failure rather than throwing. If the bundle passes that through, you will get false silently flowing into your domain. Wrap every decrypt in a check and throw a dedicated exception — silently returning null or an empty string from a decrypt path is a data-corruption bug waiting to happen.sodium_crypto_box_seal uses a 32-byte public key. If your config loader decodes base64 you can end up with the wrong length; assert lengths at bootstrap so failures are loud and early rather than at first decrypt.config:cache and secrets. Symfony's config cache bakes in values at compile time. If the bundle resolves keys from %env()% at container compile, key rotation requires a cache clear and deploy rather than a restart. Verify with bin/console debug:container after changing the env var — especially after the 1.0.3 wiring changes.password_hash/password_verify. If the bundle exposes a generic "hash" method, ignore it — sodium_crypto_generichash is fast and unsuited to passwords.services.yaml override if you need to add key rotation, envelope versioning, or logging around it. This is cleaner than subclassing bundle internals and keeps upgrade surface near zero — particularly valuable now that the only published changes are Symfony/PHP constraint churn.How can I help you explore Laravel packages today?