Cryptnox Python Communication Library

Warning: This is a beta release of the software. It is released for development purposes. Use at your own risk.

A Python3 library to use the Cryptnox smartcard applet. It provides high level functions to send instructions with the Cryptnox and to manage its lifecycle. The core module is CryptnoxPy which provides a Connection class to establish a channel of communication that can be used to initialize a card instance through the factory method.

To buy NFC enabled cards that are supported by this library go to: https://www.cryptnox.com/

License

The library is available under dual licensing. You can use the library under the conditions of GNU LESSER GENERAL PUBLIC LICENSE 3.0+ or contact us to ask about commercial licensing.

Documentation

API documentation can be found in HTML format in the docs folder It is generated using Sphynx from the code and can be generated in other formats too.

Installation and requirements

Requires :

  • Python 3.6-3.9

  • PCSCd on Linux

Ubuntu / Debian

(sudo) apt-get install python3-pip python3-setuptools pcscd

Fedora / CentOS / RHEL

yum install python3-pip python3-setuptools pcsc-lite-ccid

On some Linux, starts PCSCd service

(sudo) systemctl start pcscd
(sudo) systemctl enable pcscd

Installation of this library

Make sure that you installed the required packages for your system. See the “Requires” section above.

Install with pip:

pip install cryptnoxpy

Install from source:

Download and run in its directory:

pip install .

or:

pip install git+ssh://git@github.com/Cryptnox-Software/cryptnoxpy.git

This might require sudo on some systems.

Remove:

pip uninstall cryptnoxpy

Installation issues

If the Linux system doesn't have Python 3.6, 3.7, 3.8 nor 3.9, install Python 3.7 with the following recipe (Debian like):

sudo apt-get install -y make build-essential libssl-dev zlib1g-dev swig libpcsclite-dev
sudo apt-get install -y libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm git
sudo apt-get install -y libncurses5-dev libncursesw5-dev xz-utils tk-dev pcscd opensc
wget https://www.python.org/ftp/python/3.7.8/Python-3.7.8.tgz
tar xf Python-3.7.8.tgz
cd Python-3.7.8
./configure --enable-optimizations
make -j8 build_all
sudo make -j8 altinstall

sudo pip3.7 install git+ssh://git@gitlab.com/cryptnox-phase2/cryptnoxpy.git

# or (if issue about agent forwarding with sudo) :

cd ~
git clone git@gitlab.com:cryptnox-phase2/cryptnoxpy.git
cd cryptnoxpy
sudo pip3.7 install .

In case of pyscard can't be installed automatically with pip:

  1. Try to pip3 install with sudo or root: sudo pip install .

  2. If still a failure, install the following packages: Needed if pyscard can't be installed from package manager sudo apt install python3-dev swig libpcsclite-dev then retry sudo pip install ..

If you use contactless readers on Linux, the RFID modules need to be disabled :

