phpseclib/phpseclib2_compat
A compatibility layer that lets phpseclib 2.0 code run unchanged on phpseclib 3.0, providing namespaced shims for legacy classes and methods so existing projects can upgrade the underlying library without rewriting cryptographic code.
Swap the requirement, don't add to it. The compat package provides the phpseclib\ namespace, so it cannot coexist with phpseclib/phpseclib:^2.0.
composer remove phpseclib/phpseclib
composer require phpseclib/phpseclib2_compat
This pulls in phpseclib/phpseclib:^3.0 transitively. Confirm you didn't end up with both v2 and the compat layer:
composer why phpseclib/phpseclib
composer show | grep phpseclib
Look at the source tree first, not the docs. The README is thin. The authoritative "what is supported" list is vendor/phpseclib/phpseclib2_compat/src/. Anything under phpseclib\Crypt\, phpseclib\Net\, phpseclib\File\, phpseclib\Math\ that exists there is a thin subclass of the v3 equivalent; anything missing (e.g. Net\SSH1, ciphers/hashes removed in v3) simply won't autoload.
First use case: zero-change legacy calls. Existing code like:
use phpseclib\Crypt\RSA;
use phpseclib\Net\SFTP;
$rsa = new RSA();
$rsa->loadKey($pem);
$sftp = new SFTP('host');
should run unmodified. Write one smoke test per class you actually use (key load/sign/verify, SFTP put/get, SSH exec) before trusting the shim in production.
Strangler-fig migration, file by file. Because every compat class is a subclass of its phpseclib3\ counterpart, both namespaces load fine in the same process and can even be passed the same values. Adopt a rule: whenever you touch a file, rewrite its imports to phpseclib3\. The compat layer shrinks to zero over time.
New code goes straight to v3. Never write new use phpseclib\... statements. Enforce it in CI so the shim doesn't become permanent:
! grep -rn "use phpseclib\\\\" app/ src/
Wrap the surface you use. Don't let phpseclib\Crypt\RSA leak across the codebase. Put signing/verification/SFTP behind one small service class; the compat dependency then becomes a one-file deletion when you're ready.
final class Signer {
public function sign(string $data): string {
$rsa = new \phpseclib\Crypt\RSA();
$rsa->loadKey($this->key);
return $rsa->sign($data);
}
}
Mixed-version interop is fine but be deliberate. Pass phpseclib\Math\BigInteger where a v3 method expects phpseclib3\Math\BigInteger — it's the same class underneath. Don't assume that's true for objects coming from a separate dependency that still pins phpseclib v2.
CI matrix. Pin the compat package and test against the exact v3 version it pulls; a composer update can change behavior without a version bump you'd notice.
It is not a full polyfill. Removed v3 features are simply absent: SSH1, several legacy ciphers (RC2, etc.), and some v2-only helpers. If a v2 class or method is gone, you get a Class not found / Call to undefined method, not a graceful deprecation. Grep your codebase for phpseclib\ usages and cross-check each against src/ before rollout.
Exception types shift. v3 throws specific phpseclib3\Exception\* classes where v2 often threw generic \RuntimeException/\Exception. Catch \Throwable (or catch both namespaces' types) rather than relying on v2 catch blocks.
Return-shape differences leak through. v3 changed some signatures — e.g. RSA::getPublicKey() returns an object in v3, a string in v2; setPassword() is superseded by withPassword() immutability. Compat shims cover many cases but not all; when a value "looks wrong," compare the v3 method's real signature in vendor/phpseclib/phpseclib/phpseclib3/.
Debugging. v2 idioms like $ssh->getLog() may not exist. Use v3's PSR-3 logger support (inject a LoggerInterface) or setLogger(); getLastError() behavior also differs on Net\* classes. For key/cipher problems, reduce to a v3 minimal repro — the bug is almost always in v3 semantics, not the shim.
Version pinning matters. The compat package tracks phpseclib 3 releases; treat its version as a proxy for the v3 version you're really running. Pin both phpseclib/phpseclib2_compat and phpseclib/phpseclib conservatively in composer.json.
Extension points. Subclassing phpseclib\Crypt\RSA works (it's a normal class), but don't build on it — that ties you to the shim. Wrap instead.
Treat it as scaffolding with an expiry date. Add a ticket to remove phpseclib2_compat once your grep returns nothing; otherwise you carry an indirection layer and its partial-parity surprises indefinitely.
How can I help you explore Laravel packages today?