3. Functionality overview

This section provides a high-level overview of the functionality provided by the interface defined in this specification. Refer to the API definition for a detailed description.

Future additions describes features that might be included in future versions of this specification.

Due to the modularity of the interface, almost every part of the library is optional. The only mandatory function is psa_crypto_init().

3.1. Library management

Applications must call psa_crypto_init() to initialize the library before using any other function.

3.2. Key management

Applications always access keys indirectly via an identifier, and can perform operations using a key without accessing the key material. This allows keys to be non-extractable, where an application can use a key but is not permitted to obtain the key material. Non-extractable keys are bound to the device, can be rate-limited and can have their usage restricted by policies.

Each key has a set of attributes that describe the key and the policy for using the key. A psa_key_attributes_t object contains all of the attributes, which is used when creating a key and when querying key attributes.

The key attributes include:

  • A type and size that describe the key material.

  • The key identifier that the application uses to refer to the key.

  • A lifetime that determines when the key material is destroyed, and where it is stored.

  • A policy that determines how the key can be used.

Keys are created using one of the key creation functions:

These output the key identifier, that is used to access the key in all other parts of the API.

All of the key attributes are set when the key is created and cannot be changed without destroying the key first. If the original key permits copying, then the application can specify a different lifetime or restricted policy for the copy of the key.

A call to psa_destroy_key() destroys the key material, and will cause any active operations that are using the key to fail. Therefore an application must not destroy a key while an operation using that key is in progress, unless the application is prepared to handle a failure of the operation.

3.2.1. Key identifiers

Key identifiers are integral values that act as permanent names for persistent keys, or as transient references to volatile keys. Key identifiers are defined by the application for persistent keys, and by the implementation for volatile keys and for built-in keys.

Key identifiers are output from a successful call to one of the key creation functions.

Valid key identifiers must have distinct values within the same application. If the implementation provides caller isolation, then key identifiers are local to each application. That is, the same key identifier in two applications corresponds to two different keys.

See Key identifiers.

3.2.2. Key lifetimes

The lifetime of a key indicates where it is stored and which application and system actions will create and destroy it.

There are two main types of lifetimes: volatile and persistent.

Volatile keys are automatically destroyed when the application instance terminates or on a power reset of the device. Volatile key identifiers are allocated by the implementation when the key is created. Volatile keys can be explicitly destroyed with a call to psa_destroy_key().

Persistent keys are preserved until the application explicitly destroys them or until an implementation-specific device management event occurs, for example, a factory reset. The key identifier for a persistent key is set by the application when creating the key, and remains valid throughout the lifetime of the key, even if the application instance that created the key terminates.

See Key lifetimes.

3.2.3. Key policies

All keys have an associated policy that regulates which operations are permitted on the key. Each key policy is a set of usage flags and a specific algorithm that is permitted with the key. See Key policies.

3.2.4. Recommendations of minimum standards for key management

Most implementations provide the following functions:

  • psa_import_key(). The exceptions are implementations that only give access to a key or keys that are provisioned by proprietary means, and do not allow the main application to use its own cryptographic material.

  • psa_get_key_attributes() and the psa_get_key_xxx() accessor functions. They are easy to implement, and it is difficult to write applications and to diagnose issues without being able to check the metadata.

  • psa_export_public_key(). This function is usually provided if the implementation supports any asymmetric algorithm, since public-key cryptography often requires the delivery of a public key that is associated with a protected private key.

  • psa_export_key(). However, highly constrained implementations that are designed to work only with short-term keys, or only with long-term non-extractable keys, do not need to provide this function.

3.3. Symmetric cryptography

This specification defines interfaces for the following types of symmetric cryptographic operation:

  • Message digests, commonly known as hash functions.

  • Message authentication codes (MAC).

  • Symmetric ciphers.

  • Authenticated encryption with associated data (AEAD).

For each type of symmetric cryptographic operation, the API includes:

  • A pair of single-part functions. For example, compute and verify, or encrypt and decrypt.

  • A series of functions that permit multi-part operations.

3.3.1. Single-part Functions

Single-part functions are APIs that implement the cryptographic operation in a single function call. This is the easiest API to use when all of the inputs and outputs fit into the application memory.

Some use cases involve messages that are too large to be assembled in memory, or require non-default configuration of the algorithm. These use cases require the use of a multi-part operation.

3.3.2. Multi-part operations

Multi-part operations are APIs which split a single cryptographic operation into a sequence of separate steps. This enables fine control over the configuration of the cryptographic operation, and allows the message data to be processed in fragments instead of all at once. For example, the following situations require the use of a multi-part operation:

  • Processing messages that cannot be assembled in memory.

  • Using a deterministic IV for unauthenticated encryption.

  • Providing the IV separately for unauthenticated encryption or decryption.

  • Separating the AEAD authentication tag from the cipher text.

