Basic Usage

In this Basic Usage Guide, you learn how to integrate the SDK in your app, pair with a Security Key, and use it for data encryption.

Initialize

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. It is recommended to 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.

Once initialized, you can register callbacks in SecurityKeyManager that are called when a Security Key is discovered. We add a simple callback that displays a notification:

class SecurityKeyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val securityKeyManager = SecurityKeyManager.getInstance()
        securityKeyManager.init(this)

        // callbacks are kept in a stack, last registered is called first
        securityKeyManager.registerCallback({ securityKeyInteractor->
            Toast.makeText(this, "Security key connected!", Toast.LENGTH_SHORT).show()

            // return true to stop processing further callbacks
            false
        })
    }
}
public class SecurityKeyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        SecurityKeyManager securityKeyManager = SecurityKeyManager.getInstance();
        securityKeyManager.init(this);

        // callbacks are kept in a stack, last registered is called first
        securityKeyManager.registerCallback(securityKeyInteractor -> {
            Toast.makeText(this, "Security key connected!", Toast.LENGTH_SHORT).show();

            // return true to stop processing further callbacks
            return false;
        });
    }
}

The registered callbacks are maintained in a stack, where the last registered callback is called first, and processing stops when any callback returns true. That way an Activity can register its own callback, and consume the event without calling the app-wide callback.

Don’t forget to register your Application subclass in your AndroidManifest.xml:

<application 
   android:name=".SecurityKeyApplication"
   android:label="@string/app_name" 
...>

Pairing Security Keys

Before it can be used for encryption, a Security Key must be set up. To do this, call setupPairedKey on the Security Key’s SecurityKeyInteractor. This method generates the necessary keys, and protects the card with a PIN code.

This procedure requires user interaction, e.g., the user needs to keep NFC-capable Security Keys at the phone’s back. Thus, it should be implemented in an Activity that is part of your app’s first time/registration procedure.

For this example, AndroidPreferencePairedPinProvider is used, which generates a random PIN code and stores it in your app. This way, no other app can communicate with the Security Key after setup, effectively pairing it with your app.

class SetupActivity : Activity(), SecurityKeyManager.SecurityKeyManagerCallback {
    private val securityKeyManager = SecurityKeyManager.getInstance()
    private val pairedSecurityKeyStorage: PairedSecurityKeyStorage
            by lazy { AndroidPreferencePairedSecurityKeyStorage.getInstance(applicationContext) }
    private val pairedPinProvider: PairedPinProvider
            by lazy { AndroidPreferenceSimplePairedPinProvider.getInstance(applicationContext) }

    override fun onResume() {
        super.onResume()
        // register a callback for this activity when a Security Key is discovered
        securityKeyManager.registerCallback(this)
    }

    override fun onPause() {
        super.onPause()
        // make sure to unregister the callback when this Activity is no longer in the foreground
        securityKeyManager.unregisterCallback(this)
    }

    override fun onSecurityKeyDiscovered(keyInteractor: SecurityKeyInteractor): Boolean {
        // keyInteractor operations are blocking, consider executing them in a new thread
        val pairedSecurityKey = keyInteractor.setupPairedKey(pairedPinProvider)
        // Store the pairedSecurityKey. That way we can use it for encryption at any point
        pairedSecurityKeyStorage.addPairedSecurityKey(pairedSecurityKey)

        // return true, to skip other registered callbacks
        return true
    }
}
public class SetupActivity extends Activity implements SecurityKeyManager.SecurityKeyManagerCallback {
    private final SecurityKeyManager securityKeyManager = SecurityKeyManager.getInstance();
    private PairedPinProvider pairedPinProvider;
    private PairedSecurityKeyStorage pairedSecurityKeyStorage;

    @Override
    public void onCreate() {
        super.onCreate();
        pairedPinProvider =
                AndroidPreferenceSimplePairedPinProvider.getInstance(getApplicationContext());
        pairedSecurityKeyStorage =
                AndroidPreferencePairedSecurityKeyStorage.getInstance(getApplicationContext());
    }

    @Override
    public void onResume() {
        super.onResume();
        // register a callback for this activity when a Security Key is discovered
        securityKeyManager.registerCallback(this);
    }

    @Override
    public void onPause() {
        super.onPause();
        // make sure to unregister the callback when this Activity is no longer in the foreground
        securityKeyManager.unregisterCallback(this);
    }

