You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
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_hex values; the recommended tagged form prefix_secret_hex/prefix_public_hex makes 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 by encryptWithAd() 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 FormatMarkerMismatchException or UnknownFormatMarkerException where the old code threw Halite's InvalidMessage or InvalidNumberOfComponentsException; catch blocks written for the old types won't catch the new ones. The format-error message now names both accepted shapes. (#38, #40)
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 an AGENTS.md with the invariants that AI tools and reviewers must respect (#41).