sudo rmmod pn533_usb
sudo rmmod pn533
sudo rm -r /lib/modules/*/kernel/drivers/nfc/pn533

Update issues

In case you just want to update the package, with old pip version on some Linux, it is better to remove and reinstall the package:

sudo pip uninstall cryptnoxpy
sudo pip install .

Library use

To get the card a connection has to be established with the reader’s index. The connection can then be passed to the factory that will initialize an object for the card in the reader from the correct class for the card type and version.

import cryptnoxpy

try:
    connection = cryptnoxpy.Connection(0)
except cryptnoxpy.ReaderException:
    print("Reader not found on index")
else:
    try:
        card = cryptnoxpy.factory.get_card(connection)
    except cryptnoxpy.CryptnoxException as error:
        # There is an issue with loading the card
        # CryptnoxException is the base exception class for module
        print(error)
    else:
        # Card is loaded and can be used
        print(f"Card serial number: {card.serial_number}")

The factory will:

  • connect to the card

  • select the applet

  • read the applet parameters

  • select class to handle the card

The card contains basic information:

  • card.serial_number : Integer : Card/applet instance Unique ID

  • card.applet_version : 3 integers list : Applet version (ex. 1.2.2)

Initialization and pairing

Right after the installation, the applet is not initialized, and the user needs to send some parameters to use the card. The initialization can be executed once. Any change of the base parameters requires a full applet reinstallation (except PIN/PUK change).

After the initialization, the card and the PC must share a common secret to be used as authenticated secure channel. This secret is required any time further, to communicate with the card (using a secure channel). The registration of this common secret is done during the init phase.

The init parameters required are :

  • Name (up to 20 chars string)

  • Email (up to 60 chars string)

  • PIN (9 digits string)

  • PUK (15 digits string)

  • optional : the first Paring Secret (32 bytes bytearray)

pairing_key = card.init(name, email, pin, puk, pairing_secret)

The returned data is the first PairingKey (32 bytes byte-array) and its index (0) : 0x00 + ParingKeySlot0

During the initialization phase, until the user public key for authentication registration is allowed, the set_pairing_key command is also allowed. Then set_pairing_key needs the applet to have the signature unlocked.

After getting the pairing_key, the user needs to store it in a safe place. In the case the client would communicate with several cards, the user needs to associate the pairing_key with the instance serial number of the card, so that the user client can keep track of multiple cards, and use the right one with the right card. The pairing_key must be saved in a file to reconnect the next time to this card. It should be saved with the serial number of card in order to associate this card with this key.

A common hardcoded PairingKey can be used.

After this init phase, the secure channel must be used with all communications with the card. A secure channel is an encrypted and 2-ways authenticated link layer with the card using standards APDU messages. Many applet commands require a secure channel.

PIN

The PIN chosen during the initialization needs to be provided after each card reset, and a secure channel is opened.

To test a PIN string, simply use:

card.verify_pin(pin)

Seed administration

The applet manages a 256 bits master secret called the “seed”. This is the BIP32 Master Seed, and can be externally computed from a mnemonic to a binary seed using BIP39. The key pairs used for ECDSA are then computationally derived from this seed using BIP32 derivation scheme.

Seed generation

The seed can be generated in the card using the random number generator in the java chip system (AIS 20 class DRG.3). Doing this way, the seed secret never escapes the card protection.

The method to generate a new seed key is:

card.generate_seed(pin)

The card can also randomly generate BIP39 mnemonics words list. But in this case, the query answer is only output and not used internally by the card. It is administrator responsibility to get a mnemonic using the GENERATE MNEMONIC command and then eventually compute the corresponding seed, which can be uploaded in the card using RECOVER KEY command. We don’t recommend doing so, this is very insecure, as the seed is exposed in clear and full in the user’s system.

Recovery

The Cryptnox applet can load binary seed.

The seed is loaded in the card using this method:

card.load_seed(seed, pin)

Seed is 32 bytes.

Once this seed is loaded in the card using the load_seed method, this card now behaves like were (or the one) it was backup. Be aware that key derivation paths are not backup, and must be identical to retrieve the same key pairs. See derivation and key system just below for more details.

For more details about the recovery, see load_seed operation in the API documentation.

Derivation and keys system

The card applet is fully compliant with BIP32, except the maximum depth of derivation from the master key is 8 levels. It can be turned on for the card to return extended public keys for use in applications requiring it.

The card stores the present key pair (and its parent), used for signature. This can be changed using the derive method, and also during a signature command, giving a relative path (from the present key pair), or in an absolute path (from the master key pair). See derive method in the API documentation.

Any derivation aborts any opened signing sessions and resets the authentications for signature. The generated key is used for all subsequent sign sessions.

The ability to start derivation from the parent keys allows to more efficiently switch between children of the same key. Note however that only the immediate parent of the current key is cached so one cannot use this to go back in the keys hierarchy.

For ease of use, the user can derive from the root master node key pair (absolute path) at each card startup, or even before each signature. This takes a couple of seconds. So this is better to store intermediate public keys hash and check the status to observe the current key pair in use. This off-card complex key management is not needed if the signatures volume is below one thousand per day.

See derive and sign methods in the API documentation.

EC Signature

The derivation of the key pair node can be also possible using the signature command (relative or absolute).

The card applet can sign any 256 bits hash provided, using ECDSA with 256k1 EC parameters. Most of the blockchain system used SHA2-256 to hash the message, but this card applet is agnostic from this point, since the signature is performed on a hash provided by the user. Note that this hash needs to be confirmed by the users beforehand, when they provide their EC384 signature of this hash.

The code to sign with the EC current key node is:

signature = card.sign(data_hash, cryptnoxpy.Derivation.CURRENT_KEY)

data_hash is a byte-array containing the EC hash to sign using ECDSA ecp256k1:

The signature a byte array, encoded as an ASN1 DER sequence of two INTEGER values, r and s.

See the sign method in the API documentation for more information.

Card

class cryptnoxpy.card.base.Base(connection: cryptnoxpy.connection.Connection, data: Optional[List[int]] = None, debug: bool = False)

Object that contains information about the card that is in the reader.

Parameters
  • connection (Connection) – Connection to use for card initialization

  • debug (bool) – Show debug information to the user.

Variables
  • applet_version (List[int]) – Version of the applet on the card.

  • serial_number (int) – Serial number of card.

  • session_public_key (str) – Public key of the session.

  • initialized (bool) – The card has been initialized with secrets.

Raises

CardTypeException – The card in the reader is not a Cryptnox card

property alive: bool
Returns

The connection to the card is established and the card hasn’t been changed

Return type

bool

abstract change_pairing_key(index: int, pairing_key: bytes, puk: str = '') None

Set the pairing key of the card

Parameters
  • index (int) – Index of the pairing key

  • pairing_key (bytes) – Pairing key to set for the card

  • puk (str) – PUK code of the card

Raises
abstract change_pin(new_pin: str) None

Change the current pin code of the card to a new pin code.

The method will set the given pin code as the pin code of the card. For it to work the card first must be opened with the current pin code.

Requires
  • PIN code or challenge-response validated

Parameters

new_pin (str) – The desired PIN code to be set for the card (4-9 digits).

abstract change_puk(current_puk: str, new_puk: str) None

Change the current pin code of the card to a new pin code.

The method will set the given pin code as the pin code of the card. For it to work the card first must be opened with the current pin code.

Parameters
  • current_puk (str) – The current PUK code of the card

  • new_puk (str) – The desired PUK code to be set for the card

check_init() None

Check if the initialization has been done on the card.

It can be useful to check if the card is initialized before doing anything else, like asking for pin code from the user.

Raises

InitializationException – The card is not initialized

abstract derive(key_type: cryptnoxpy.enums.KeyType = KeyType.K1, path: str = '')

Derive key on path and make it the current key in the card

Requires
  • PIN code or challenge-response validated

  • Seed must exist

Parameters
  • key_type (KeyType) – Key type to do derive on

  • path (str) – Path on which to do derivation

abstract dual_seed_load(data: bytes, pin: str = '') None

Load public key and signature from the other card into the card to generate same seed.

Requires
  • PIN code or challenge-response validated

Parameters
  • pin (str) – PIN code of card if it was opened with a PIN check

  • data (bytes) – Public key and signature of public key from the other card

abstract dual_seed_public_key(pin: str = '') bytes

Get the public key from the card for dual initialization of the cards

Requires
  • PIN code or challenge-response validated

Parameters

pin (str) – PIN code of card if it was opened with a PIN check

Returns

Public key and signature that can be sent into the other card

Return type

bytes

Raises

DataException – The received data is invalid

abstract property extended_public_key: bool
Returns

Extended public key turned on

Return type

bool

abstract generate_random_number(size: int) bytes

Generate random number on the car and return it.

Parameters

size (int) – Output data size in bytes (between 16 and 64, mod 4)

Returns

Random number generated by the chip

Return type

bytes

Raises

DataValidationException – size in not a number between 16 and 64 or is not divisible by 4

abstract generate_seed(pin: str = '') bytes

Generate a seed directly on the card.

Requires
  • PIN code or challenge-response validated

Parameters

pin (str, optional) – PIN code of the card. Can be empty if card is opened with challenge-response validation

Returns

Primary node “m” UID (hash of public key)

Return type

bytes

Raises
abstract get_public_key(derivation: cryptnoxpy.enums.Derivation, key_type: cryptnoxpy.enums.KeyType = KeyType.K1, path: str = '', compressed: bool = True) str

Get the public key from the card.

Requires
  • PIN code or challenge-response validated, except for PIN-less path

  • Seed must exist

Parameters
  • derivation (Derivation) – Derivation to use.

  • key_type (KeyType) – Key type to use

  • path (str) –

  • compressed (bool) – The returned value is in compressed format.

Returns

The public key for the given path in hexadecimal string format

Return type

str

Raises
abstract history(index: int = 0) NamedTuple

Get history of hashes the card has signed regardless of any parameters given to sign

Requires
  • PIN code or challenge-response validated

Parameters

index (int) – Index of entry in history

Returns

Return entry containing signing_counter, representing index of sign call, and hashed_data, the data that was signed

Return type

NamedTuple

property info: Dict[str, Any]

Get relevant information about the card.

Returns

Dictionary containing information for the card

Return type

Dict[str, Any]

abstract init(name: str, email: str, pin: str, puk: str, pairing_secret: bytes) bytes

Initialize the Cryptnox card.

Initialize the Cryptnox card with the owners name and email address. Set the PIN and PUK codes for authenticating with the card to be able to use it.

Parameters
  • name (str) – Name of the card owner

  • email (str) – Email of the card owner

  • pin (str) – PIN code that will be used to open the card

  • puk (str) – PUK code that will be used to open the card

  • pairing_secret (bytes) – Pairing secret to use with the card

Returns

Pairing secret

Return type

bytes

Raises

InitializationException – There was an issue with initialization

abstract property initialized: bool
Returns

Whether the card is initialized

Return type

bool

abstract load_seed(seed: bytes, pin: str = '') None

Load the given seed into the Cryptnox card.

Requires
  • PIN code or challenge-response validated

Parameters
  • seed (bytes) – Seed to initialize the card with

  • pin (str, optional) – PIN code of the card. Can be empty if card is opened with challenge-response validation

Raises

KeyGenerationException – Data is not correct

property open: bool
Returns

Whether the user has authenticated using the PIN code or challenge-response validation

Return type

bool

abstract property pin_authentication: bool
Returns

Whether the PIN code can be used for authentication

Return type

bool

abstract property pin_rule: str

Human readable PIN code rule

Returns

Human readable PIN code rule

Return type

str

abstract property pinless_enabled: bool
Returns

Return whether the card has a pinless path

Return type

bool

abstract property puk_rule: str

Human readable PUK code rule

Returns

Human readable PUK code rule

Return type

str

abstract reset(puk: str) None

Reset the card and return it to factory settings.

Parameters

puk – PUK code associated with the card

abstract property seed_source: cryptnoxpy.enums.SeedSource
Returns

How the seed was generated

Return type

SeedSource

abstract property select_apdu: List[int]
Returns

Value to add to select command to select the applet on the card

Return type

List[int]

abstract set_extended_public_key(status: bool, puk: str) None

Turn on/off extended public key output.

Requires
  • Seed must be loaded

Parameters
  • status (bool) – Status of PIN authentication

  • puk (str) – PUK code associated with the card

Raises
abstract set_pin_authentication(status: bool, puk: str) None

Turn on/off authentication with the PIN code. Other methods can still be used.

Parameters
  • status (bool) – Status of PIN authentication

  • puk (str) – PUK code associated with the card

Raises
abstract set_pinless_path(path: bytes, puk: str) None

Enable working with the card without a PIN on path.

Parameters
  • path (bytes) – Path to be available without a PIN code

  • puk (str) – PUK code of the card

Raises
abstract sign(data: bytes, derivation: cryptnoxpy.enums.Derivation, key_type: cryptnoxpy.enums.KeyType = KeyType.K1, path: str = '', pin: str = '', filter_eos: bool = False) bytes

Sign the message using given derivation.

Requires
  • PIN code provided, authenticate with user key by signing same message or PIN-less path used

  • Seed must be loaded

Parameters
  • data (bytes) – Data to sign

  • derivation (Derivation) – Derivation to use.

  • key_type (KeyType, optional) – Key type to use. Defaults to K1

  • path (str, optional) – Path of the key. If empty use main key

  • pin (str, optional) – PIN code of the card

  • filter_eos (str, optional) – Filter signature so it is valid for EOS network, might take longer. Defaults to False

Returns

The signature generated by the card in DER common format.

Return type

bytes

Raises

DataException – Invalid data received during signature

abstract property signing_counter: int
Returns

Counter of how many times the card has been used to sign

Return type

int

abstract property type: int
Returns

Card type

Return type

int

unblock_pin(puk: str, new_pin: str) None

Verifies the user using the PUK code and sets a new PIN code on the card.

Method should be used when the user has forgotten this/hers PIN code. By entering the PUK code the user verifies his/hers identity and can set the new PIN code on the card. Can be used only if the card is locked.

Requires
  • User PIN must be locked

  • PIN code authentication must be enabled

Parameters
  • puk (str) – PUK code for verification of the user, before changing the PIN code.

  • new_pin (str) – The desired PIN code to be set for the card (4-9 digits).

Raises
abstract property user_data: bytes
Returns

Read user data that was written into the card.

Return type

bytes

abstract user_key_add(slot: cryptnoxpy.enums.SlotIndex, data_info: str, public_key: bytes, puk_code: str, cred_id: bytes = b'') None

Add user public key into the card for user authentication

Parameters
  • slot (int) – Slot to write the public key to 1 - EC256R1 2 - RSA key, 2048 bits, public exponent must be 65537 3 - FIDO key

  • data_info (bytes) – 64 bytes of user data

  • public_key (bytes) – Public key of the secure element to be used for authentication

  • puk_code (str) – PUK code of the card

  • cred_id (bytes, optional) – Cred id. Used for FIDO2 authentication

Raises

DataValidationException – Invalid input data

abstract user_key_challenge_response_nonce() bytes

Get 32 bytes random value from the card that is used to open the card with a user key

Take nonce value from the card. Sign it with a third party application, like TPM. Send the signature back into the card using user_key_challenge_response_open()

Returns

32 bytes random value used as nonce

Return type

bytes

abstract user_key_challenge_response_open(slot: cryptnoxpy.enums.SlotIndex, signature: bytes) bool

Send the nonce signature to the card to open it for operations, like it was opened by a PIN code

Parameters
  • slot (SlotIndex) – Slot to use to open the card

  • signature (bytes) – Signature generated by a third party like TPM.

Returns

Whether the challenge response authentication succeeded

Return type

bool

Raises

DataValidationException – invalid input data

abstract user_key_delete(slot: cryptnoxpy.enums.SlotIndex, puk_code: str) None

Delete the user key from slot and free up for insertion

Parameters
  • slot (SlotIndex) – Slot to remove the key from

  • puk_code (str) – PUK code of the card

Raises

DataValidationException – Invalid input data

abstract user_key_enabled(slot_index: cryptnoxpy.enums.SlotIndex) bool

Check if user key is present in given slot

Parameters

slot_index (SlotIndex) – Slot index to check for

Returns

Whether the user key for slot is present

Return type

bool

abstract user_key_info(slot: cryptnoxpy.enums.SlotIndex) Tuple[str, str]

Get the description and public key of the user key

Requires
  • PIN code or challenge-response validated

Parameters

slot (SlotIndex) – Index of slot for which to fetch the description

Returns

Description and public key in slot

Return type

tuple[str, str]

abstract user_key_signature_open(slot: cryptnoxpy.enums.SlotIndex, message: bytes, signature: bytes) bool

Used for opening the card to sign the given message

Parameters
  • slot (SlotIndex) – Slot to use to open the card

  • message (bytes) – Message that will be sent to sign operation

  • signature (bytes) – Signature generated by a third party, like TPM, on the same message

Returns

Whether the challenge response authentication succeeded

Return type

bool

Raises

DataValidationException – invalid input data

abstract property valid_key: bool

Check if the card has a valid key

Returns

Whether the card has a valid key.

Return type

bool

abstract static valid_pin(pin: str, pin_name: str = 'pin') str

Check if provided pin is valid

Parameters
  • pin (str) – The pin to check if valid

  • pin_name (str) – Value used in DataValidationException for pin name

Return str

Provided pin in str format if valid

Raises

DataValidationException – Provided pin is not valid

abstract static valid_puk(puk: str, puk_name: str = 'puk') str

Check if provided puk is valid

Parameters
  • puk (str) – The puk to check if valid

  • puk_name (str, optional) – Value used in DataValidationException for puk name. Defaults to: puk

Return str

Provided puk in str format if valid

Raises

DataValidationException – Provided puk is not valid

abstract verify_pin(pin: str) None

Check PIN code and open the card for operations that are protected.

The method is sending the PIN code to the card to open it for other operations. If there is an issue an exception will be raised.

Parameters

pin (str) – PIN code to check against the card.

Raises

Connection

class cryptnoxpy.connection.Connection(index: int = 0, debug: bool = False)

Connection to the reader.

Sends and receives messages from the card using the reader.

Parameters
  • index (int) – Index of the reader to initialize the connection with

  • debug (bool) – Show debug information during requests

Variables

self.card (Card) – Information about the card.

send_apdu(apdu: List[int]) Tuple[List[int], int, int]

Send data to the card in plain format

Parameters

apdu (int) – list of the APDU header

Return bytes

Result of the query that was sent to the card

Return type

bytes

Raises

ConnectionException – Issue in the connection

send_encrypted(apdu: List[int], data: bytes, receive_long: bool = False) bytes

Send data to the card in encrypted format

Parameters
  • apdu (int) – list of the APDU header

  • data – bytes of the data payload (in clear, will be encrypted)

  • receive_long (bool) –

Return bytes

Result of the query that was sent to the card

Return type

bytes

Raises

CryptnoxException – General exceptions