    @Override
    public boolean onSecurityKeyDiscovered(SecurityKeyInteractor keyInteractor) {
        // keyInteractor operations are blocking, consider executing them in a new thread
        PairedSecurityKey pairedSecurityKey = keyInteractor.setupPairedKey(pairedPinProvider);
        // Store the pairedSecurityKey. That way we can use it for encryption at any point
        pairedSecurityKeyStorage.addPairedSecurityKey(pairedSecurityKey);

        // return true, to skip other registered callbacks
        return true;
    }
}

Using Secrets instead of Passwords

Security Keys themselves are not directly used to encrypt full files or databases.

Instead, they decrypt short secrets, that are in turn used to encrypt data.

When you would normally use a user-chosen password/passphrase to encrypt data, you now generate a random secret with the SDK’s ByteSecretGenerator. This secret is used for in-app data encryption, while the secret itself is encrypted to the Security Key. Thus, the physical Security Key replaces user-chosen passwords.

fun generateSecret(): ByteSecret {
    val byteSecretGenerator = ByteSecretGenerator.getInstance()
    val secret = byteSecretGenerator.create256bitSecret()

    // use returned secret for data encryption in-app
    return secret
}
public ByteSecret generateSecret() {
    ByteSecretGenerator byteSecretGenerator = ByteSecretGenerator.getInstance();
    ByteSecret secret = byteSecretGenerator.create256bitSecret();

    // use returned secret for data encryption in-app
    return secret;
}

For a fully working example, consult the guide to encrypt full SQLite databases.

Encrypt to Security Keys

When the Security Key is paired and available as a PairedSecurityKey, the generated secret can be encrypted. Encrypting secrets “to a Security Key” can be done anywhere in your app. Usually, it is done once during setup, as detailed in the encrypted databases guide. For encryption, it is NOT required that Security Key is connected to the device.

fun encryptToSecurityKey(secret: ByteSecret): ByteArray {
    val pairedSecurityKeyStorage =
            AndroidPreferencePairedSecurityKeyStorage.getInstance(getApplicationContext())

    // for simplicity, we assume a single paired security key
    val pairedSecurityKey =
            pairedSecurityKeyStorage.getAllPairedSecurityKeys().firstOrNull()

    val encryptedSecret = PairedEncryptor(pairedSecurityKey).encrypt(secret)

    return encryptedSecret
}
public byte[] encryptToSecurityKey(ByteSecret secret) {
    PairedSecurityKeyStorage pairedSecurityKeyStorage =
            AndroidPreferencePairedSecurityKeyStorage.getInstance(getApplicationContext());

    // for simplicity, we assume a single paired security key
    PairedSecurityKey pairedSecurityKey =
            pairedSecurityKeyStorage.getAllPairedSecurityKeys().iterator().next();

    byte[] encryptedSecret = new PairedEncryptor(pairedSecurityKey).encrypt(secret);

    return encryptedSecret;
}

Storing and Retrieving Encrypted Secrets

The SDK offers utilities for persisting the encryptedSecret. In this basic guide, we store it in an Android preference XML file using the Security Key’s Application Identifier (AID) for later retrieval.

private fun saveEncryptedSecret(pairedSecurityKey: PairedSecurityKey, encryptedSecret: ByteArray) {
    val encryptedSessionStorage =
            AndroidPreferencesEncryptedSessionStorage.getInstance(getApplicationContext())
    encryptedSessionStorage.setEncryptedSessionSecret(
            pairedSecurityKey.getSecurityKeyAid(), encryptedSecret)
}

private fun getEncryptedSecret(pairedSecurityKey:PairedSecurityKey):ByteArray {
    val encryptedSessionStorage =
            AndroidPreferencesEncryptedSessionStorage.getInstance(getApplicationContext())
    return encryptedSessionStorage.getEncryptedSessionSecret(pairedSecurityKey.getSecurityKeyAid())
}
private void saveEncryptedSecret(PairedSecurityKey pairedSecurityKey, byte[] encryptedSecret) {
    EncryptedSessionStorage encryptedSessionStorage =
            AndroidPreferencesEncryptedSessionStorage.getInstance(getApplicationContext());
    encryptedSessionStorage.setEncryptedSessionSecret(
            pairedSecurityKey.getSecurityKeyAid(), encryptedSecret);
}

