Search code examples
pythonsmartcardpublic-key-encryptionpkcs#11opensc

Encrypting a data with a public key using pkcs11 module fails


I am using Python's pkcs11 package to access an X.509 certificate stored on my Yubikey 5. Accessing the certificate, public and private keys using pkcs11 Objects work fine as is signing and signature verification. Howerver, for the life of me, I cannot figure out why encrypting with the public key does not work. Here is my code:

import pkcs11
from pkcs11 import Attribute, ObjectClass, KeyType, util
lib = pkcs11.lib('/usr/lib/x86_64-linux-gnu/pkcs11/onepin-opensc-pkcs11.so')
token = lib.get_token(token_label='PIV Card Holder pin (PIV_II)'
session = token.open(user_pin=pin)
# Getting a private and a public key as pkcs11 Object
private = next(session.get_objects({
    Attribute.CLASS: ObjectClass.PRIVATE_KEY, 
}))
public = next(session.get_objects({
    Attribute.CLASS: ObjectClass.PUBLIC_KEY, 
}))
data = 'Hello, world!'
sig = private.sign(data) # Works!
sig_verif = public.verify(data, sig) # Works!
print("Signature is valid? "+str(sig_verif)) # True
# So far, everything above worked fine.
# ----------
# Now, this is the part that does not work
encrypt_data = public.encrypt(data) # Fails!

The last line above fails with pkcs11.exceptions.FunctionNotSupported error. I did some research, and it the explanation I found seems to imply that this function (encrypt) is not supported by the openSC library file (*.so) that I am using. However, I find it hard to believe considering that signature feature works just fine.

Just to make sure that I can use this particular public key outside of session context, I tried the following code using Crypto package:

from Crypto.Cipher import PKCS1_OAEP
public_key = RSA.importKey(public[Attribute.VALUE]) # The content of pkcs11 public key as DER
cipher = PKCS1_OAEP.new(public_key)
encr_data = cipher.encrypt(data) # This works!

So, it seems that using my stand-alone public key allows me to encrypt data. But why can't I do it in the context of a pkcs11 token session?

Then, I tried using the pkcs11 Object decrypt function to try to decrypt the data generated using Crypto module above:

decrypted = private.decrypt(encr_data) # It fails!

The above failed with pkcs11.exceptions.MechanismInvalid error. I tried using different mechanisms, but all of them resulted in the same error. What is interesting -- it seems that pkcs11 object allows me to at least invoke decrypt function without complaining that it is not supported.

One more thing I should mention. I checked my certificate and saw that under Extension -> Certificate Key Usage, it says:

Critical
Signing
Key Encipherment

I read on the difference between key encipherment and data encipherment and learned that key encipherment is used to encrypt a secret (symmetric) key instead of data. Can it be the reason I can't use encrypt function for this token session?

Any feedback would be greatly appreciated!


Solution

  • I'm sorry, but I think this is just a shortcoming of the API. As encryption with the public key doesn't require any security, it doesn't make sense to implement it on the Yubikey. It is much faster to export the public key values and perform the encryption on the host.

    To be fair, Yubikey could have been nice and implement the functionality in software within the Ubikey PKCS#11 library. If you really want to then you could create a new PKCS#11 "wrapper" library that does contain the missing functionality in software; all other commands that Yubikey does implement can be forwarded to the original Yubikey PKCS#11 library.