Each multi-part operation defines a specific object type to maintain the state of the operation. These types are implementation-defined. All multi-part operations follow the same pattern of use:

  1. Allocate: Allocate memory for an operation object of the appropriate type. The application can use any allocation strategy: stack, heap, static, etc.

  2. Initialize: Initialize or assign the operation object by one of the following methods:

    • Set it to logical zero. This is automatic for static and global variables. Explicit initialization must use the associated PSA_xxx_INIT macro as the type is implementation-defined.

    • Set it to all-bits zero. This is automatic if the object was allocated with calloc().

    • Assign the value of the associated macro PSA_xxx_INIT.

    • Assign the result of calling the associated function psa_xxx_init().

    The resulting object is now inactive.

    It is an error to initialize an operation object that is in active or error states. This can leak memory or other resources.

  3. Setup: Start a new multi-part operation on an inactive operation object. Each operation object will define one or more setup functions to start a specific operation.

    On success, a setup function will put an operation object into an active state. On failure, the operation object will remain inactive.

  4. Update: Update an active operation object. The update function can provide additional parameters, supply data for processing or generate outputs.

    On success, the operation object remains active. On failure, the operation object will enter an error state.

  5. Finish: To end the operation, call the applicable finishing function. This will take any final inputs, produce any final outputs, and then release any resources associated with the operation.

    On success, the operation object returns to the inactive state. On failure, the operation object will enter an error state.

An operation can be aborted at any stage during its use by calling the associated psa_xxx_abort() function. This will release any resources associated with the operation and return the operation object to the inactive state.

Any error that occurs to an operation while it is in an active state will result in the operation entering an error state. The application must call the associated psa_xxx_abort() function to release the operation resources and return the object to the inactive state.

Once an operation object is returned to the inactive state, it can be reused by calling one of the applicable setup functions again.

If a multi-part operation object is not initialized before use, the behavior is undefined.

If a multi-part operation function determines that the operation object is not in any valid state, it can return PSA_ERROR_CORRUPTION_DETECTED.

If a multi-part operation function is called with an operation object in the wrong state, the function will return PSA_ERROR_BAD_STATE and the operation object will enter the error state.

It is safe to move a multi-part operation object to a different memory location, for example, using a bitwise copy, and then to use the object in the new location. For example, an application can allocate an operation object on the stack and return it, or the operation object can be allocated within memory managed by a garbage collector. However, this does not permit the following behaviors:

  • Moving the object while a function is being called on the object. This is not safe. See also Concurrent calls.

  • Working with both the original and the copied operation objects. This requires cloning the operation, which is only available for hash operations using psa_hash_clone().

Each type of multi-part operation can have multiple active states. Documentation for the specific operation describes the configuration and update functions, and any requirements about their usage and ordering.

3.3.3. Message digests (Hashes)

The single-part hash functions are:

The psa_hash_operation_t multi-part operation allows messages to be processed in fragments:

  1. Initialize the psa_hash_operation_t object to zero, or by assigning the value of the associated macro PSA_HASH_OPERATION_INIT.

  2. Call psa_hash_setup() to specify the required hash algorithm, call psa_hash_clone() to duplicate the state of active psa_hash_operation_t object, or call psa_hash_resume() to restart a hash operation with the output from a previously suspended hash operation.

  3. Call the psa_hash_update() function on successive chunks of the message.

  4. At the end of the message, call the required finishing function:

    • To suspend the hash operation and extract a hash suspend state, call psa_hash_suspend(). The output state can subsequently be used to resume the hash operation.

    • To calculate the digest of a message, call psa_hash_finish().

    • To verify the digest of a message against a reference value, call psa_hash_verify().

To abort the operation or recover from an error, call psa_hash_abort().

3.3.4. Message authentication codes (MACs)

The single-part MAC functions are:

The psa_mac_operation_t multi-part operation allows messages to be processed in fragments:

  1. Initialize the psa_mac_operation_t object to zero, or by assigning the value of the associated macro PSA_MAC_OPERATION_INIT.

  2. Call psa_mac_sign_setup() or psa_mac_verify_setup() to specify the algorithm and key.

  3. Call the psa_mac_update() function on successive chunks of the message.

  4. At the end of the message, call the required finishing function:

To abort the operation or recover from an error, call psa_mac_abort().

3.3.5. Encryption and decryption

Note

The unauthenticated cipher API is provided to implement legacy protocols and for use cases where the data integrity and authenticity is guaranteed by non-cryptographic means. It is recommended that newer protocols use Authenticated encryption (AEAD).

