Releases: spaze/encryption
Releases · spaze/encryption
Release list
Asymmetric encryption, and stored values that say what created them
TL;DR
- Asymmetric encryption: Two new ways to encrypt
- Markers: Stored values now say what created them.
- Everything written by previous versions still decrypts, and always will.
Encryption between two parties, encryption to a public key
AuthenticatedPublicKeyEncryption: each side configures its own secret key and the other side's public key under one key id; both sides encrypt and decrypt, and decryption also proves the data came from the other party, or from us. (#38)AnonymousPublicKeyEncryption: anyone with the public key can encrypt, only whoever holds the matching secret key can decrypt. A deployment that only writes data needs just the public keys and cannot read anything back, not even what it has just encrypted. (#38)- Key configuration works like the symmetric one,
prefix_hexvalues; the recommended tagged formprefix_secret_hex/prefix_public_hexmakes a key pasted into the wrong slot fail when the object is created, instead of producing data nobody can ever decrypt. A mismatched pair (AnonymousPublicKeyEncryption) or a missing half (AuthenticatedPublicKeyEncryption) fails there too. (#38)
Stored values carry a marker
- Newly written values look like
$keyId$SymV1$...(SymAdV1,AuthV1,AuthAdV1,AnonV1). Feeding a value to the wrong class or the wrong method fails with an exception that names the class and method that created it, instead of a misleading decryption error. (#38, #40) - The key id and the marker go into what decryption verifies (except in
AnonymousPublicKeyEncryption, where a sealed value has no place for that): changing either in a stored value, stripping the marker, or forging one onto an old value makes decryption fail. The old "key id is not protected against tampering" caveat now applies only to values in the old format. (#38, #40) - Everything written by previous versions still decrypts, and always will.
Upgrading
needsReEncrypt()now also returns true for values in the old format, which is everything written before this release, so your usual re-encryption sweep migrates all data to the marked format. Rows created byencryptWithAd()must be re-encrypted with the row's additional data. (#38, #40)- Older versions cannot read the marked values: when multiple deployments share data, upgrade all of them before writing anything new. (#38, #40)
- Mixed-up values now throw
FormatMarkerMismatchExceptionorUnknownFormatMarkerExceptionwhere the old code threw Halite'sInvalidMessageorInvalidNumberOfComponentsException; catch blocks written for the old types won't catch the new ones. The format-error message now names both accepted shapes. (#38, #40) - Requires
paragonie/halite5.1+ (was 5.0). (#36)
Smaller changes
- Key ids and markers repeated in exception messages are shortened and made printable, so a key pasted into an id slot or a tampered stored value can't push key material or garbage into logs. (#38, #39)
- README: key generation examples now use
sodium_bin2hex()(#42), keys are documented as valid in both lowercase or uppercase (#43, #44), and the runtime-call service example uses the right parameter name (#39). - For contributors: PHPStan checks the code with all bundled rules of
spaze/phpstan-disallowed-calls, including the new non-timing-safe encoding rules (#37, #42), and the repo has anAGENTS.mdwith the invariants that AI tools and reviewers must respect (#41).
Validate the configuration in the constructor, reject malformed encrypted data early
What's Changed
- Follow-ups to the constructor validation from 2.3.0 (#35). A numeric key id no longer fails with a
TypeError, andInvalidKeyPrefixExceptionno longer includes the key value in its message. A key with no prefix at all now throws the newMissingKeyPrefixException, which extendsInvalidKeyPrefixException, so existingcatchblocks keep working.
2.3.1 release notes below
- Require
ext-sodiumin composer.json (#34). The requirement was already announced in the 2.3.0 release notes below, but composer.json in 2.3.0 was accidentally missing it, this release adds it.
2.3.0 release notes below
- Validate keys, key ids, and the active key id in the constructor instead of failing at first use (#31)
- A misconfigured key - wrong length, not valid hex, a key id that would break the output format (empty or containing
$), or an active key id missing from the keys array - now throws when the service is created, not on the firstencrypt()call after a deploy. - New exceptions:
InvalidKeyEncodingException,InvalidKeyLengthException,InvalidKeyIdException,ActiveKeyIdNotFoundException.
- A misconfigured key - wrong length, not valid hex, a key id that would break the output format (empty or containing
- Reject encrypted data in a format that this library never produces (#32)
- Inputs like
garbage$keyId$ciphertext,$keyId$or$$ciphertextnow throw the newInvalidCipherTextFormatExceptioninstead of failing later with confusing downstream exceptions. InvalidNumberOfComponentsExceptionnow extends the new class, so existingcatchblocks keep working.
- Inputs like
- Documentation additions (#33)
- Keys defined in Nette config parameters also end up in the compiled DI container file in the temp directory, treat it accordingly.
- From the README: "The key id in the output […] is the only part of the output not protected against tampering […] Never configure the same key under two different ids."
Upgrade notes
- Nothing changes for correctly configured setups, and everything they ever encrypted still decrypts.
- The library now requires
ext-sodium(bundled with PHP since 7.2); previously it could in theory run on thesodium_compatpolyfill that Halite uses. - If a configuration was broken without you knowing, the constructor will now tell you.
- One case possibly needs care: a key truncated to an odd number of hex characters (e.g. 63 instead of 64) used to work, because the decoder used previously,
Hex::decode(), would quietly prepend a0instead of rejecting the input, so the key was used as if it had a leading zero, without anyone knowing. Keys are now decoded withsodium_hex2bin(), which rejects anything that is not valid hex, so such a configuration throwsInvalidKeyEncodingException. To keep the already encrypted data readable, add the0back yourself, right after the prefix separator - that is byte for byte the key that was actually used.
Add Additional Authenticated Data (AAD) support
What's Changed
- Add Additional Authenticated Data (AAD) support via
encryptWithAd()/decryptWithAd()(#30)
From the README: "Additional Authenticated Data (AAD) cryptographically binds a ciphertext to a context (like a row id, column name, or tenant id). The additional data (the context) is not encrypted, and thus it must not be a secret. This prevents attackers or buggy scripts from copying a valid ciphertext from one place and pasting it into another." - Docs updates (#28)
- Bump actions/checkout (#26, #27)
Support PHP 8.5 and internal improvements
Internal changes and improvements:
Support PHP 8.4
v2: support key prefixes, renamed class, no key groups
Backwards compatibility breaks
- Rename
Symmetric\StaticKeyclass toSymmetricKeyEncryption(#12) - Use custom exceptions but new classes extend from the old ones, so your
catchblocks should still work (#14) - Drop key group constructor param, you can always just pass particular keys (#16)
- Add a prefix to encryption keys so they're easier to find with tools like Gitleaks (#18)
Other changes
Support PHP 8.3
What's Changed
- Support PHP 8.3 (#11)
- Bump actions/checkout from 3 to 4 (#10) Did you know you can use @dependabot to update your actions, not just your code? I've updated my article which mentions Dependabot https://www.michalspacek.com/dont-let-security-bugs-catch-you-off-guard#github-dependabot
Support PHP 8.2
Require PHP 8.0 at least
... not only because this release uses Halite v5 which requires PHP 8.0 at least.