Common questions, organised roughly by how often they come up.
If you have no opinion, Sodium. The construction has no knobs, the defaults are correct, and the cryptography is modern. Pick OpenSSL only if you have a concrete reason: FIPS compliance, custom cipher requirements, or libsodium-isn't-available environments.
See 03 — Sodium Handler and 02 — OpenSSL Handler.
Three reasons:
- URL-, cookie-, JSON-safe everywhere without escaping. Base64 needs
base64urlto be URL-safe; hex is URL-safe by default. - Easier to debug.
02006f1c...immediately tells you "this is a v2 ciphertext, JSON-serialized". A base64 string hides the format header. - Cheap to detect tampering at the boundary. Any non-hex character is rejected before any cryptographic work runs.
The cost is a 2× size factor over the raw binary. If you store millions of
ciphertexts and bytes matter, run hex2bin() before storage and bin2hex()
again on read — the package's encrypt() / decrypt() only ever speak hex
on the public surface.
Yes — hex is URL-safe. For cookies, set the usual Secure, HttpOnly,
SameSite=Strict flags. Ciphertext length is roughly `2 * (header + IV/nonce
- HMAC + payload)`, so be mindful of the 4 KB cookie limit if you put a lot in there.
Because unserialize() on attacker-controlled bytes is the canonical PHP
object-injection vector, and "the bytes are HMAC-verified so the attacker
can't control them" is true only as long as your key never leaks. JSON
cannot instantiate classes — defense in depth.
If your payloads genuinely need PHP's serialization (binary blobs, deeply
custom classes), pass 'serializer' => 'php_serialize' explicitly. The
package always invokes unserialize() with ['allowed_classes' => false]
in that mode, so you still get a meaningful defense.
See the table in 05 — Options Reference.
No. There is no "encrypt without authenticate" mode. Authenticated encryption is the only encryption a modern library should offer; the most common "I just want to hide bytes" use case has stronger guarantees with authentication on than off.
If you have a legitimate need for unauthenticated encryption (e.g. you are
streaming gigabytes and want to authenticate the whole stream at the end),
this package is the wrong tool — use openssl_encrypt() directly.
Not in 2.x. Both handlers operate on the full payload in memory.
For large blobs:
- libsodium has
crypto_secretstream_xchacha20poly1305_*which is the right primitive for streaming AEAD. You can build a custom handler around it — see 04 — Custom Handlers. - For raw OpenSSL streaming,
openssl_encrypt/openssl_decryptoperate on full buffers; you need to useext-openssl's stream filter machinery directly, which is outside the package's scope.
No, that's a feature. OpenSSL uses a fresh random IV per call; Sodium uses a fresh random nonce. If two ciphertexts of the same plaintext were identical, an attacker could deduce that the plaintexts match without ever decrypting either.
If you need deterministic encryption for, say, a database index, the package is the wrong tool — that requires a separate construction (synthetic IV, FF1, etc.) with different security properties.
Two processes using the same handler class and the same options will interoperate. The Sodium key derivation and OpenSSL HKDF are both deterministic on the user key.
You cannot decrypt an OpenSSL ciphertext with the Sodium handler or vice versa — the wire formats are different and (intentionally) the relevant integration test asserts this.
You can't, with the built-in handlers — the cipher and algorithm are not recorded in the wire format. Decryption uses whatever the handler is configured with at decrypt time.
If you want self-describing cipher choice, build a custom handler that embeds a cipher ID in the payload (between the format header and the ciphertext bytes). See the "Versioning Your Own Format" section in 04 — Custom Handlers.
Effectively yes. allowed_classes: false means unserialize() returns
__PHP_Incomplete_Class for any custom class rather than instantiating it
— no constructor runs, no __wakeup/__destruct is triggered.
That said, JSON is categorically safer because no class metadata is even parsed. Use JSON unless you have a concrete reason to use serialize.
The Sodium handler zeroes the derived key (the 32 bytes that go into
crypto_secretbox) before returning, but the user key — the one you passed
into the handler's options — lives in your $options array, which is
your buffer to manage.
If you want the user key wiped, manage that buffer yourself:
$key = read_key_from_vault();
$handler = new \InitPHP\Encryption\Sodium(['key' => $key]);
// $handler now has the key inside its options array; that's the buffer
// you'll need to clear if you want it gone. Either trust the GC or
// hold a reference yourself for sodium_memzero().
$ct = $handler->encrypt($payload);For small files (anything that fits in PHP's memory_limit comfortably) —
yes. encrypt(file_get_contents(...)) / file_put_contents(..., $ct)
works fine, but the entire file is processed as one buffer.
For large files: see "Does the package support streaming encryption?" above.
^8.1 means "8.1 or any later 8.x". When 9.0 comes out, this constraint
prevents the package from auto-installing — that's intentional, since 9.0
will probably have breaking language changes the package needs to verify
against. A future 2.x release will widen the constraint.
CHANGELOG.md at the repository root, in
Keep a Changelog format.
Open a GitHub Discussion in the Q&A category. If the answer turns out to be a doc gap, the question ends up answered here in the next release.