private byte[] getEncryptedSecret(PairedSecurityKey pairedSecurityKey) {
    EncryptedSessionStorage encryptedSessionStorage =
            AndroidPreferencesEncryptedSessionStorage.getInstance(getApplicationContext());
    return encryptedSessionStorage.getEncryptedSessionSecret(pairedSecurityKey.getSecurityKeyAid());
}

Decryption

Decryption is possible when the user connects the correct Security Key to the device. Similar to the Pairing step, this can be done with the SecurityKeyManagerCallback when the Security Key is discovered. For this, the PairedSecurityKey object is retrieved using the AID of the connected Security Key and the encryptedSecret is decrypted.

class DecryptActivity:Activity(), SecurityKeyManager.SecurityKeyManagerCallback {
    private val securityKeyManager = SecurityKeyManager.getInstance()
    private val pairedSecurityKeyStorage: PairedSecurityKeyStorage
            by lazy { AndroidPreferencePairedSecurityKeyStorage.getInstance(applicationContext) }
    private val pairedPinProvider: PairedPinProvider
            by lazy { AndroidPreferenceSimplePairedPinProvider.getInstance(applicationContext) }
    private val encryptedSessionStorage: EncryptedSessionStorage
            by lazy { AndroidPreferencesEncryptedSessionStorage.getInstance(applicationContext) }

    override fun onResume() {
        super.onResume()
        securityKeyManager.registerCallback(this)
    }

    override fun onPause() {
        super.onPause()
        securityKeyManager.unregisterCallback(this)
    }

    override fun onSecurityKeyDiscovered(keyInteractor:SecurityKeyInteractor):Boolean {
        val encryptedSecret =
                encryptedSessionStorage.getEncryptedSessionSecret(keyInteractor.getSecurityKeyAid())
        decrypt(keyInteractor, encryptedSecret)

        // return true, to skip other registered callbacks
        return true
    }

    fun decrypt(keyInteractor:SecurityKeyInteractor, encryptedSecret:ByteArray) {
        val pairedSecurityKey =
                pairedSecurityKeyStorage.getPairedSecurityKey(keyInteractor.getSecurityKeyAid())

        val decryptor =
                PairedDecryptor(keyInteractor, pairedPinProvider, pairedSecurityKey)
        val secret = decryptor.decryptSessionSecret(encryptedSecret)
    }
}
public class DecryptActivity extends Activity implements SecurityKeyManager.SecurityKeyManagerCallback {
    private final SecurityKeyManager securityKeyManager = SecurityKeyManager.getInstance();
    private PairedPinProvider pairedPinProvider;
    private PairedSecurityKeyStorage pairedSecurityKeyStorage;
    private EncryptedSessionStorage encryptedSessionStorage;

    @Override
    public void onCreate() {
        super.onCreate();
        pairedPinProvider =
                AndroidPreferenceSimplePairedPinProvider.getInstance(getApplicationContext());
        pairedSecurityKeyStorage =
                AndroidPreferencePairedSecurityKeyStorage.getInstance(getApplicationContext());
        encryptedSessionStorage =
                AndroidPreferencesEncryptedSessionStorage.getInstance(getApplicationContext());
    }

    @Override
    public void onResume() {
        super.onResume();
        securityKeyManager.registerCallback(this);
    }

    @Override
    public void onPause() {
        super.onPause();
        securityKeyManager.unregisterCallback(this);
    }

    @Override
    public boolean onSecurityKeyDiscovered(SecurityKeyInteractor keyInteractor) {
        byte[] encryptedSecret = encryptedSessionStorage.getEncryptedSessionSecret(
                keyInteractor.getSecurityKeyAid());
        decrypt(keyInteractor, encryptedSecret);

        // return true, to skip other registered callbacks
        return true;
    }

    public void decrypt(SecurityKeyInteractor keyInteractor, byte[] encryptedSecret) {
        PairedSecurityKey pairedSecurityKey = pairedSecurityKeyStorage.getPairedSecurityKey(
                keyInteractor.getSecurityKeyAid());

        PairedDecryptor decryptor =
                new PairedDecryptor(keyInteractor, pairedPinProvider, pairedSecurityKey);
        ByteSecret secret = decryptor.decryptSessionSecret(encryptedSecret);
    }
}