The single-part functions for encrypting or decrypting a message using an unauthenticated symmetric cipher are:

  • psa_cipher_encrypt() to encrypt a message using an unauthenticated symmetric cipher. The encryption function generates a random IV. Use the multi-part API to provide a deterministic IV: this is not secure in general, but can be secure in some conditions that depend on the algorithm.

  • psa_cipher_decrypt() to decrypt a message using an unauthenticated symmetric cipher.

The psa_cipher_operation_t multi-part operation permits alternative initialization parameters and allows messages to be processed in fragments:

  1. Initialize the psa_cipher_operation_t object to zero, or by assigning the value of the associated macro PSA_CIPHER_OPERATION_INIT.

  2. Call psa_cipher_encrypt_setup() or psa_cipher_decrypt_setup() to specify the algorithm and key.

  3. Provide additional parameters:

    • When encrypting data, generate or set an initialization vector (IV), nonce, or similar initial value such as an initial counter value. To generate a random IV, which is recommended in most protocols, call psa_cipher_generate_iv(). To set the IV, call psa_cipher_set_iv().

    • When decrypting, set the IV or nonce. To set the IV, call psa_cipher_set_iv().

  4. Call the psa_cipher_update() function on successive chunks of the message.

  5. Call psa_cipher_finish() to complete the operation and return any final output.

To abort the operation or recover from an error, call psa_cipher_abort().

3.3.6. Authenticated encryption (AEAD)

The single-part AEAD functions are:

These functions follow the interface recommended by An Interface and Algorithms for Authenticated Encryption [RFC5116].

The encryption function requires a nonce to be provided. To generate a random nonce, either call psa_generate_random() or use the AEAD multi-part API.

The psa_aead_operation_t multi-part operation permits alternative initialization parameters and allows messages to be processed in fragments:

  1. Initialize the psa_aead_operation_t object to zero, or by assigning the value of the associated macro PSA_AEAD_OPERATION_INIT.

  2. Call psa_aead_encrypt_setup() or psa_aead_decrypt_setup() to specify the algorithm and key.

  3. Provide additional parameters:

  4. Call psa_aead_update_ad() zero or more times with fragments of the non-encrypted additional data.

  5. Call psa_aead_update() zero or more times with fragments of the plaintext or ciphertext to encrypt or decrypt.

  6. At the end of the message, call the required finishing function:

    • To complete an encryption operation, call psa_aead_finish() to compute and return authentication tag.

    • To complete a decryption operation, call psa_aead_verify() to compute the authentication tag and verify it against a reference value.

To abort the operation or recover from an error, call psa_aead_abort().

Having a multi-part interface to authenticated encryption raises specific issues.

Multi-part authenticated decryption produces partial results that are not authenticated. Applications must not use or expose partial results of authenticated decryption until psa_aead_verify() has returned a success status and must destroy all partial results without revealing them if psa_aead_verify() returns a failure status. Revealing partial results, either directly or indirectly through the application’s behavior, can compromise the confidentiality of all inputs that are encrypted with the same key.

For encryption, some common algorithms cannot be processed in a streaming fashion. For SIV mode, the whole plaintext must be known before the encryption can start; the multi-part AEAD API is not meant to be usable with SIV mode. For CCM mode, the length of the plaintext must be known before the encryption can start; the application can call the function psa_aead_set_lengths() to provide these lengths before providing input.

3.3.7. Key derivation

A key derivation encodes a deterministic method to generate a finite stream of bytes. This data stream is computed by the cryptoprocessor and extracted in chunks. If two key derivation operations are constructed with the same parameters, then they produce the same output.

A key derivation consists of two phases:

  1. Input collection. This is sometimes known as extraction: the operation “extracts” information from the inputs to generate a pseudorandom intermediate secret value.

  2. Output generation. This is sometimes known as expansion: the operation “expands” the intermediate secret value to the desired output length.

The specification defines a multi-part operation API for key derivation that allows for multiple key and non-key outputs to be extracted from a single derivation operation object.

In an implementation with isolation, the intermediate state of the key derivation is not visible to the caller, and if an output of the derivation is a non-exportable key, then this key cannot be recovered outside the isolation boundary.

