Repository navigation
Encryption key rotation with XChaCha20-Poly1305 - #73
Merged
Merged
Conversation
This was referenced Sep 29, 2026
Add Cubex\Encryption: EncrypterInterface (string in, string out) and an XChaCha20-Poly1305 implementation. Payloads are base64url of a version byte, a random 24 byte nonce and the ciphertext with tag; the version is bound as additional data. Cipher keys are derived with HKDF-SHA256 from configured keys of at least 16 bytes. security.encryption_key may be a list: the first key encrypts, later keys only decrypt. An empty first entry throws instead of promoting an old key. Payloads written by illuminate/encryption (Cubex 2.6) still decrypt through Legacy\IlluminateFallbackEncrypter, which is decrypt-only and can be removed once they have expired. 'encrypter' is now an adapter implementing Illuminate's encrypter contract, unserializing with allowed_classes disabled. illuminate/encryption moves to require-dev for compatibility tests; illuminate/contracts is required directly. Sodium is suggested, and checked when an encrypter is constructed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
TomK
force-pushed
the
encryption-sodium-2x
branch
from
September 29, 2026 09:43
a5ac41f to
24187be
Compare
bajb
approved these changes
Sep 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
encrypteris a single-key illuminate/encryption instance, so rotating[security] encryption_key(e.g. after a leak) makes every existing encrypted cookie undecryptable at once. Its AES-CBC + HMAC JSON payload is also large, and it unserializes decrypted data without restriction.Adds
Cubex\Encryption:EncrypterimplementsEncrypterInterface(string in, string out) with XChaCha20-Poly1305. A payload is base64url of a version byte, a random 24-byte nonce and the ciphertext with its tag. The version byte is bound as additional data. The cipher key is derived with HKDF-SHA256 from each configured key, which must be at least 16 bytes.encryption_keycan now be a list. The first key encrypts; later keys only decrypt. An empty first entry throws, so an unset env var can't promote an old key. A missing item or[]keeps the existing default key.Legacy\IlluminateFallbackEncrypterdecrypts payloads written by Cubex 2.6, using AES-128/256-CBC chosen by key length. It never writes that format and can be deleted once old cookies have expired.encrypterbinding: now anIlluminateEncrypterAdapterimplementing Illuminate's contract, soEncryptCookieskeeps working. It unserializes withallowed_classes => false.EncrypterInterfaceis bound too.illuminate/encryptionmoves torequire-dev(compatibility tests only), andilluminate/contractsis required directly. Sodium is optional:ext-sodiumorparagonie/sodium_compatis listed undersuggestand checked only when an encrypter is constructed. There is no fallback algorithm.Previous keys are normally compromised, so payloads they decrypt may be forged. Keep the window short, and consumers can re-issue cookies onto the new key. Payloads are roughly half the size: 45 bytes of plaintext becomes 115 characters instead of 244.
Test plan
New tests for
Encrypter, the legacy fallback, the adapter and the service cover:__PHP_Incomplete_Classwithout__wakeupRan
vendor/bin/phpunit --coverage-texton PHP 7.4.33 with PHPUnit 9.6: 338 tests passed with 99.26% line coverage (threshold 98). The new classes have 100% coverage. The 9createPartialMockwarnings inCubexKernelTestalso appear on2.0without this change.Rollout
2.0withgh release create.cubex/frameworkin every app that shares the cookie key, then setsencryption_key[] = {{ENV:COOKIE_ENCRYPTION_KEY}}followed byencryption_key[] = <old key>.🤖 Generated with Claude Code