SSH Authentication with Jsch

Keypad provided by the Hardware Security SDK to access the smartcard over NFC.
Animation showing how the smartcard should be held against the smartphone for SSH authentication.

In this guide, you’ll learn how to integrate the Hardware Security SDK in your app to implement SSH authentication with security keys and smartcards. The Hardware Security SDK will automatically…

  1. retrieve the publickey from the security key and use it for the SSH connection.
    (If an OpenSSH certificate has been stored on the security key, this will be used instead.)
  2. cryptographically sign the SSH challenge using the security key.
Fork sample code on Github: Get Sample on Github
Try on Google Play: Get it on Google Play

Add the SDK to Your Project

To get a username and password for our Maven repository, please contact us for a license.

Add this to your build.gradle:

repositories {
    maven {
        credentials {
            username 'xxx'
            password 'xxx'
        url ""

dependencies {
    // For use with OpenPGP Cards
    implementation 'de.cotech:hwsecurity-openpgp:4.4.0'
    // Or with PIV cards
    //implementation 'de.cotech:hwsecurity-piv:4.4.0'

    // Jsch bridge
    implementation 'de.cotech:hwsecurity-ssh:4.4.0'
    // Jsch library
    implementation 'com.jcraft:jsch:0.1.55'

Initialize the Hardware Security SDK

To use the SDK’s functionality in your app, you need to initialize the SecurityKeyManager first. This is the central class of the SDK, which dispatches incoming NFC and USB connections. Perform this initialization in the onCreate method of your Application subclass. This ensures Security Keys are reliably dispatched by your app while in the foreground.

We start by creating a new class which extends as follows:

class MyCustomApplication : Application() {
    override fun onCreate() {

        val securityKeyManager = SecurityKeyManager.getInstance()
        val config = SecurityKeyManagerConfig.Builder()
        securityKeyManager.init(this, config)
public class MyCustomApplication extends Application {
    public void onCreate() {

        SecurityKeyManager securityKeyManager = SecurityKeyManager.getInstance();
        SecurityKeyManagerConfig config = new SecurityKeyManagerConfig.Builder()
        securityKeyManager.init(this, config);

Then, register your MyCustomApplication in your AndroidManifest.xml:


Show Security Key Dialog and Receive Callbacks

In this guide, we use the OpenPgpSecurityKeyDialogFragment to show a neat kepad for PIN input. It also handles security key errors, for example when a wrong PIN is entered.

private fun showSecurityKeyDialog() {
    val options = SecurityKeyDialogOptions.builder()
        //.setPinLength(4) // security keys with a fixed PIN and PUK length improve the UX
        .setShowReset(true) // show button to reset/unblock of PIN using the PUK

    val securityKeyDialogFragment = OpenPgpSecurityKeyDialogFragment.newInstance(options)
    // if you like to use PIV:
    //val securityKeyDialogFragment = PivSecurityKeyDialogFragment.newInstance(options)

Implement SecurityKeyDialogCallback<OpenPgpSecurityKey> (or, if you are using PIV cards: SecurityKeyDialogCallback<PivSecurityKey>) in your Activity and override onSecurityKeyDialogDiscovered to receive callbacks from the securityKeyDialogFragment when a security key is discovered over NFC (or Security Keys over USB):

override fun onSecurityKeyDialogDiscovered(
    dialogInterface: SecurityKeyDialogInterface,
    securityKey: OpenPgpSecurityKey,
    pinProvider: PinProvider?
) {
    val loginName = "cotech"
    val loginHost = ""

    connectToSsh(loginName, loginHost, dialogInterface, securityKey, pinProvider!!)
IOExceptions thrown inside onSecurityKeyDialogDiscovered are catched by the securityKeyDialogFragment and a proper error UI is shown to the user. Alternatively, SecurityKeyDialogInterface.postError() can be used.

Threading and Exception Handling

The actual SSH connection is deferred to a new thread so that network operations are not blocking the main thread. To properly handle Exceptions, deferred.await() is used. IOExceptions are posted to the securityKeyDialogFragment using the SecurityKeyDialogInterface.postError() for user feedback.

private fun connectToSsh(
    loginName: String,
    loginHost: String,
    dialogInterface: SecurityKeyDialogInterface,
    securityKey: OpenPgpSecurityKey,
    pinProvider: PinProvider
) = GlobalScope.launch(Dispatchers.Main) {
    val deferred = GlobalScope.async(Dispatchers.IO) {
        val securityKeyAuthenticator = securityKey.createSecurityKeyAuthenticator(pinProvider)
        val securityKeySshAuthenticator = SecurityKeySshAuthenticator.fromPublicKey(securityKeyAuthenticator)
        // NOTE: If you are using OpenSSH certificates:
        //val securityKeySshAuthenticator = SecurityKeySshAuthenticator.fromOpenSshCertificate(securityKeyAuthenticator)

        val securityKeyIdentity = SecurityKeyJschIdentity(loginName, securityKeySshAuthenticator)

        jschConnection(dialogInterface, loginHost, securityKeyIdentity)

    try {
    } catch (e: JSchException) {
        Log.e("SSH", "Exception", e)
        // wrap in IOException and show
        // unwrap IOExceptions thrown in SshIdentity and handle them in securityKeyDialogFragment
        e.cause?.let { dialogInterface.postError(it as IOException?) }
    } catch (e: IOException) {
    } catch (e: Exception) {
        Log.e("SSH", "Exception", e)

SSH Identity

Using the SecurityKeySshAuthenticator, we can create a Jsch SSH Identity class that delegates the signature operation to the security key.

Special care needs to be taken due to Jsch’s exception handling. Since Jsch swallows other exceptions happening during the authentication, we need to wrap IOExceptions happening during securityKeySshAuthenticator.authenticateSshChallenge() into a JschException. These are catched during the SSH connection and unwrapped to properly delegate them to the securityKeyDialogFragment, as shown previously.

class SecurityKeyJschIdentity(
    private val loginName: String,
    private val securityKeySshAuthenticator: SecurityKeySshAuthenticator
) : Identity {
    override fun getName() = loginName
    override fun getAlgName() = securityKeySshAuthenticator.sshPublicKeyAlgorithmName
    override fun getPublicKeyBlob() = securityKeySshAuthenticator.sshPublicKeyBlob
    override fun getSignature(data: ByteArray?): ByteArray {
        // wrap IOExceptions thrown by authenticateSshChallenge() into JschExceptions to handle them later in securityKeyDialogFragment
        try {
            return securityKeySshAuthenticator.authenticateSshChallenge(data)
        } catch (e: IOException) {
            throw JSchException("IOException", e)

    override fun clear() {}
    override fun isEncrypted() = false
    override fun setPassphrase(passphrase: ByteArray?) = true
    override fun decrypt() = true

Jsch SSH Connection

The actual SSH connection can be done according to the Jsch documentation. After successfull authentication, securityKeyDialogFragment must be dismissed manually.

private fun jschConnection(
    dialogInterface: SecurityKeyDialogInterface,
    loginHost: String,
    securityKeyIdentity: SecurityKeyJschIdentity
) {
    val jsch = JSch()
    // disable strict host key checking for testing purposes
    JSch.setConfig("StrictHostKeyChecking", "no")
    jsch.addIdentity(securityKeyIdentity, null)
    val sshSession = jsch.getSession(, loginHost)

    val baos = ByteArrayOutputStream()
    baos.write("Server Output: ".toByteArray(), 0, 15)


    val channel = sshSession.openChannel("shell")
    channel.outputStream = baos

    // close dialog after successful authentication
    Log.d("SSH", "SSH connection successful!")
    Log.d("SSH", "")
    Log.d("SSH", baos.toString())

Prevent Re-Creation of Activity with USB Security Keys

Besides the functionalities used by our SDK, some Security Keys register themselves as USB keyboards to be able to insert One Time Passwords (OTP) when touching the golden disc. Thus, when inserting a Security Key into the USB port, Android recognizes a new keyboard and re-creates the current activity.

To prevent this, add keyboard|keyboardHidden to the activity’s configChanges in your AndroidManifest.xml:

... > Test Server

To test your implementation, we provide a SSH test server at When logging in with the username cotech, it simply returns the used SSH algorithm and public key. If an OpenSSH certificate is used, it is returned instead.

Public Key

Try it with your OpenSSH client first:

user@laptop:~$ ssh

Hardware Security SDK - SSH Sample

You are using a publickey (not certificate) with type:

Your full key is:


Connection to closed.


When enforcing the use of OpenSSH certificates, the certificate is returned:

user@laptop:~$ ssh -i user -o

Hardware Security SDK - SSH Sample

You are using a certificate with type:

Your full certificate is:                                                                             


Connection to closed. 


That’s all! If you have any questions, don’t hesitate to contact us: