The missing key management bridge between (Open)SSH and Java worlds. SSHProvider
(a Java Security Provider) makes SSH keys, certificates and signatures first-class citizens in the Java ecosystem:
- access keys and certificates in any compatible agent (as set in
$SSH_AUTH_SOCK
) via JavaKeyStore
- use hardware-backed agent keys with standard Java
Signature
like normal keys - verify and create
SSHSIG
and raw SSH signature formats, also with SSH certificates (including webauthn signatures) - use existing OpenSSH
allowed_signers
trust anchoring files (as used by Git, for example)
Reproducible 175K .jar with pure Java and zero 3rd party dependencies.
Tip
Sign easily with local hardware keys on remote machines with SSH agent forwarding ❤️
There is also a longer Tutorial (WIP).
Generate standard signatures with a key in hardware via $SSH_AUTH_SOCK
Note
Keys are reported by their public key fingerprint (same as shown by ssh-add -l
) but can also be addressed by full public key/certificate string (as shown by ssh-add -L
or available in a .pub
file).
import com.hardssh.provider.SSHProvider;
Security.addProvider(new SSHProvider()); // Add the provider
KeyStore ks = KeyStore.getInstance("SSH"); // access $SSH_AUTH_SOCK
ks.load(null, null); // a password would send an "unlock" command to the agent (ssh-add -X)
// same output as "ssh-add -l"
for (String alias : Collections.list(ks.aliases())) {
System.out.println(alias); // SHA256:5DmYCoIkCgEoOnbx3K+UXLhHVh8pX8GXgf7IS8i9QPo
}
String alias = "SHA256:5DmYCoIkCgEoOnbx3K+UXLhHVh8pX8GXgf7IS8i9QPo";
PrivateKey key = (PrivateKey) ks.getKey(alias);
Signature sig = Signature.getInstance("SHA256withECDSA");
sig.initSign(key);
// Continue as usual
Important
If a key has associated SSH certificate(s), a KeyStore.PrivateKeyEntry with key + certificate will be available with the hash of the certificate (unlike OpenSSH/ssh-add
, which reports certificates with the fingerprint of the key). SSHProvider
supports multiple certificates per key. Certificates without an available private key will not be available via KeyStore
.
After signing a file ($ echo 'Hello, World!' > /tmp/helloworld.txt
) with OpenSSH:
$ ssh-keygen -t ed25519 -f /tmp/id_ed25519 -N '' -C 'Test key'
$ ssh-keygen -Y sign -n file -f /tmp/id_ed25519 /tmp/helloworld.txt
Verify it with Java (or vice-versa):
PublicKey pub = SSHIdentity.fromPath(Paths.get("/tmp/id_ed25519.pub"));
byte[] signature = SSHSIG.fromArmored(Paths.get("/tmp/helloworld.txt.sig"));
Signature sig = Signature.getInstance("SSHSIG");
sig.setParameter(new SSHSIGVerificationSpec("file"));
sig.initVerify(pub);
sig.update(Files.readAllBytes(Paths.get("/tmp/helloworld.txt")))
Assert.assertTrue(sig.verify(signature));
Note
Supported key types are Ed25519, ECDSA, RSA and FIDO with Ed25519 and ECDSA. DSA keys are actively rejected.
- KeyStore
SSH
- Signature
Ed25519
(sign, agent only) - Signature
SHA256withECDSA
(sign, agent only) - Signature
SHA384withECDSA
(sign, agent only) - Signature
SHA512withECDSA
(sign, agent only) - Signature
SHA256withRSA
(sign, agent only) - Signature
SHA512withRSA
(sign, agent only) - Signature
ssh-ed25519
(sign, verify) - Signature
ssh-ecdsa-nistp256
(sign, verify) - Signature
ssh-ecdsa-nistp384
(sign, verify) - Signature
ssh-ecdsa-nistp521
(sign, verify) - Signature
rsa-sha2-256
(sign, verify) - Signature
rsa-sha2-512
(sign, verify) - Signature
[email protected]
(sign (agent only), verify) - Signature
[email protected]
(sign (agent only), verify) - Signature
[email protected]
(verify) - Signature
SSHSIG
(sign, verify) - CertificateFactory
SSH
- KeyFactory
SSH
- OpenSSHPublicKeySpec (to/from OpenSSH string format (
~/.ssh/*.pub
))
Important
Requires Java 21+ and currently targeting unices only. Source publish pending on final package re-structuring and cleanups. com.hardssh.provider.SSHProvider
is here to stay.
With Maven:
<repositories>
<repository>
<id>javacard-pro</id>
<url>https://mvn.javacard.pro/maven/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.martinpaljak</groupId>
<artifactId>sshprovider</artifactId>
<version>25.02.21</version>
</dependency>
</dependencies>
Tip
SSHProvider
does not implement or handle plaintext private keys in any form by design.
If you really have to work with your plaintext keys from ~/.ssh
, add them to the standard OpenSSH ssh-agent
.
SSHProvider
is regularly tested with and works seamlessly with:
- Secretive
- yubikey-agent
- ssh-tpm-agent
- OpenSSH ssh-agent
- YAUSA
- anything that follows the specification
SSHProvider
was not created in vacuum, there is other software available.
Nothing from the existing open source universe fitted the approach and requirements for building HardSSH
, thus SSHProvider
was born as a minimal and fresh cleanroom implementation for modern Java. The Security Provider builds upon the SSH agent code in YAUSA, but is published and packaged separately, due to its generic and re-usable nature.
It reflects on quarter century of pain experience agony with smart cards, secure elements and hardware cryptography. Please don't ask about PKCS#11 (which you can use via OpenSSH ssh-agent
).
- http://www.jcraft.com/jsch/
- https://github.com/apache/mina-sshd
- https://github.com/eclipse-jgit/jgit/tree/master/org.eclipse.jgit.ssh.apache.agent
- https://github.com/eclipse-jgit/jgit/tree/master/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh
- https://github.com/profhenry/sshsig
- https://github.com/sshtools/maverick-synergy
- https://github.com/Tesco/ssh-certificates
- https://github.com/rkeene/ssh-agent-pkcs11