k2gl / sshsig
Pure-PHP SSHSIG signing and verification, compatible with ssh-keygen -Y
Requires
- php: >=8.1
Requires (Dev)
- k2gl/phpunit-fluent-assertions: ^12
- laravel/pint: ~1.20.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10|^11|^12
Suggests
- ext-openssl: Required to verify rsa-sha2-256/512 and ecdsa-sha2-nistp256/384/521 signatures.
- ext-sodium: Required to verify ssh-ed25519 signatures.
This package is auto-updated.
Last update: 2026-07-07 20:24:08 UTC
README
Sign and verify files with SSH keys in PHP, in the OpenSSH SSHSIG format that ssh-keygen -Y sign/verify produces (the signatures Git uses for commit and tag signing). Verifying checks
the signer against an allowed_signers file; signing emits bytes ssh-keygen -Y verify
accepts. No dependencies.
Fail-closed by design: anything malformed, unsupported, cryptographically invalid, or unauthorized throws — nothing is ever silently treated as verified.
Install
composer require k2gl/sshsig
Requires PHP 8.1+ with ext-sodium (Ed25519) and ext-openssl (RSA and ECDSA) — both are
bundled with most PHP builds.
Usage
Verify a signature against an allowed_signers file
This is the equivalent of ssh-keygen -Y verify -f allowed_signers -I <identity> -n <namespace>.
use K2gl\Sshsig\AllowedSigners; use K2gl\Sshsig\Exception\SshsigException; use K2gl\Sshsig\SshsigVerifier; $verifier = new SshsigVerifier; try { $result = $verifier->verify( message: file_get_contents('release.tar.gz'), armoredSignature: file_get_contents('release.tar.gz.sig'), allowedSigners: AllowedSigners::fromFile('allowed_signers'), identity: 'alice@example.com', namespace: 'file', ); echo "Verified: {$result->publicKey->fingerprint()}\n"; } catch (SshsigException $e) { // not verified — malformed, unsupported, bad signature, or unauthorized signer echo "Rejected: {$e->getMessage()}\n"; }
Check a signature without authorizing the signer
The equivalent of ssh-keygen -Y check-novalidate -n <namespace>: confirm the signature is
structurally sound and cryptographically valid under its own embedded key, then inspect it.
$signature = $verifier->checkNoValidate($message, $armoredSignature, namespace: 'git'); echo $signature->signatureAlgorithm; // e.g. "ssh-ed25519" echo $signature->publicKey->fingerprint(); // "SHA256:…"
Sign a message
Produce an SSHSIG signature (the ssh-keygen -Y sign operation). Provide the signing key —
Ed25519 (a 64-byte libsodium secret key) or an OpenSSL RSA/ECDSA private key:
use K2gl\Sshsig\Ed25519SigningKey; use K2gl\Sshsig\OpensshPrivateKey; use K2gl\Sshsig\OpensslSigningKey; use K2gl\Sshsig\SshsigSigner; // Ed25519 $keypair = sodium_crypto_sign_keypair(); $signer = new SshsigSigner(new Ed25519SigningKey(sodium_crypto_sign_secretkey($keypair))); // …or RSA / ECDSA from an OpenSSL private key (PEM) $signer = new SshsigSigner(OpensslSigningKey::fromPem(file_get_contents('id_rsa'))); // …or an unencrypted ssh-ed25519 key straight off disk, in the format ssh-keygen writes $signer = new SshsigSigner(OpensshPrivateKey::fromFile('/home/you/.ssh/id_ed25519')); $armored = $signer->sign($message, namespace: 'file'); // sha512 by default file_put_contents('message.sig', $armored); // the resulting signature verifies with `ssh-keygen -Y verify` and with this library
$signer->publicKey() returns the matching SshPublicKey (e.g. to build an allowed_signers
line). The default hash is sha512; pass hashAlgorithm: 'sha256' to switch.
Inspect or build allowed_signers in memory
$allowed = AllowedSigners::fromString(<<<TXT # comments and blank lines are ignored alice@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA… *@example.com namespaces="git,file" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA… TXT);
Design
- Fail-closed. Every failure path throws a
K2gl\Sshsig\Exception\SshsigException(InvalidSignatureException,UnsupportedAlgorithmException,SignatureVerificationFailed,SignerNotAllowedException). A returned value always means verified. - Spec-accurate. Implements OpenSSH
PROTOCOL.sshsig: theSSHSIGmagic preamble, version 1, theto-be-signedblob (namespace + reserved + hash algorithm + message hash), and the armor. - Algorithms.
ssh-ed25519,rsa-sha2-256,rsa-sha2-512,ecdsa-sha2-nistp256/384/521;sha256andsha512message hashing. The legacy SHA-1ssh-rsasignature algorithm is refused. allowed_signers. Principals pattern-lists,namespaces=,valid-after/valid-beforevalidity windows, andcert-authorityentries (parsed and recorded; certificate-chain verification is not yet performed).- Signing.
SshsigSignerover a pluggableSigningKey(Ed25519 via ext-sodium; RSA and ECDSA via ext-openssl), producing armor byte-compatible withssh-keygen -Y verify.OpensshPrivateKey::fromFile()/::fromString()load an unencryptedssh-ed25519key straight from theopenssh-key-v1containerssh-keygenwrites by default; encrypted containers and RSA/ECDSA containers are refused (fail-closed) rather than partially read. - Zero dependencies. Pure PHP over
ext-sodiumandext-openssl; nophpseclib. - Verified against OpenSSH. The test suite verifies real
ssh-keygen -Y signoutput for every supported algorithm, plus tampered, wrong-namespace, unauthorized, and malformed cases.
License
MIT. See LICENSE.