YubiKit PIV JCA guide

Private keys and certificates in the YubiKey PIV application can be accessed and managed through the standard Java Cryptography Architecture (JCA) interfaces through a custom Provider. This guide shows examples for common JCA operations. Read more about JCA in the official documentation.

Please note, that for brevity, error and exception handling code is omitted from the examples. For even more usage examples, the source code of the :testing module is recommended.

Provider installation

The YKPiv PivProvider needs an active PivSession to be able to perform any actions. You can either pass an instance of PivSession directly to the constructor of PivProvider, or you can provide a Callback which will be invoked to supply a PivSession whenever one is required. To obtain a PivSession, the following code can be used:

// device instance is obtained on a successful USB/NFC connection
// see YubiKit documentation
YubikeyDevice device = ... ;

device.requestConnection(SmartCardConnection::class.java) {
    PivSession pivSession = new PivSession(it.value);
    // use pivSession
}

Before calling methods which modify data on the YubiKey, such as key generation or key import, the YubiKey PIV Session must be authenticated with a management key by calling piv.authenticate().

YKPiv provider can be installed into the application/process or used directly.

Installing the YKPiv Provider

Before you can use the YKPiv Provider you will need to make it accessible to the application by adding it to the list of Security Providers.

The YKPiv Provider uses custom PrivateKey classes which aren’t usable by other Providers. To avoid a different Provider from being used with these PrivateKeys, we recommend installing the PivProvider in the first position:

PivProvider pivProvider = new PivProvider(pivSession)
Security.insertProviderAt(pivProvider, 1); // JCA Security providers are indexed from 1

Inserting YKPiv provider in the first position makes it the preferred provider, and calling JCA APIs which don’t specify a provider by name or pointer will return PIV JCA implementations (where applicable).

The Provider uses services from existing other Providers to perform padding and hashing of messages. If additional Signature or Cipher schemes are required for use with YKPiv, you can install additional JCA Providers prior to instantiating the YKPiv PivProvider. For example, to be able to use an algorithm which is provided by the third party Provider Bouncy Castle, you could do the following:

// This example installs an updated version of Bouncy Castle JCA provider
// for this, we first need to remove the original Bouncy Castle provider
Security.removeProvider("BC");
// after the system Bouncy Castle provider has been removed, we install a new instance
Security.addProvider(new BouncyCastle());

// we call the PivProvider constructor after we have installed additional providers
PivProvider pivProvider = new PivProvider(pivSession);
Security.insertProviderAt(pivProvider, 1);

Alternative: Using the Provider directly

To use YKPiv without global installation (avoiding call to Security.insertProviderAt(), the application needs to instantiate the provider class and use it in the JCA getInstance methods. For example:

PivProvider pivProvider = new PivProvider(pivSession)
KeyStore keyStore = KeyStore.getInstance("YKPiv", pivProvider);

Generating new keys

The KeyPairGenerator service is used for generating new keys. Depending on requested key type, use the following code to acquire an instance of the service:

// get instance of RSA KeyPairGenerator
KeyPairGenerator rsaKpg = KeyPairGenerator.getInstance("YKPivRSA");

// get instance of EC KeyPairGenerator
KeyPairGenerator ecKpg = KeyPairGenerator.getInstance("YKPivEC");

The instance then needs to be initialized with PivAlgorithmParameterSpec object which describes the properties of the new key:

PivAlgorithmParameterSpec(
    Slot slot,
    KeyType keyType,
    @Nullable PinPolicy pinPolicy,
    @Nullable TouchPolicy touchPolicy,
    @Nullable char[] pin)
slot

is the PIV slot, one of Slot.AUTHENTICATION, Slot.SIGNATURE, Slot.KEY_MANAGEMENT or Slot.CARD_AUTH

keyType

one of KeyType.RSA1024, KeyType.RSA2048, KeyType.ECCP256, KeyType.ECCP384; must match the KeyPairGenerator type

pinPolicy

defines the pin policy for using the key. PinPolicy.DEFAULT, PinPolicy.NEVER, PinPolicy.ONCE or PinPolicy.ALWAYS

touchPolicy

defines the whether or not a user presence is required (physical touch) when using the key. One of TouchPolicy.DEFAULT, TouchPolicy.NEVER, TouchPolicy.ALWAYS, TouchPolicy.CACHED

pin

the PIV application PIN; valid PIN is required depending on the PinPolicy value and the operation which is planned to be performed with the resulting PrivateKey - signing and decrypting require PIN.

The DEFAULT values of PinPolicy and TouchPolicy depend on the slot value. See YubiKit PIV module JavaDoc and PIV Certificate slots for more details.

Follows an example of generation of an RSA key pair.

KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("YKPivRSA");

rsaGen.initialize(
    new PivAlgorithmParameterSpec(
        Slot.AUTHENTICATION,
        KeyType.RSA1024,
        null, // PinPolicy
        null, // TouchPolicy
        DEFAULT_PIN // PIV PIN
    )
);

KeyPair keyPair = rsaGen.generateKeyPair();

Accessing and importing keys

Private keys can be stored in YKPiv KeyStore with the setEntry method. The slot, pin and touch policies have the same values as when generating new keys. Example snippet:

KeyStore keyStore = KeyStore.getInstance("YKPiv");
keyStore.load(null);

KeyPair keyPair = ...;
X509Certificate cert = ...;

keyStore.setEntry(
    Slot.SIGNATURE,
    new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), new Certificate[]{cert}),
    new PivKeyStoreKeyParameters(PinPolicy.DEFAULT, TouchPolicy.DEFAULT)
);