Applications use the psa_key_derivation_operation_t type to create key derivation operations. The operation object is used as follows:

  1. Initialize a psa_key_derivation_operation_t object to zero or to PSA_KEY_DERIVATION_OPERATION_INIT.

  2. Call psa_key_derivation_setup() to select a key derivation algorithm.

  3. Call the functions psa_key_derivation_input_bytes() and psa_key_derivation_input_key(), or psa_key_derivation_key_agreement() to provide the inputs to the key derivation algorithm. Many key derivation algorithms take multiple inputs; the step parameter to these functions indicates which input is being provided. The documentation for each key derivation algorithm describes the expected inputs for that algorithm and in what order to pass them.

  4. Optionally, call psa_key_derivation_set_capacity() to set a limit on the amount of data that can be output from the key derivation operation.

  5. Call psa_key_derivation_output_key() to create a derived key, or psa_key_derivation_output_bytes() to export the derived data. These functions can be called multiple times to read successive output from the key derivation, until the stream is exhausted when its capacity has been reached.

  6. Key derivation does not finish in the same way as other multi-part operations. Call psa_key_derivation_abort() to release the key derivation operation memory when the object is no longer required.

To recover from an error, call psa_key_derivation_abort() to release the key derivation operation memory.

A key derivation operation cannot be rewound. Once a part of the stream has been output, it cannot be output again. This ensures that the same part of the output will not be used for different purposes.

3.3.8. Example of the symmetric cryptography API

Here is an example of a use case where a master key is used to generate both a message encryption key and an IV for the encryption, and the derived key and IV are then used to encrypt a message.

  1. Derive the message encryption material from the master key.

    1. Initialize a psa_key_derivation_operation_t object to zero or to PSA_KEY_DERIVATION_OPERATION_INIT.

    2. Call psa_key_derivation_setup() with PSA_ALG_HKDF as the algorithm.

    3. Call psa_key_derivation_input_key() with the step PSA_KEY_DERIVATION_INPUT_SECRET and the master key.

    4. Call psa_key_derivation_input_bytes() with the step PSA_KEY_DERIVATION_INPUT_INFO and a public value that uniquely identifies the message.

    5. Populate a psa_key_attributes_t object with the derived message encryption key’s attributes.

    6. Call psa_key_derivation_output_key() to create the derived message key.

    7. Call psa_key_derivation_output_bytes() to generate the derived IV.

    8. Call psa_key_derivation_abort() to release the key derivation operation memory.

  2. Encrypt the message with the derived material.

    1. Initialize a psa_cipher_operation_t object to zero or to PSA_CIPHER_OPERATION_INIT.

    2. Call psa_cipher_encrypt_setup() with the derived message encryption key.

    3. Call psa_cipher_set_iv() using the derived IV retrieved above.

    4. Call psa_cipher_update() one or more times to encrypt the message.

    5. Call psa_cipher_finish() at the end of the message.

  3. Call psa_destroy_key() to clear the generated key.

3.4. Asymmetric cryptography

This specification defines functions for asymmetric cryptography, including asymmetric encryption, asymmetric signature, and two-way key agreement.

3.4.1. Asymmetric encryption

Asymmetric encryption is provided through the functions psa_asymmetric_encrypt() and psa_asymmetric_decrypt().

3.4.2. Hash-and-sign

The signature and verification functions psa_sign_message() and psa_verify_message() take a message as one of their inputs and perform a hash-and-sign algorithm.

The functions psa_sign_hash() and psa_verify_hash() take a message hash as one of their inputs. This is useful for signing pre-computed hashes, or for implementing hash-and-sign using a multi-part hash operation before signing the resulting hash. To determine which hash algorithm to use, call the macro PSA_ALG_GET_HASH() on the corresponding signature algorithm.

Some hash-and-sign algorithms add padding to the message hash before completing the signing operation. The format of the padding that is used depends on the algorithm used to construct the signature.

3.4.3. Key agreement

This specification defines two functions for a Diffie-Hellman-style key agreement where each party combines its own private key with the peer’s public key.

The recommended approach is to use a key derivation operation with the psa_key_derivation_key_agreement() input function, which calculates a shared secret for the key derivation function.

Where an application needs direct access to the shared secret, it can call psa_raw_key_agreement() instead. Note that in general the shared secret is not directly suitable for use as a key because it is biased.

3.5. Randomness and key generation

We strongly recommended that implementations include a random generator, consisting of a cryptographically secure pseudo-random generator (CSPRNG), which is adequately seeded with a cryptographic-quality hardware entropy source, commonly referred to as a true random number generator (TRNG). Constrained implementations can omit the random generation functionality if they do not implement any algorithm that requires randomness internally, and they do not provide a key generation functionality. For example, a special-purpose component for signature verification can omit this.

It is recommended that applications use psa_generate_key(), psa_cipher_generate_iv() or psa_aead_generate_nonce() to generate suitably-formatted random data, as applicable. In addition, the API includes a function psa_generate_random() to generate and extract arbitrary random data.