To get a private key stored in a specific slot of the KeyStore, use getKey method.

KeyStore keyStore = KeyStore.getInstance("YKPiv");
keyStore.load(null);

PrivateKey privateKey = (PrivateKey) keyStore.getKey(Slot.SIGNATURE, DEFAULT_PIN);

Using digital signatures

The YKPiv private keys can be used for digital signatures:

// note: the signature algorithm and key have to be compatible
PrivateKey privateKey = keyPair.getPrivate();
Signature signature = Signature.getInstance("SHA256withECDSA");

byte[] message = "message to sign".getBytes(StandardCharsets.UTF_8);
signature.initSign(privateKey);
signature.update(message);
byte[] messageSignature = signature.sign();

To verify a digital signature, following code can be used:

// note: the signature algorithm and key have to be compatible
PublicKey publicKey = keyPair.getPublic();
Signature signature = Signature.getInstance("SHA256withECDSA");

byte[] message = "message to sign".getBytes(StandardCharsets.UTF_8);
byte[] messageSignature = ...;

signature.initVerify(publicKey);
signature.update(message);
bool success = signature.verify(messageSignature);

Encryption and Decryption

YKPiv keys can be used for encryption and decryption of data. The following example shows how:

KeyPair keyPair = ...;
String cipherAlgorithm = "RSA/ECB/PKCS1Padding"; // or other algorithm
byte[] message = "message to encrypt".getBytes(StandardCharsets.UTF_8);

Cipher cipher = Cipher.getInstance(cipherAlgorithm);
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
byte[] encrypted = cipher.doFinal(message);

cipher = Cipher.getInstance(cipherAlgorithm);
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
byte[] decrypted = cipher.doFinal(encrypted);

// decrypted == message

Key agreement

YKPiv implements a KeyAgreement service. Key agreement is a protocol by which 2 or more parties can establish the same cryptographic keys, without having to exchange any secret information. The following example shows how to use the KeyAgreement instance for two different key pairs (one of them is YKPiv key pair) for getting a common secret.

// generate EC key with the YKPiv provider
KeyPairGenerator pivKpg = KeyPairGenerator.getInstance("YkPivEC");
pivKpg.initialize(
    new PivAlgorithmParameterSpec(Slot.AUTHENTICATION, KeyType.ECCP256, null, null, DEFAULT_PIN));
KeyPair pivKeyPair = pivKpg.generateKeyPair();

// generate EC key with another provider, based on pivKeyPair
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(((ECKey) pivKeyPair.getPublic()).getParams());
KeyPair peerPair = kpg.generateKeyPair();

// this is YKPiv KeyAgreement service
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(pivKeyPair.getPrivate());
ka.doPhase(peerPair.getPublic(), true);
byte[] secret = ka.generateSecret();

ka = KeyAgreement.getInstance("ECDH");
ka.init(peerPair.getPrivate());
ka.doPhase(pivKeyPair.getPublic(), true);
byte[] peerSecret = ka.generateSecret();

// secret == peerSecret