Introduction

The ISARA Radiate Quantum-safe Toolkit gives you the cryptographic building blocks to create applications that will resist attacks by quantum computers.

For more information about ISARA and our quantum-safe solutions, please visit www.isara.com.

This Developer’s Guide tells you how to do things with the toolkit, such as:

  • use NewHopeDH lattice based key agreement

  • use the Hierarchical Signature Scheme (HSS) scheme for digital signatures

  • provide your own implementations of generic algorithms, such as hashes

The toolkit’s API is designed around generic algorithms; you create an instance of the algorithm you want (say, SHA2-256) and then use it with the generic algorithm APIs (iqr_HashBegin(), iqr_HashUpdate(), and iqr_HashEnd()).

Things like signature schemes and key agreement protocols are specialized enough to require customized APIs. We’ve designed these to follow the generic algorithm APIs as much as possible.

We recommend that you have an advanced understanding of cryptography and the theory of security protocols.

The Developer’s Guide covers the following topics:

  • Getting Started — Foundational information for using the toolkit.

  • FIPS 140-2 Certification — Using the FIPS 140-2 edition of the toolkit.

  • Hashing — How to use the toolkit’s SHA2, and SHA3 implementations, and how to provide your own implementations.

  • Random Numbers — How to generate pseudorandom bytes.

  • Message Authentication Codes — How to use the HMAC and Poly1305 MACs.

  • Key Derivation Functions — How to use the KDFs.

  • Symmetric Encryption — How to use the ChaCha20 symmetric-key encryption algorithm.

  • Digital Signatures — How to use the Dilithium, HSS, SPHINCS+, Rainbow, XMSS, and XMSSMT digital signature schemes.

  • Key Agreement — How to use the FrodoDH, NewHopeDH, and SIDH key agreement schemes.

  • Key Encapsulation — How to use the Classic McEliece KEM, FrodoKEM, Kyber, NTRUPrime, and SIKE key encapsulation mechanisms.

  • Technical Info — Detailed information about the compiler options we’ve used, how to maximize code stripping when using the static toolkit, and other details that might be helpful.

  • Building On Windows — How to use the toolkit on the Windows platform.

Classical and Quantum Security

Cryptographic algorithms are often described as providing "x bits of security" when used with a certain set of parameters. These parameters can be tuned to provide additional security, usually at the cost of additional CPU and/or memory usage.

These x bits of security apply to an adversary equipped with classical, non- quantum computers, and will appear as "x bits of classical security" in the toolkit’s documentation. The strength of an algorithm against attacks by quantum computers will be written as "x bits of quantum security."

Some classes of algorithm, such as encryption schemes based on the discrete log problem, are thought to be easily breakable by adversaries equipped with quantum computers.

Some algorithms, such as cryptographic hashes and symmetric encryption schemes, are not known or believed to be efficiently breakable by a quantum adversary. Their quantum security strength is generally considered to be half of their classical strength. For example, SHA2-256 provides 256 bit classical security, and 128 bit quantum security.

Packaging

The toolkit archive contains the following files and directories:

  • README.html — Information about the toolkit package.

  • SECURITY.html — Information about reporting security issues, and ISARA’s PGP public key.

  • doc — API documentation and this Developer’s Guide document.

  • include — Toolkit headers.

  • lib_<cpu> — Toolkit static and shared libraries tuned for various CPU architectures (for example, lib_skylake would have binaries optimized for the "skylake" architecture).

  • samples — Sample programs demonstrating how to use the toolkit.

Cryptographic signatures for the installation archives are distributed with the archives. If you don’t have access to the signatures, please contact support@isara.com.

Getting Help

The latest version of the toolkit documentation is available on the ISARA website. You can also request support via email.

Reporting Security Issues

The ISARA team takes security bugs in the toolkit seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.

To report a security issue, email security@isara.com and include the word "SECURITY" in the subject line.

The ISARA team will send a response indicating the next steps in handling your report. After the initial reply to your report, the team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.

ISARA’s PGP Key

If you need to validate signatures on files coming from ISARA, or you want to encrypt a file you’re sending to us, you can grab our PGP public key from https://www.isara.com/public_key.asc.

It has the following digests:

Table 1. ISARA PGP Key Digests
Algorithm Digest

MD5

9bd6531fee9e6be983a22f0da3415029

SHA1

0fac805775c29bb23a769e4c28e36c1c01ec9325

SHA2-256

10edbd26d3e470f48ca24e404fa78f45f494ecf492fc6f76ea77a5eb433328d0

SHA2-512

726b71a09803801135b02ea4bb87d8bbef9bd9c0f0926ebab6aa7cd2cdaa1375 140f94d572c32ce1b16a10b771e444397882cb7c425e96cf95e22498e7c30161

Samples

The samples directory has a number of sub-directories, each with a self-contained program inside demonstrating how to use the toolkit for a specific purpose:

  • aead_chacha20_poly1305 — Encrypt/decrypt using ChaCha20/Poly1305 for authenticated encryption.

  • chacha20 — Encrypt/decrypt using ChaCha20.

  • classicmceliece — Generate keys, encapsulate and decapsulate data using the Classic McEliece KEM.

  • dilithium — Generate Dilithium keys, sign a file’s data with a Dilithium key, and verify a Dilithium signature.

  • frododh — Agree on a shared secret using FrodoDH, a relative of the NewHope scheme.

  • frodokem — Generate keys, encapsulate and decapsulate data using FrodoKEM.

  • hash — Hash a file’s data using SHA2-256, SHA2-384, SHA2-512, SHA3-256, or SHA3-512.

  • hmac — Get the HMAC tag for a file’s data using any of the available hash algorithms.

  • hss — Generate keys, sign a file’s data, detach signatures from a private key state, and verify a signature using the HSS algorithm.

  • kdf_concatenation, kdf_pbkdf2, and kdf_rfc5869 — Derive a key (some pseudorandom data) using the specified key derivation function.

  • kyber — Generate keys, encapsulate and decapsulate data using Kyber.

  • newhopedh — Agree on a shared secret using the NewHopeDH scheme.

  • ntruprime — Generate keys, encapsulate and decapsulate data using NTRUPrime.

  • poly1305 — Get the Poly1305 tag for a file’s data.

  • rainbow — Generate keys, sign a file’s data, and verify a signature using the Rainbow algorithm.

  • rng — Generate pseudorandom bytes using HMAC-DRBG or SHAKE.

  • sidh — Agree on a shared secret using Supersingular Isogeny Diffie-Hellman.

  • sike — Generate keys, encapsulate and decapsulate data using Supersingular Isogeny Key Encapsulation.

  • sphincs — Generate keys, sign a file’s data, and verify a signature using the SPHINCS+ algorithm.

  • version — Display the library’s version information.

  • xmss — Generate keys, sign a file’s data, detach signatures from a private key state, and verify a signature using the XMSS algorithm.

  • xmssmt — Generate keys, sign a file’s data, detach signatures from a private key’s state, and verify a signature using the XMSSMT algorithm.

To compile the samples, you will need:

  • C99 compliant compiler; recent versions of clang or gcc are preferred

  • cmake 3.7 or newer

  • GNU make 3.8 or newer (or some other build tool supported by your version of cmake)

Note
Don’t build the samples on macOS using gcc 8, they will crash before main() due to a problem with -fstack-protector-all. Use clang to produce Mac binaries.

Set your IQR_TOOLKIT_ROOT environment variable to the directory where you unpacked the installation archive. The build will expect to find include and lib inside the IQR_TOOLKIT_ROOT directory.

Compile all of the samples with:

  1. cd to the samples directory.

  2. mkdir build

  3. cd to the build directory.

  4. cmake ..

  5. make

Getting Started

This section gives you an overview of things you’ll need to know to effectively use the toolkit.

Objects in the toolkit follow a standard cycle:

  1. Create object.

  2. Do things with the object.

  3. Destroy the object.

Some things, like hashes, use a generic API; all hashes use the same Begin(), Update(), End() functions.

Even algorithms that don’t use a generic API will have functions that indicate exactly what they do. For example, HSS (a digital signature scheme) has Sign() and Verify(), while Kyber (a key encapsulation scheme) has Encapsulate() and Decapsulate().

The Context

To create objects and algorithms in the toolkit, you need a Context. The iqr_Context object keeps track of the registered hashing algorithms as well as additional internal information.

One Context object can be used to create as many other toolkit objects as necessary. Use multiple Context objects if you need to provide several different hash implementations (for example).

The iqr_Context object and its APIs are defined in the iqr_context.h header file. To create one:

#include "iqr_context.h"
...
iqr_Context *context = NULL;
iqr_retval result = iqr_CreateContext(&context);
if (result != IQR_OK) {
    // Examine "result" to see what error has occurred.
}

The toolkit’s Create() functions all take a Context as their first argument.

To properly destroy the context:

#include "iqr_context.h"
...
// Create an iqr_Context object.
// Do crypto.
...
iqr_retval result = iqr_DestroyContext(&context);
if (result != IQR_OK) {
    // Examine "result" to see what error has occurred.
}

Standardized Return Values

All of the APIs in the toolkit return an iqr_retval value, as defined in the iqr_retval.h header.

On success, functions return IQR_OK; if an error occurs, the return value will tell you why the error happened.

Use the iqr_StrError() function (also in iqr_retval.h) to convert an iqr_retval value into an English string. Be sure to write your own error messages if you need to support localization.

Note
Depending on your compiler flags, you must check the return values for toolkit functions.

Registering Hashes

Many algorithms require hash implementations. The Context supplies these, but doesn’t provide a default implementation. This reduces the size of the code in your application by only linking in the algorithms you actually need to use. It also lets you provide your own implementations for additional speed or security.

See the iqr_HashRegisterCallbacks() function in iqr_hash.h.

The toolkit provides implementations of the quantum-safe SHA2 and SHA3 hash algorithms:

  • IQR_HASH_DEFAULT_SHA2_256 - C implementation of SHA2-256

  • IQR_HASH_DEFAULT_SHA2_384 - C implementation of SHA2-384

  • IQR_HASH_DEFAULT_SHA2_512 - C implementation of SHA2-512

  • IQR_HASH_DEFAULT_SHA3_256 - C implementation of SHA3-256

  • IQR_HASH_DEFAULT_SHA3_512 - C implementation of SHA3-512

For example, to use the toolkit implementation of SHA2-256:

#include "iqr_context.h"
#include "iqr_hash.h"
...
// Create an iqr_Context object.
...
iqr_retval result = iqr_HashRegisterCallbacks(context, IQR_HASHALGO_SHA2_256,
    &IQR_HASH_DEFAULT_SHA2_256);
if (result != IQR_OK) {
    // Examine "result" to see what error has occurred.
}

After this call to iqr_HashRegisterCallbacks() any toolkit APIs that need a SHA2-256 object can create one using the toolkit’s built-in SHA2-256 implementation.

Registering a Watchdog

Some algorithms, such as HSS key generation, can take a long time to complete. In some cases, you might need to signal a supervising process (or a user) that the code is still working.

In iqr_watchdog.h the toolkit provides a function for registering your own callback function. This function is called periodically by long-running algorithms (see the API documentation for information about which algorithms call the watchdog callback).

Your watchdog function should look something like this:

#include "iqr_watchdog.h"
...
iqr_retval my_watchdog_function(void *watchdog_data)
{
    // Notify a supervisor process or provide user feedback.
    ...

    if (some_error_condition) {
        // Return an appropriate error value to abandon the long-running
        // algorithm.
        return IQR_ENOMEM;
    }

    return IQR_OK;
}
...

Then use iqr_WatchdogRegisterCallback() to register it:

#include "iqr_context.h"
#include "iqr_watchdog.h"
...
// Create an iqr_Context object.
...
iqr_retval result = iqr_WatchdogRegisterCallback(context, my_watchdog_function,
    my_data);
if (result != IQR_OK) {
    // Examine "result" to see what error has occurred.
}

After this call to iqr_WatchdogRegisterCallback() any toolkit algorithm using this context that invokes the watchdog will call my_watchdog_function(), passing it the pointer my_data.

If the watchdog function returns an error (anything other than IQR_OK), the calling function will exit without finishing its task.

To remove your watchdog function, call iqr_WatchdogRegisterCallback() with NULL as the function pointer:

#include "iqr_context.h"
#include "iqr_watchdog.h"
...
// Create an iqr_Context object.
...
iqr_retval result = iqr_WatchdogRegisterCallback(context, NULL, NULL);
if (result != IQR_OK) {
    // Examine "result" to see what error has occurred.
}

The data pointer is ignored when the function is set to NULL.

Thread Safety

Objects in the toolkit are self-contained. Any data required to use the object is controlled by the toolkit.

Access to the object is not managed by the toolkit. To use toolkit objects in a multi-threaded environment you’ll have to use the operating system’s mutexes or critical section guards carefully. Your use of the toolkit is as thread-safe as you make it so the toolkit can be as fast as possible in a single-threaded environment.

There are no global variables in the toolkit. There are no data structures with shared, static data.

Anything that can be accessed from several threads needs to be locked in a way that makes sense for the object. For example, with a hash, you’d need to wrap the entire iqr_HashBegin()/iqr_HashUpdate()/iqr_HashEnd() sequence (or the call to iqr_HashMessage()). For an RNG you might only need to lock iqr_RNGGetBytes() calls (but that’d get more complex if you’re using one that needs to be reseeded regularly).

Data Hygiene

Parameter objects (iqr_*Params) in the toolkit never contain any cryptographic material.

All objects in the toolkit have their buffers wiped with 0x00 bytes prior to being deallocated.

Buffers (seed data, hash inputs, etc.) passed to toolkit functions are never modified by the toolkit unless explicitly stated in API documentation.

Version Information

The iqr_version.h header gives you access to the library’s version numbers, plus some identifying information about the build.

  • IQR_VERSION_MAJOR and IQR_VERSION_MINOR provide the major and minor versions for the toolkit. They can be combined as major.minor to produce a version string.

  • IQR_VERSION_STRING is a verbose version string for the toolkit.

The iqr_VersionGetBuildTarget() function returns a string representation of the target OS, target CPU architecture, and compiler used to build the toolkit, separated by / characters if you need to parse it into separate fields. Give this information to ISARA’s support team if you discover something that looks like a bug.

Note
The version sample is an easy way to get this information for ISARA’s support team.

The iqr_VersionGetBuildHash() function returns a string representation of the library’s build hash and build time stamp, separated by a / character if you need to parse it into separate fields. Give this information to ISARA’s support team if you discover something that looks like a bug.

There’s also an iqr_VersionCheck() function that you can use to ensure that your headers match the version of the library you’re using:

#include "iqr_version.h"
...
iqr_retval result = iqr_VersionCheck(IQR_VERSION_MAJOR, IQR_VERSION_MINOR);
if (result == IQR_OK) {
    // Your headers and library are the same version.
    ...
} else {
    // Your headers and library come from different versions. Your code is
    // not likely to compile and/or link properly.
    ...
}

What Next?

These sections can be read in any order, depending on what you need to do:

  • If you’re using the FIPS 140-2 edition, see FIPS 140-2.

  • To hash some data into a digest, see Hashing.

  • To generate random bytes, see Random Numbers.

  • To create message authentication codes from data, see MACs.

  • To derive secret key data, see KDFs.

  • To perform symmetric encryption using a shared secret, see Symmetric Encryption.

  • To digitally sign and verify messages, see Digital Signatures.

  • To securely agree on a shared secret, see Key Agreement.

  • To generate a shared secret and encapsulate it for secure transport, see Key Encapsulation.

  • For detailed technical information about how the library was compiled, see Technical Info.

  • For information about building toolkit projects with Visual Studio, see Building on Windows.

FIPS 140-2 Certification

Note
If you’re not using the FIPS 140-2 certified version of the toolkit, you can skip this section. Check your toolkit distribution files, or use iqr_FIPS140GetState() to find out if you’ve got the FIPS 140-2 version.

The toolkit is being certified for Level 1 of FIPS 140-2 Security Requirements for Cryptographic Modules. If your toolkit has been compiled with FIPS 140-2 support, two modes are available:

  • IQR_FIPS140_DISABLED

  • IQR_FIPS140_ENABLED

FIPS 140-2 mode applies to these algorithms:

  • SHA2-256, SHA2-384, SHA2-512, SHA3-256, and SHA3-512

  • SHAKE128, and SHAKE256

  • HMAC with SHA2-256, SHA2-384, SHA2-512, SHA3-256, or SHA3-512

  • HMAC-DRBG with SHA2-256, SHA2-384, SHA2-512, SHA3-256, or SHA3-512

The IQR_FIPS140_ENABLED mode adds some additional behaviour to the toolkit:

  • On library load, a code integrity test and various operational tests are performed. These tests are also performed when switching from IQR_FIPS140_DISABLED to IQR_FIPS140_ENABLED. If any of these tests fail, only IQR_FIPS140_DISABLED mode is available.

Installing With FIPS 140-2 Mode

Follow the appropriate section to correctly install the FIPS 140-2 version of the ISARA toolkit.

Installing on Linux

  1. Copy libiqr_toolkit_fips140.so to a directory in your application’s PATH, LD_LIBRARY_PATH, etc.

  2. The libiqr_toolkit_fips140.so file must be installed with a file mode of 0755 (-rwxr-xr-x).

  3. Install the included HMAC key file (libiqr_toolkit_fips140.key) with a file mode of 0444 (-r—​r—​r--).

  4. Install the included HMAC tag file (libiqr_toolkit_fips140.tag) with a file mode of 0444 (-r—​r—​r--).

  5. For your application, set the ISARA_FIPS140_KEY environment variable to the full path to the HMAC key file.

  6. For your application, set the ISARA_FIPS140_TAG environment variable to the full path to the HMAC tag file.

If the toolkit, its key, and its tag aren’t installed with the correct permissions, or the environment variables aren’t set correctly, your application will not function in FIPS 140-2 mode.

Installing on macOS

  1. Copy libiqr_toolkit_fips140.dylib to a directory in your application’s PATH, DYLD_LIBRARY_PATH, etc.

  2. The libiqr_toolkit_fips140.dylib file must be installed with a file mode of 0755 (-rwxr-xr-x).

  3. Install the included HMAC key file (libiqr_toolkit_fips140.key) with a file mode of 0444 (-r—​r—​r--).

  4. Install the included HMAC tag file (libiqr_toolkit_fips140.tag) with a file mode of 0444 (-r—​r—​r--).

  5. For your application, set the ISARA_FIPS140_KEY environment variable to the full path to the HMAC key file.

  6. For your application, set the ISARA_FIPS140_TAG environment variable to the full path to the HMAC tag file.

If the toolkit, its key, and its tag aren’t installed with the correct permissions, or the environment variables aren’t set correctly, your application will not function in FIPS 140-2 mode.

Installing on Windows

  1. Copy libiqr_toolkit_fips140.dll to a directory in your application’s PATH.

  2. Install the included HMAC key file (libiqr_toolkit_fips140.key) and be sure it’s Read Only for everyone.

  3. Install the included HMAC tag file (libiqr_toolkit_fips140.tag) and be sure it’s Read Only for everyone.

  4. For your application, set the ISARA_FIPS140_KEY environment variable to the full path to the HMAC key file.

  5. For your application, set the ISARA_FIPS140_TAG environment variable to the full path to the HMAC tag file.

If the toolkit, its key, and its tag aren’t installed with the correct permissions, or the environment variables aren’t set correctly, your application will not function in FIPS 140-2 mode.

Compiling With FIPS 140-2 Mode

To compile code with a FIPS 140-2 version of the toolkit, you must:

  • Link against a FIPS 140-2 version of the library. These are provided as shared libraries only, as static libraries cannot automatically perform the required self tests on application start up.

Using FIPS 140-2 Mode

Use iqr_FIPS140GetState() and iqr_FIPS140SetState() (from iqr_fips140.h) to get and set the FIPS 140-2 mode of the library.

The default state for a FIPS 140-2 version of the library is IQR_FIPS140_ENABLED.

On-Demand Tests

If you need to perform on-demand known-answer tests for Approved algorithms, use the appropriate function from the iqr_fips140.h header:

  • iqr_FIPS140ConcatenationKDFTest() - Known-answer test for Concatenation KDF with Approved hash.

  • iqr_FIPS140HashTest() - Known-answer test for Approved hash.

  • iqr_FIPS140HMACTest() - Known-answer test for HMAC with Approved hash.

  • iqr_FIPS140HMACDRBGTest() - Known-answer test for HMAC-DRBG with Approved hash.

  • iqr_FIPS140RNGTest() - Known-answer tests for Approved RNGs (that is, HMAC-DRBG with all Approved hashes and SHAKE).

  • iqr_FIPS140SHAKETest() - Known-answer tests for SHAKE.

Hashing

Getting a hash digest for a message is a basic building block of many cryptographic algorithms. The toolkit provides implementations of SHA2 and SHA3.

Registering Hashes

As mentioned in the Getting Started section, you must register an implementation. The toolkit doesn’t automatically associate its own hashes with the Context when you create one. This makes it easier for code stripping to remove unused hash implementations from your application.

#include "iqr_context.h"
#include "iqr_hash.h"
...
// Create iqr_Context.
...
iqr_retval result = iqr_HashRegisterCallbacks(context, algorithm,
    implementation);

The toolkit supports the following algorithms and provides the given implementations:

  • SHA2-256 (IQR_HASHALGO_SHA2_256) — IQR_HASH_DEFAULT_SHA2_256

  • SHA2-384 (IQR_HASHALGO_SHA2_384) — IQR_HASH_DEFAULT_SHA2_384

  • SHA2-512 (IQR_HASHALGO_SHA2_512) — IQR_HASH_DEFAULT_SHA2_512

  • SHA3-256 (IQR_HASHALGO_SHA3_256) — IQR_HASH_DEFAULT_SHA3_256

  • SHA3-512 (IQR_HASHALGO_SHA3_512) — IQR_HASH_DEFAULT_SHA3_512

For example, to use the toolkit’s SHA3-512 implementation as the default for all SHA3-512 hashing:

#include "iqr_hash.h"
...
// Create iqr_Context.
...
iqr_retval result = iqr_HashRegisterCallbacks(context, IQR_HASHALGO_SHA3_512,
    &IQR_HASH_DEFAULT_SHA3_512);
Note
The iqr_Context object tracks all of the supported SHA2, and SHA3 algorithms; you can register implementations for all of them on a single iqr_Context.

When you register a hash implementation, the toolkit runs a known-answer test using the supplied hash callbacks. If this test fails, the registration is ignored and iqr_HashRegisterCallbacks() returns an error.

Using Hashes

After an appropriate hash has been registered, you can use it with the generic hashing API. This API is the same for all hashes.

To create a hash object:

#include "iqr_hash.h"
...
// Create iqr_Context.
// Register hash implementations.
...
iqr_Hash *hash = NULL;
iqr_retval result = iqr_HashCreate(context, algorithm, &hash);
if (result != IQR_OK) {
    // Handle error.
}

To use a hash object:

...
// Call Begin() to initialize the hash.
result = iqr_HashBegin(hash);
if (result != IQR_OK) {
    // Handle error.
}

// Call Update() zero or more times to add data to the hash.
result = iqr_HashUpdate(hash, buffer, buffer_size);
if (result != IQR_OK) {
    // Handle error.
}

// Use iqr_HashGetDigestSize() to get the digest size for this hash.
uint8_t *digest = NULL;
size_t digest_size = 0;
result = iqr_HashGetDigestSize(hash, &digest_size);
if (result != IQR_OK) {
    // Handle error.
}

digest = calloc(1, digest_size);
if (digest == NULL) {
    // Handle out-of-memory.
}

// Call End() to finish the operation and get the digest.
result = iqr_HashEnd(hash, digest, digest_size);

To destroy a hash object:

...
result = iqr_HashDestroy(&hash);
if (result != IQR_OK) {
    // Handle error.
}

There’s also an iqr_HashMessage() function that combines the iqr_HashBegin(), iqr_HashUpdate(), and iqr_HashEnd() process into one call:

...
result = iqr_HashMessage(hash, buffer, buffer_size, digest, digest_size);
if (result != IQR_OK) {
    // Handle error.
}

Writing a Hash Implementation

Because the toolkit doesn’t have a default hash implementation associated with the iqr_Context object, you can use the iqr_HashCallbacks structure (from iqr_hash.h) to provide your own code. This is handy if you have hardware that provides fast hashing, or you want to use another library’s implementation.

The iqr_HashCallbacks structure shows you the signatures of the functions you’ll need to implement:

typedef struct {
    iqr_retval (*initialize)(void **state);

    iqr_retval (*begin)(void *state);

    iqr_retval (*update)(void *state, const uint8_t *data, size_t size);

    iqr_retval (*end)(void *state, uint8_t *digest, size_t size);

    iqr_retval (*cleanup)(void **state);
} iqr_HashCallbacks;

The initialize() function is passed an empty pointer, where you can store any necessary state. Allocate any memory you need.

#include "iqr_hash.h"
#include "iqr_retval.h"
...
iqr_retval myhash_initialize(void **state)
{
    // Sanity-check inputs.
    if (state == NULL) {
        return IQR_ENULLPTR;
    }

    if (*state != NULL) {
        return IQR_EINVPTR;
    }

    // Allocate whatever state you need. It's OK to leave it
    // NULL if you don't need to track any state.
    myhash_state *myhash = calloc(1, sizeof(myhash_state));
    if (myhash == NULL) {
        return IQR_ENOMEM;
    }
    ...
    *state = myhash;

    return IQR_OK;
}

Your begin() function is called at the start of a new hashing operation. Initialize your hash code, and get ready to accept data.

#include "iqr_hash.h"
#include "iqr_retval.h"
...
iqr_retval myhash_begin(void *state)
{
    // Sanity-check inputs.
    if (state == NULL) {
        return IQR_ENULLPTR;
    }

    // Perform any other pre-hashing initialization.
    ...

    return IQR_OK;
}

The update() function is passed the state pointer, a buffer, and the size of the buffer in bytes. Add the buffer’s data to your hash state. This function can be called zero or more times during a hashing operation.

iqr_retval myhash_update(void *state, uint8_t *data, size_t data_size)
{
    // Sanity-check input.
    if (data == NULL && data_size != 0) {
        return IQR_ENULLPTR;
    }

    if (data_size == 0) {
        return IQR_OK;
    }

    // Add the data from the buffer to your hash.
    ...

    return IQR_OK;
}

The end() function gets the state pointer, a buffer, and the size of the buffer in bytes. Complete the hashing operation and store its digest in the supplied buffer.

iqr_retval myhash_end(void *state, uint8_t *digest, size_t digest_size)
{
    // Sanity-check input.
    if (state == NULL || digest == NULL) {
        return IQR_ENULLPTR;
    }

    // Make sure there's enough room to store your digest.
    if (digest_size != MYHASH_DIGEST_SIZE) {
        return IQR_EINVBUFSIZE;
    }

    // Finish processing the hash.
    ...

    // Extract the digest and store it in the given buffer.
    ...
}

Finally, the cleanup() function gets a pointer to the state pointer. Wipe and deallocate any state you allocated during initialize() and set the state pointer to NULL.

iqr_retval myhash_cleanup(void **state)
{
    // Clean up and deallocate any state you allocated.
    //
    // You may need to substitute your platform's version of memset_s()
    // (a secure memset() that won't be optimized away by the compiler).
    ...
    memset_s(*state, 0, sizeof(myhash_state));
    free(*state);
    *state = NULL;

    return IQR_OK;
}
Note
The end() function is always called when a hash object is destroyed, whether or not begin() and update() succeeded.

Using OpenSSL’s SHA2-256

For a concrete example of creating your own hash implementation, let’s use OpenSSL’s SHA2-256.

First, we’ll write the hash’s initialize(), begin(), update(), end(), and cleanup() functions using calls to the OpenSSL library:

#include "iqr_hash.h"
#include "iqr_retval.h"

#include <openssl/sha.h>

// OpenSSL APIs return 1 for success.
#define OPENSSL_OK 1

static iqr_retval sha2_256_initialize(void **state)
{
    // Sanity-check inputs.
    if (state == NULL) {
        return IQR_ENULLPTR;
    }

    if (*state != NULL) {
        return IQR_EINVPTR;
    }

    // Allocate an OpenSSL SHA256_CTX to store the state.
    SHA256_CTX *ctx = NULL;
    ctx = calloc(1, sizeof(*ctx));
    if (ctx == NULL) {
        return IQR_ENOMEM;
    }

    *state = ctx;

    return IQR_OK;
}

static iqr_retval sha2_256_begin(void *state)
{
    // Sanity-check inputs.
    if (state == NULL) {
        return IQR_ENULLPTR;
    }

    SHA256_CTX *ctx = (SHA256_CTX *)state;

    // Let OpenSSL set up its context.
    int rc = SHA256_Init(ctx);
    if (rc != OPENSSL_OK) {
        return IQR_ENOTINIT;
    }

    return IQR_OK;
}

static iqr_retval sha2_256_update(void *state, const uint8_t *data,
    size_t size)
{
    // Sanity-check input.
    if (data == NULL && data_size != 0) {
        return IQR_ENULLPTR;
    }

    if (data_size == 0) {
        return IQR_OK;
    }

    // In this case, the state can't be NULL.
    if (state == NULL) {
        return IQR_ENULLPTR;
    }

    SHA256_CTX *ctx = (SHA256_CTX *)state;

    // Pass the data pointer into the OpenSSL update function.
    int rc = SHA256_Update(ctx, data, size);
    if (rc != OPENSSL_OK) {
        // Update failed.
        return IQR_EINVOBJECT;
    }

    return IQR_OK;
}

static iqr_retval sha2_256_end(void *state, uint8_t *digest, size_t digest_size)
{
    // Sanity-check input.
    if (state == NULL || digest == NULL) {
        return IQR_ENULLPTR;
    }

    SHA256_CTX *ctx = (SHA256_CTX *)state;

    // Make sure there's enough room to store your digest.
    if (digest_size != IQR_SHA2_256_DIGEST_SIZE) {
        return IQR_EINVBUFSIZE;
    }

    // Pass the data pointer into the OpenSSL Final function.
    int rc = SHA256_Final(digest, *ctx);
    if (rc != OPENSSL_OK) {
        return IQR_EINVOBJECT;
    }

    return IQR_OK;
}

static iqr_retval sha2_256_cleanup(void **state)
{
    SHA256_CTX *ctx = (SHA256_CTX *)*state;

    memset_s(ctx, 0, sizeof(*ctx));
    free(ctx);
    *state = NULL;

    return IQR_OK;
}

Now that we’ve got an implementation, we need to register it so the rest of the toolkit can use it when an algorithm needs a SHA2-256 hash:

// Create the callback structure.
const iqr_HashCallbacks openssl_sha2_256 = {
    .initialize = sha2_256_initialize,
    .begin = sha2_256_begin,
    .update = sha2_256_update,
    .end = sha2_256_end,
    .cleanup = sha2_256_cleanup
};

// Register the OpenSSL implementation.
result = iqr_HashRegisterCallbacks(context, IQR_HASHALGO_SHA2_256,
    &openssl_sha2_256);
if (result != IQR_OK) {
    // Handle error.
}

After that, any IQR_HASHALGO_SHA2_256 hash object you create with that iqr_Context object will use the OpenSSL implementation:

iqr_Hash *hash = NULL;
iqr_retval result = iqr_HashCreate(context, IQR_HASHALGO_SHA2_256, &hash);
if (result != IQR_OK) {
    // Handle error.
}

Using Microsoft’s SHA2-256

Windows Vista and newer operating systems feature Microsoft’s Cryptography API: Next Generation (CNG). This example demonstrates a SHA2-256 implementation that wraps Microsoft’s CNG functions. For more information, see the Creating a Reusable Hashing Object documentation.

The Windows API can return a number of error codes. In this example, we ignore the NTSTATUS codes and replace them with our own iqr_retval codes. You must handle NTSTATUS codes appropriately for your application.

First, we’ll write the hash’s initialize(), begin(), update(), end(), and cleanup() functions:

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN  // Exclude rarely-used stuff from Windows headers.
#endif

#include <windows.h>
#include <bcrypt.h>
#include <ntstatus.h>

// This used to be in <ntstatus.h>, but was removed in recent versions of the
// Platform SDK.
#ifndef NT_SUCCESS
#define NT_SUCCESS(status) (status >= 0)
#endif

#include "iqr_hash.h"
#include "iqr_retval.h"

// This will be our state.
typedef struct {
    BCRYPT_ALG_HANDLE alg;
    BCRYPT_HASH_HANDLE hash;
} BCRYPT_HANDLES;

static iqr_retval cng_sha2_256_initialize(void **state)
{
    // Sanity-check inputs.
    if (state == NULL) {
        return IQR_ENULLPTR;
    }

    if (*state != NULL) {
        return IQR_EINVPTR;
    }

    BCRYPT_HANDLES *handles = NULL;

    // Allocate the state that will be passed around.
    handles = calloc(1, sizeof(BCRYPT_HANDLES));
    if (handles == NULL) {
        return IQR_ENOMEM;
    }

    // A reusable hash object will automatically reset for reuse following a
    // call to BCryptFinishHash(). Thus it is not necessary to recreate a
    // hashing handle.
    DWORD flag = BCRYPT_HASH_REUSABLE_FLAG;

    NTSTATUS status = BCryptOpenAlgorithmProvider(&handles->alg,
        BCRYPT_SHA256_ALGORITHM, NULL, flag);
    if (NT_SUCCESS(status) == false) {
        goto cleanup;
    }

    status = BCryptCreateHash(handles->alg, &handles->hash, NULL, 0, NULL, 0, flag);
    if (NT_ERROR(status)) {
        goto cleanup;
    }

    // Success! Set the state and exit.
    *state = handles;

    return IQR_OK;

cleanup:
    BCryptCloseAlgorithmProvider(handles->alg, 0);
    free(handles);

    return IQR_ENOTINIT;
}

static iqr_retval cng_sha2_256_begin(void *state)
{
    // A reusable hash object automatically resets its internal state.
    //
    // By reusing a hash object, this implementation favours speed by
    // utilizing more memory. An alternative implementation could call
    // BCryptCreateHash() here without the use of BCRYPT_HASH_REUSABLE_FLAG.
    return IQR_OK;
}

static iqr_retval cng_sha2_256_update(void *state, const uint8_t *data,
    size_t size)
{
    // Sanity-check input.
    if (data == NULL && data_size != 0) {
        return IQR_ENULLPTR;
    }

    if (data_size == 0) {
        return IQR_OK;
    }

    // In this case, the state can't be NULL.
    if (state == NULL) {
        return IQR_ENULLPTR;
    }

    BCRYPT_HANDLES *handles = (BCRYPT_HANDLES *)state;

    NTSTATUS status = BCryptHashData(handles->hash, (PUCHAR)data, (ULONG)size, 0);
    if (NT_SUCCESS(status) == false) {
        return IQR_EINVOBJECT;
    }

    return IQR_OK;
}

static iqr_retval cng_sha2_256_end(void *state, uint8_t *digest, size_t digest_size)
{
    // Sanity-check input.
    if (state == NULL || digest == NULL) {
        return IQR_ENULLPTR;
    }

    // Make sure there's enough room to store your digest.
    if (digest_size != IQR_SHA2_256_DIGEST_SIZE) {
        return IQR_EINVBUFSIZE;
    }

    BCRYPT_HANDLES *handles = (BCRYPT_HANDLES *)state;

    // The hash object automatically resets once this call succeeds.
    NTSTATUS status = BCryptFinishHash(handles->hash, (PUCHAR)digest, (ULONG)size, 0);
    if (NT_SUCCESS(status) == false) {
        return IQR_EINVOBJECT;
    }

    return IQR_OK;
}

static iqr_retval cng_sha2_256_cleanup(void **state)
{
    // Sanity-check input.
    if (state == NULL) {
        return IQR_ENULLPTR;
    }

    BCRYPT_HANDLES *handles = (BCRYPT_HANDLES *)*state;

    NTSTATUS status = BCryptDestroyHash(handles->hash);
    if (NT_SUCCESS(status) == false) {
        return IQR_EINVOBJECT;
    }

    status = BCryptCloseAlgorithmProvider(handles->alg, 0);
    if (NT_SUCCESS(status) == false) {
        return IQR_EINVOBJECT;
    }

    memset_s(handles, 0, sizeof(*handles));
    free(handles);
    *state = NULL;

    return IQR_OK;
}

Now we use iqr_HashRegisterCallbacks() to register this implementation as the SHA2-256 hash:

// Create the callback structure.
const iqr_HashCallbacks cng_sha2_256 = {
    .initialize = cng_sha2_256_initialize,
    .begin = cng_sha2_256_begin,
    .update = cng_sha2_256_update,
    .end = cng_sha2_256_end,
    .cleanup = cng_sha2_256_cleanup
};

// Register the CNG implementation.
result = iqr_HashRegisterCallbacks(context, IQR_HASHALGO_SHA2_256,
    &cng_sha2_256);
if (result != IQR_OK) {
    // Handle error.
}

Now, any IQR_HASHALGO_SHA2_256 hash object you create will use the CNG SHA2-2 256 implementation:

iqr_Hash *hash = NULL;
iqr_retval result = iqr_HashCreate(context, IQR_HASHALGO_SHA2_256, &hash);
if (result != IQR_OK) {
    // Handle error.
}

Random Numbers

Generating random data is an important part of many cryptographic algorithms. The toolkit supports the HMAC-DRBG algorithm for generating data.

For simplicity, we refer to this class of algorithm as random number generators (RNGs).

Seed Data

Pseudo-random number generators are only as good as the seed data you use to initialize them. This seed data must come from a good source of entropy.

Refer to your target system’s CPU or OS documentation to find the best source of entropy available to you.

Using a poor source of entropy data will compromise the randomness of the data produced by these algorithms.

Using RNGs

Like hashes, RNGs use a generic API. Unlike hashes, the RNGs supported by the toolkit require custom Create() functions to provide suitable initialization data for each algorithm.

To create an HMAC-DRBG object using any of the IQR_HASHALGO_* constants from iqr_hash.h:

#include "iqr_context.h"
#include "iqr_hash.h"
#include "iqr_rng.h"
...
// Create iqr_Context.
// Register a hash implementation for the implementation you want to use
// (IQR_HASHALGO_SHA2_256 in this example).
...
iqr_RNG *rng = NULL;
iqr_retval result = iqr_RNGCreateHMACDRBG(context, IQR_HASHALGO_SHA2_256, &rng);
if (result != IQR_OK) {
    // Handle error.
}
Note
To provide a nonce for HMAC-DRBG, include it in the seed data given to iqr_RNGInitialize(). If your seed data was "password" and your nonce is "random", set the initialization buffer to "passwordrandom".

To create an RNG object using your own implementation:

#include "iqr_context.h"
#include "iqr_rng.h"
...
// Create iqr_Context.
...
iqr_RNGCallbacks rng_callbacks = {
    .initialize = my_rng_initialize,
    .reseed = my_rng_reseed,
    .getbytes = my_rng_getbytes,
    .cleanup = my_rng_cleanup
};
...
iqr_RNG *rng = NULL;
iqr_retval result = iqr_RNGCreate(context, &rng_callbacks, &rng);
if (result != IQR_OK) {
    // Handle error.
}

To use an RNG object:

...
// Call Initialize() to initialize the RNG.
result = iqr_RNGInitialize(rng, seed_buffer, seed_size);
if (result != IQR_OK) {
    // Handle error.
}

// Call GetBytes() to get bytes from the RNG.
result = iqr_RNGGetBytes(rng, buffer, buffer_size);
if (result == IQR_ERESEED) {
    // The RNG is depleted, call Reseed() to give it more seed data.
    result = iqr_RNGReseed(rng, new_seed_buffer, new_seed_size);
    if (result != IQR_OK) {
        // Handle error.
    }

    result = iqr_RNGGetBytes(rng, buffer, buffer_size);
    if (result != IQR_OK) {
        // Handle error.
    }
} else if (result != IQR_OK) {
    // Handle error.
}

// You can also reseed the RNG as necessary; you don't
// need to wait until GetBytes() returns IQR_ERESEED.
result = iqr_RNGReseed(rng, new_seed_buffer, new_seed_size);
if (result != IQR_OK) {
    // Handle error.
}

To destroy an RNG object:

...
result = iqr_RNGDestroy(&rng);
if (result != IQR_OK) {
    // Handle error.
}

Writing an RNG Implementation

You can use the iqr_RNGCallbacks structure (from iqr_rng.h) to provide your own RNG implementation. This is handy if you have hardware that provides cryptographically secure random number generation, or you want to use another library’s implementation.

The iqr_RNGCallbacks structure shows you the signatures of the functions you’ll need to implement:

typedef struct {
    iqr_retval (*initialize)(void **state, const uint8_t *seed,
        size_t seed_size);

    iqr_retval (*reseed)(void *state, const uint8_t *entropy,
        size_t entropy_size);

    iqr_retval (*getbytes)(void *state, uint8_t *buffer, size_t buffer_size);

    iqr_retval (*cleanup)(void **state);
} iqr_RNGCallbacks;

Your initialize() function is passed an empty pointer, which it can use to store any necessary state. In addition, you’re passed a buffer containing seed data, and the size (in bytes) of that buffer. Allocate any memory you need, initialize your RNG code, and get ready to generate bytes.

#include "iqr_retval.h"
#include "iqr_rng.h"
...
iqr_retval myrng_initialize(void **state, const uint8_t *seed, size_t seed_size)
{
    // Sanity-check inputs.
    if (state == NULL || seed == NULL) {
        return IQR_ENULLPTR;
    }

    if (*state != NULL) {
        return IQR_EINVPTR;
    }

    // The caller must provide seed data.
    if (seed_size == 0) {
        return IQR_EINVBUFSIZE;
    }

    // Allocate whatever state you need. It's OK to leave it
    // NULL if you don't need to track any state.
    myrng_state *myrng = calloc(1, sizeof(myrng_state));
    if (myrng == NULL) {
        return IQR_ENOMEM;
    }
    ...
    *state = myrng;

    // Perform any other initialization.
    ...

    return IQR_OK;
}

The reseed() function is passed the state pointer, a buffer, and the size of the buffer in bytes. Add the buffer’s data to your RNG’s internal entropy.

iqr_retval myrng_reseed(void *state, uint8_t *seed, size_t seed_size)
{
    // Sanity-check input.
    if (seed == NULL) {
        return IQR_ENULLPTR;
    }

    if (seed_size == 0) {
        return IQR_EINVBUFSIZE;
    }

    // Add the data from the seed to your RNG's entropy.
    ...

    return IQR_OK;
}

The getbytes() function is passed the state pointer, a buffer, and the size of the buffer in bytes. Write that many random bytes into the buffer.

iqr_retval myrng_getbytes(void *state, uint8_t *buffer, size_t buffer_size)
{
    // Sanity-check input.
    if (buffer == NULL) {
        return IQR_ENULLPTR;
    }

    if (buffer_size == 0) {
        return IQR_EINVBUFSIZE;
    }

    // Generate random bytes and write them into the buffer.
    ...

    return IQR_OK;
}

Finally, the cleanup() function gets a pointer to the state pointer. Before returning, deallocate any state you allocated during initialize() and set the state pointer to NULL.

iqr_retval myrng_cleanup(void **state)
{
    // Sanity-check input.
    if (state == NULL) {
        return IQR_ENULLPTR;
    }

    // Clean up and deallocate any state you allocated.
    //
    // You may need to substitute your platform's version of memset_s()
    // (a secure memset() that won't be optimized away by the compiler).
    ...
    memset_s(*state, 0, sizeof(myrng_state));
    free(*state);
    *state = NULL;

    return IQR_OK;
}
Note
The cleanup() function is always called when an RNG object is destroyed, whether or not initialize(), getbytes(), and reseed() succeeded.

Using /dev/urandom as an RNG

Here’s a concrete example of how to create your own RNG implementation, using /dev/urandom as a source of random bytes. Refer to your operating system’s /dev/urandom documentation for details about its behaviour, and its suitability as a cryptographic random number generator.

This sample assumes your /dev/urandom implementation lets you write additional entropy to the device.

First, we’ll write the RNG’s initialize(), reseed(), getbytes(), and cleanup() functions using POSIX functions and /dev/urandom:

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "iqr_context.h"
#include "iqr_retval.h"
#include "iqr_rng.h"

static iqr_retval devrandom_initialize(void **state, const uint8_t *seed,
    size_t seed_size)
{
    // Sanity-check inputs.
    if (state == NULL || seed == NULL) {
        return IQR_ENULLPTR;
    }

    if (*state != NULL) {
        return IQR_EINVPTR;
    }

    // The caller must provide seed data.
    if (seed_size == 0) {
        return IQR_EINVBUFSIZE;
    }

    int *device_handle = calloc(1, sizeof(int));
    if (device_handle == NULL) {
        return IQR_ENOMEM;
    }

    iqr_retval result = IQR_OK;

    // Write our seed data to the device.
    *device_handle = open("/dev/urandom", O_RDWR);
    if (*device_handle == -1) {
        result = IQR_ENOTINIT;
        goto cleanup;
    }

    ssize_t bytes_written = 0;
    while (seed_size > 0) {
        bytes_written = write(*device_handle, seed, seed_size);
        if (bytes_written == -1) {
            result = IQR_EINVOBJECT;
            goto cleanup;
        }
        seed_size -= (size_t)bytes_written;
    }

    // We don't need to allocate state, just store the file descriptor.
    *state = device_handle;
    return result;

cleanup:
    if (*device_handle != -1) {
        close(*device_handle);
        free(device_handle);
        device_handle = NULL;
    }

    return result;
}

static iqr_retval devrandom_reseed(void *state, const uint8_t *entropy,
    size_t entropy_size)
{
    // Sanity-check input.
    if (state == NULL || entropy == NULL) {
        return IQR_ENULLPTR;
    }

    if (entropy_size == 0) {
        return IQR_EINVBUFSIZE;
    }

    // Add the data to your RNG's entropy.
    int *device_handle = state;
    ssize_t bytes_written = 0;
    while (entropy_size > 0) {
        bytes_written = write(*device_handle, entropy, entropy_size);
        if (bytes_written == -1) {
            return IQR_EINVOBJECT;
        }
        entropy += bytes_written;
        entropy_size -= (size_t)bytes_written;
    }

    return IQR_OK;
}

static iqr_retval devrandom_getbytes(void *state, uint8_t *buffer,
    size_t buffer_size)
{
    // Sanity-check input.
    if (state == NULL || buffer == NULL) {
        return IQR_ENULLPTR;
    }

    if (buffer_size == 0) {
        return IQR_EINVBUFSIZE;
    }

    // Generate random bytes and write them into the buffer.
    int *device_handle = state;
    ssize_t bytes_read = 0;
    while (buffer_size > 0) {
        bytes_read = read(*device_handle, buffer, buffer_size);
        if (bytes_read == -1) {
            return IQR_EINVDATA;
        }
        buffer += bytes_read;
        buffer_size -= (size_t)bytes_read;
    }

    return IQR_OK;
}

static iqr_retval devrandom_cleanup(void **state)
{
    // Sanity-check input.
    if (state == NULL) {
        return IQR_ENULLPTR;
    }

    // Clean up and deallocate any state you allocated.
    int *device_handle = *state;
    close(*device_handle);
    free(device_handle);
    *state = NULL;

    return IQR_OK;
}

Now that you’ve got an implementation, you can use it with iqr_RNGCreate():

// Create the callback structure.
static const iqr_RNGCallbacks devrandom_rng = {
    .initialize = devrandom_initialize,
    .reseed = devrandom_reseed,
    .getbytes = devrandom_getbytes,
    .cleanup = devrandom_cleanup
};

iqr_RNG *rng = NULL;
iqr_retval result = iqr_RNGCreate(context, &devrandom_rng, &rng);
if (result != IQR_OK) {
    // Handle error.
}

Using Microsoft’s Cryptographic Service as an RNG

Windows Vista and newer operating systems feature Microsoft’s Cryptography API: Next Generation (CNG). Like the previous example, the following code demonstrates an RNG implementation that wraps Microsoft’s random number generation functions. For more information, please see the CNG documentation.

Windows constantly adds entropy, so the CNG random number generator does not need to be reseeded.

The Windows API can return a number of error codes. In this example, we ignore the NTSTATUS codes and replace them with our own iqr_retval codes. You must handle NTSTATUS codes appropriately for your application.

First, we’ll write the RNG’s initialize(), reseed(), getbytes(), and cleanup() functions:

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN  // Exclude rarely-used stuff from Windows headers.
#endif

#include <windows.h>
#include <bcrypt.h>
#include <ntstatus.h>

// This may not exists in <ntstatus.h>
#ifndef NT_SUCCESS
#define NT_SUCCESS(status) (status >= 0)
#endif

#include "iqr_context.h"
#include "iqr_retval.h"
#include "iqr_rng.h"

static iqr_retval cng_rng_initialize(void **state, const uint8_t *seed,
    size_t seed_size)
{
    // Sanity-check inputs.
    if (state == NULL || seed == NULL) {
        return IQR_ENULLPTR;
    }

    if (*state != NULL) {
        return IQR_EINVPTR;
    }

    BCRYPT_ALG_HANDLE alg = NULL;

    // Open a handle to the CNG.
    NTSTATUS status = BCryptOpenAlgorithmProvider(&alg, BCRYPT_RNG_ALGORITHM, NULL, 0);
    if (NT_SUCCESS(status) == false) {
        return IQR_ENOTINIT;
    }

    *state = alg;
    return result;
}

static iqr_retval cng_rng_reseed(void *state, const uint8_t *entropy,
    size_t entropy_size)
{
    // The CNG random number generator will never need reseeding.
    return IQR_OK;
}

static iqr_retval cng_rng_getbytes(void *state, uint8_t *buffer,
    size_t buffer_size)
{
    // Sanity-check input.
    if (state == NULL || buffer == NULL) {
        return IQR_ENULLPTR;
    }

    if (buffer_size == 0) {
        return IQR_EINVBUFSIZE;
    }

    BCRYPT_ALG_HANDLE alg = (BCRYPT_ALG_HANDLE)state;

    // Generate requested random bytes.
    NTSTATUS status = BCryptGenRandom(alg, (PUCHAR)buffer, (ULONG)buffer_size, 0);
    if (NT_SUCCESS(status) == false) {
        return IQR_EINVOBJECT;
    }

    return IQR_OK;
}

static iqr_retval cng_rng_cleanup(void **state)
{
    // Sanity-check input.
    if (state == NULL) {
        return IQR_ENULLPTR;
    }

    BCRYPT_ALG_HANDLE alg = (BCRYPT_ALG_HANDLE)*state;

    // Close the provider handle.
    NTSTATUS status = BCryptCloseAlgorithmProvider(alg, 0);
    if (NT_SUCCESS(status) == false) {
        return IQR_EINVOBJECT;
    }

    *state = NULL;
    return IQR_OK;
}

Once the implementation is finished, it’s ready to use with iqr_RNGCreate():

// Create the callback structure.
static const iqr_RNGCallbacks cng_rng = {
    .initialize = cng_dev_initialize,
    .reseed = cng_dev_reseed,
    .getbytes = cng_dev_getbytes,
    .cleanup = cng_dev_cleanup
};

iqr_RNG *rng = NULL;
iqr_retval result = iqr_RNGCreate(context, &cng_rng, &rng);
if (result != IQR_OK) {
    // Handle error.
}

Message Authentication Codes

The toolkit provides two message authentication code algorithms, HMAC and Poly1305.

MACs have the following key length requirements, based on the algorithm:

  • HMAC (SHA2-256 or SHA3-256) - 32 bytes or more

  • HMAC (SHA2-512 or SHA3-512) - 64 bytes or more

  • Poly1305 - 32 bytes (more is allowed, but additional data will be ignored)

Using MACs

Using a MAC is similar to using a hash:

  1. Create the MAC using iqr_MACCreateHMAC() or iqr_MACCreatePoly1305().

  2. Begin the MAC operation.

  3. Update it with data.

  4. End the MAC and get the tag.

  5. Destroy the MAC, or begin again.

The hash based message authentication code (HMAC) algorithm (found in iqr_mac.h) requires you to register an implementation for the hashing algorithm you want to use; see the hashing section for more information about hash algorithms.

To create an HMAC:

#include "iqr_hash.h"
#include "iqr_mac.h"
...
// Create iqr_Context.
// Register hash implementations.
...
iqr_MAC *mac = NULL;
iqr_retval result = iqr_MACCreateHMAC(context, algorithm, &mac);
if (result != IQR_OK) {
    // Handle error.
}

The Poly1305 message authentication code algorithm (found in iqr_mac.h) doesn’t have any external dependencies, but does require a one time key. It can be combined with the ChaCha20 cipher to provide Authenticated Encryption with Associated Data (AEAD) as demonstrated in the aead_chacha20_poly1305 sample (in your samples directory, or found on GitHub).

To create a Poly1305 MAC:

#include "iqr_mac.h"
...
// Create iqr_Context.
...
iqr_MAC *mac = NULL;
iqr_retval result = iqr_MACCreatePoly1305(context, &mac);
if (result != IQR_OK) {
    // Handle error.
}

To use a MAC object:

...
// Call Begin() to initialize the MAC.
result = iqr_MACBegin(mac, key, key_size);
if (result != IQR_OK) {
    // Handle error.
}

// Call Update() zero or more times to add data to the MAC.
result = iqr_MACUpdate(mac, buffer, buffer_size);
if (result != IQR_OK) {
    // Handle error.
}

// Use iqr_MACGetTagSize() to get the tag size for this MAC.
uint8_t *tag = NULL;
size_t tag_size = 0;
result = iqr_MACGetTagSize(mac, &tag_size);
if (result != IQR_OK) {
    // Handle error.
}

tag = calloc(1, tag_size);
if (tag == NULL) {
    // Handle out-of-memory.
}

// Call End() to finish the operation and get the tag.
result = iqr_MACEnd(mac, tag, tag_size);
if (result != IQR_OK) {
    // Handle error.
}

To destroy a MAC object:

...
result = iqr_MACDestroy(&mac);
if (result != IQR_OK) {
    // Handle error.
}

There’s also an iqr_MACMessage() function that combines the iqr_MACBegin(), iqr_MACUpdate(), iqr_MACEnd() process into one call:

...
result = iqr_MACMessage(mac, key, key_size, buffer, buffer_size, tag, tag_size);
if (result != IQR_OK) {
    // Handle error.
}

Key Derivation Functions

The toolkit provides three standard key derivation functions (KDFs):

Because the KDFs all have slightly different needs, there is currently no generic KDF API.

Warning
The KDF APIs will change in the next version of the toolkit. For more information see Deprecated APIs.

RFC 5869 HKDF

Because the RFC 5869 KDF uses an HMAC internally, you must register a hashing algorithm before using the KDF.

To use the RFC 5869 HMAC based KDF:

#include "iqr_context.h"
#include "iqr_hash.h"
#include "iqr_kdf.h"
...
// Create iqr_Context.
// Register hashing algorithms.
...
iqr_retval result = iqr_RFC5869HKDFDeriveKey(context, hash_algorithm,
    salt_buffer, salt_size, ikm_buffer, ikm_size, info_buffer, info_size,
    key_buffer, key_size);
if (result != IQR_OK) {
    // Handle error.
}

Use the salt buffer to provide additional randomness. The salt buffer can be NULL (and the salt buffer size 0), but providing salt will improve the security of your application.

The initial keying material (the IKM buffer) is similar to the seeding data given to a random number generator. Some algorithms may have an existing cryptographically strong key to use for the initial keying material, such as the premaster secret in TLS RSA cipher suites. You must provide data in this buffer.

The optional info buffer is for context and application specific information. This binds the derived key to your information, such as a protocol number, an algorithm identifier, user data, etc.

The derived key data is returned in the key buffer. The key size cannot be more than 254 times the size of the hash’s digest size or this will return an IQR_EOUTOFRANGE error.

Note
RFC 5869 HKDF is the cryptographically strongest KDF currently provided by the toolkit.

NIST Concatenation KDF

Because the NIST SP 800-56C Option 1 Concatenation KDF uses a hash internally, you must register a hashing algorithm before using the KDF.

To use the Concatenation KDF:

#include "iqr_context.h"
#include "iqr_hash.h"
#include "iqr_kdf.h"
...
// Create iqr_Context.
// Register hashing algorithms.
...
iqr_retval result = iqr_ConcatenationKDFDeriveKey(context, hash_algorithm,
    shared_secret, shared_secret_size, other_info, other_info_size,
    key_buffer, key_size);
if (result != IQR_OK) {
    // Handle error.
}

The shared secret ("Z" in the specification) is pre-determined data shared between all systems generating keys with the Concatenation KDF. The shared secret must be at least one byte.

The other info ("OtherInfo" in the specification) is for context and application specific information. This binds the derived key to your information, such as a protocol number, an algorithm identifier, user data, etc. It can be NULL (and set the buffer size to 0).

The derived key is returned in the key buffer.

RFC 2898 PBKDF2

Because the RFC 2898 PBKDF2 uses a hash internally, you must register a hashing algorithm before using the KDF.

To use PBKDF2:

#include "iqr_context.h"
#include "iqr_hash.h"
#include "iqr_kdf.h"
...
// Create iqr_Context.
// Register hashing algorithms.
...
iqr_retval result = iqr_PBKDF2DeriveKey(context, hash_algorithm,
    password, password_size, salt, salt_size, iteration_count,
    key_buffer, key_size);
if (result != IQR_OK) {
    // Handle error.
}

The optional password is pre-determined data shared between all systems generating keys with PBKDF2. This can be NULL if the password size is also 0 bytes.

Use the salt buffer to provide additional randomness. The salt buffer can be NULL (and the salt buffer size 0), but providing salt will improve the security of your application.

Using both a password and a salt provides the best security.

PBKDF2 uses the specified number of iterations to improve the randomness of its derived data at the expense of additional processing time. Use the maximum value that’s tolerable for your application or the value specified by your protocol.

The derived key is returned in the key buffer.

Symmetric Encryption

In general, symmetric encryption schemes are not significantly threatened by quantum computers. Doubling the key sizes provides enough security in the face of a quantum threat.

The toolkit currently provides one symmetric algorithm, RFC 8439’s ChaCha20.

ChaCha20

ChaCha20 (see iqr_chacha20.h) is an easy to use cipher that doesn’t require additional parameter objects. The key and nonce are provided as byte buffers.

To encrypt data using ChaCha20:

#include "iqr_chacha20.h"
#include "iqr_context.h"
...
// Create iqr_Context. Not strictly needed for ChaCha20, but needed for
// other toolkit APIs.
...
iqr_retval result = iqr_ChaCha20Encrypt(key_buffer, key_size, nonce, nonce_size,
    counter, plaintext, plaintext_size, ciphertext, ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

The key buffer must have exactly IQR_CHACHA20_KEY_SIZE bytes of data, and the nonce buffer must have exactly IQR_CHACHA20_NONCE_SIZE bytes of data. The key can be pre-shared using a suitable key agreement protocol, and the nonce should be unique per encryption stream.

Use the counter to indicate the start of this block. Because ChaCha20 is a block cipher operating in counter mode, you must increment this by the number of encrypted blocks (that is, ceiling(plaintext_size / 64)) when encrypting additional data using the same key and nonce.

The ciphertext buffer must be at least as large as the plaintext buffer.

To decrypt data using ChaCha20:

...
result = iqr_ChaCha20Decrypt(key_buffer, key_size, nonce, nonce_size,
    counter, ciphertext, ciphertext_size, plaintext, plaintext_size);
if (result != IQR_OK) {
    // Handle error.
}

The key, nonce, and counter must match the values used to encrypt the data.

Since ChaCha20 is a symmetric cipher, encrypt and decrypt are the same operation, with plaintext and ciphertext swapped.

Digital Signatures

The toolkit provides three digital signature schemes:

  • Dilithium — a lattice based signature scheme

  • HSS (Hierarchical Signature Scheme) — a hash based one time signature scheme

  • Rainbow — a multivariate signature scheme

  • SPHINCS+ — a stateless hash based signature scheme

  • XMSS (eXtended Merkle Signature Scheme) — a hash based one time signature scheme

  • XMSSMT Multi-tree XMSS — a hash based one time signature scheme

Digital Signature Usage

  1. Alice creates a keypair.

    • Alice makes her public key available to anyone.

    • Alice keeps her private key secret.

  2. Alice uses her private key to sign a message.

    • The message can be any data, such as a hash digest, the contents of a file, or a buffer.

    • Alice distributes the signature along with the message.

  3. Anyone can use her public key, the message, and the signature, to verify that the message was signed by the private key that matches the public key.

How digital signatures work.

HSS, XMSS, and XMSSMT Tree Strategies

Tree strategies offer a trade-off between CPU utilization and memory usage during signing. Choosing the correct strategy is highly dependent on the hardware restrictions of the target platform.

MEMORY_CONSTRAINED_STRATEGY

The Memory Constrained strategy implements an algorithm that minimizes the memory/storage requirements of the state at the cost of recomputing parts of the tree during signing. This option is ideal for memory constrained devices with a fast CPU to handle the extra computation.

CPU_CONSTRAINED_STRATEGY

The CPU Constrained strategy implements an algorithm that minimizes the CPU impact of updating the Merkle tree during signing while keeping memory usage small. This option is ideal for devices with a low-powered CPU.

FULL_TREE_STRATEGY

The Full Tree strategy keeps the entire Merkle tree in memory. This strategy uses the least CPU at the cost of using memory. This option is ideal for servers with large amounts of memory and the need to generate signatures frequently. For larger tree heights, this strategy requires up to 2GB of memory to store the state.

VERIFY_ONLY_STRATEGY

The Verify strategy is only used to verify signatures; it cannot be used to create or import private keys nor can it be used to create signatures. This option is ideal for a client that only needs to verify signatures. It uses the least CPU and RAM.

HSS, XMSS, and XMSSMT States

The HSS, XMSS, and XMSSMT algorithms are stateful hash-based signatures. Their private key must be accompanied by a state that gets updated every time you perform a Sign() operation to generate a signature.

The size (and contents) of the state depend on height and the tree strategy chosen when generating keys. See the Technical Information section for more information.

Each time the Sign() function is called, the state is advanced to the next usable state. You must store this new state in non-volatile memory prior to releasing the signature. Alternatively, use DetachState() to work with smaller states that can be lost without catastrophe.

In the unlikely case that an error occurs while the state is being updated, the state will become unusable. The Sign() function and any subsequent function calls will return IQR_ESTATECORRUPTED.

Use the DetachState() function to split a state into two distinct, non-overlapping state objects. This effectively "reserves" a number of signatures from the state and places them into a new detached state. Both state objects must be used with the same private key to generate signatures.

This operation is useful for disaster recovery. You can detach a small section of the state and use it for signing while the rest of the state is persisted in non-volatile memory. Or, this can be used to distribute a state and key pair to multiple machines, where each machine is given a distinct state for load balancing.

The detached state will have a smaller number of maximum signatures once DetachState() returns. Use GetSignatureCount() to obtain the number of available signatures. Use GetStateSize() to obtain the state sizes prior to exporting.

As with Sign(), in the unlikely case that an error occurs while the state is being updated, the state will become unusable. This function and any subsequent function calls will return IQR_ESTATECORRUPTED.

Dilithium Signature Scheme

The toolkit provides an implementation of the Dilithium signature scheme as defined in CRYSTALS - Dilithium: Digital Signatures from Module Lattices.

Dilithium comes in two variants:

  • IQR_DILITHIUM_128 — 128 bits of quantum security

  • IQR_DILITHIUM_160 — 160 bits of quantum security

No additional parameters are necessary.

There is a small (2-80) chance that a Dilithium signature will fail due to the math involved. To the algorithm, this looks like any other validation failure.

Creating Keys

The toolkit lets you create Dilithium keys after choosing the variant.

To create a Dilithium key pair:

#include "iqr_context.h"
#include "iqr_dilithium.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
// Create Dilithium parameters using one of the variants from iqr_dilithium.h.
iqr_DilithiumParams *params = NULL;
iqr_retval result = iqr_DilithiumCreateParams(context, variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

// Create the key pair.
iqr_DilithiumPublicKey *public_key = NULL;
iqr_DilithiumPrivateKey *private_key = NULL;
result = iqr_DilithiumCreateKeyPair(params, rng, &public_key, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

Signing

To sign a message using the Dilithium private key:

...
size_t signature_size = 0;
result = iqr_DilithiumGetSignatureSize(params, &signature_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *signature = calloc(1, signature_size);
if (signature == NULL) {
    // Handle error.
}

result = iqr_DilithiumSign(private_key, digest, digest_size, signature,
    signature_size);
if (result != IQR_OK) {
    // Handle error.
}

Verifying Signatures

To verify a signature using the Dilithium public key:

...
result = iqr_DilithiumVerify(public_key, digest, digest_size, signature,
    signature_size);
if (result != IQR_OK) {
    // Handle error.
}

Managing Keys

To export Dilithium keys for storage or transmission:

...
size_t public_key_data_size = 0;
result = iqr_DilithiumGetPublicKeySize(params, &public_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key_data = calloc(1, public_key_data_size);
if (public_key_data == NULL) {
    // Handle error.
}

result = iqr_DilithiumExportPublicKey(public_key, public_key_data,
    public_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

size_t private_key_data_size = 0;
result = iqr_DilithiumGetPrivateKeySize(params, &private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *private_key_data = calloc(1, private_key_data_size);
if (private_key_data == NULL) {
    // Handle error.
}

result = iqr_DilithiumExportPrivateKey(private_key, private_key_data,
    private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

To import Dilithium keys from buffers:

...
// Create an iqr_DilithiumParams object using the same variant that was used to
// create the keys.
...

iqr_DilithiumPrivateKey *private_key = NULL;
result = iqr_DilithiumImportPrivateKey(params, private_key_data,
    private_key_data_size, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

iqr_DilithiumPublicKey *public_key = NULL;
result = iqr_DilithiumImportPublicKey(params, public_key_data,
    public_key_data_size, &public_key);
if (result != IQR_OK) {
    // Handle error.
}

Public Key Format

The data produced by iqr_DilithiumExportPublicKey() follows the format documented in the CRYSTALS - Dilithium document:

uint8_t seed[32];  // The RNG seed used to create the public key.
uint8_t t1[];  // POLY16 encoded t1 data.

Signature Format

The signature produced by iqr_DilithiumSign() follows the format documented in section 4.1 of the CRYSTALS - Dilithium document:

uint8_t z[];
uint8_t h[];
uint8_t c[];

HSS (Hierarchical Signature Scheme)

The toolkit provides an implementation of the Hierarchical Signature Scheme (HSS) scheme as defined in the Hash-Based Signatures IETF Draft 13 document.

HSS is a signature scheme that has several major differences from classical digital signature schemes:

  • An HSS private key can only be used to sign a finite number of items.

  • You need to carefully maintain the state.

When you create an HSS key pair, you specify a variant that controls the number of one time signatures as well as the internal Winternitz value, which will affect the size of signatures. The variant can be one of:

  • IQR_HSS_2E30_FAST — 230 (1,073,741,824) one time signatures, trading speed for larger signatures.

  • IQR_HSS_2E30_SMALL — 230 (1,073,741,824) one time signatures, reducing speed for smaller signatures.

  • IQR_HSS_2E45_FAST — 245 (35,184,372,088,832) one time signatures, trading speed for larger signatures.

  • IQR_HSS_2E45_SMALL — 245 (35,184,372,088,832) one time signatures, reducing speed for smaller signatures.

  • IQR_HSS_2E65_FAST — 265 (36,893,488,147,419,103,232) one time signatures, trading speed for larger signatures.

  • IQR_HSS_2E65_SMALL — 265 (36,893,488,147,419,103,232) one time signatures, reducing speed for smaller signatures.

These variants correspond to the following parameters from the IETF draft:

Table 2. HSS Variant Parameters
Variant Layers Height Winternitz

IQR_HSS_2E20_FAST

1

20

2

IQR_HSS_2E20_SMALL

1

20

8

IQR_HSS_2E25_FAST

1

25

2

IQR_HSS_2E25_SMALL

1

25

8

IQR_HSS_2E30_FAST

2

15

2

IQR_HSS_2E30_SMALL

2

15

8

IQR_HSS_2E45_FAST

3

15

2

IQR_HSS_2E45_SMALL

3

15

8

IQR_HSS_2E65_FAST

5

15 and 10

2

IQR_HSS_2E65_SMALL

5

15 and 10

8

It’s up to the user to manage domain parameters.

The size of the HSS state varies depending on the variant and tree strategy used; see the Technical Information section for key and state sizes. HSS signature size is relative to the full tree height and Winternitz value.

Creating Keys

The toolkit lets you create HSS keys by specifying individual parameters.

To create an HSS key pair:

#include "iqr_context.h"
#include "iqr_hash.h"
#include "iqr_hss.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Register a SHA2-256 hash algorithm.
// Create and initialize a Random Number Generator, rng.
...
// Create HSS parameters.
iqr_HSSParams *params = NULL;
iqr_retval result = iqr_HSSCreateParams(context,
    &IQR_HSS_MEMORY_CONSTRAINED_STRATEGY, IQR_HSS_2E30_FAST, &params);
if (result != IQR_OK) {
    // Handle error.
}

// Create the key pair.
iqr_HSSPublicKey *public_key = NULL;
iqr_HSSPrivateKey *private_key = NULL;
iqr_HSSPrivateKeyState *state = NULL;
result = iqr_HSSCreateKeyPair(params, rng, &public_key, &private_key,
    &state);
if (result != IQR_OK) {
    // Handle error.
}

Detaching State

In some cases, you may need to split a key’s state. For example:

  • For disaster recovery, you can reserve some one time signatures and store the remaining state securely. If you lose power, the reserved signatures are lost, but the state can be recovered.

  • For load balancing, you can split the state, then share the unique sets of one time signatures across two or more systems.

This is accomplished by detaching the key state. Using iqr_HSSDetachState() will create a new state containing the number of signatures requested, and remove those signatures from the original state.

To detach num_sigs one time signatures from an HSS key’s state:

#include "iqr_hss.h"
...
// This assumes you've already got an HSS private key and state, and that
// you've registered a SHA2-256 hash implementation.
...
iqr_HSSPrivateKeyState *new_state = NULL;
iqr_retval result = iqr_HSSDetachState(private_key, original_state,
    num_sigs, &new_state)
if (result != IQR_OK) {
    // Handle error.
}

After calling iqr_HSSDetachState(), num_sigs one time signatures will have been removed from original_state. The new_state will have the removed signatures.

Signing

To sign a message using the HSS private key:

...
size_t signature_size = 0;
result = iqr_HSSGetSignatureSize(params, &signature_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *signature = calloc(1, signature_size);
if (signature == NULL) {
    // Handle error.
}

result = iqr_HSSSign(private_key, rng, message, message_size, state, signature,
    signature_size);
if (result != IQR_OK) {
    // Handle error.
}

/************************* CRITICALLY IMPORTANT STEP *************************

  If the state hasn't been split with iqr_HSSDetachState(), the state must be
  written to non-volatile memory before continuing.

  If the state has been split, discard the state after a catastrophic failure
  rather than re-using it.

  Failure to do so could result in a SECURITY BREACH as it could lead to the
  re-use of a one time signature.

  This step has been omitted for brevity.

  For more information about this property of the HSS state, please refer to
  the HSS specification.

 ****************************************************************************/

Verifying Signatures

To verify a signature using the HSS public key:

...
result = iqr_HSSVerify(public_key, message, message_size, signature,
    signature_size);
if (result != IQR_OK) {
    // Handle error.
}

Managing Keys

Be sure you’re not using the same private key on different systems unless you’ve used iqr_HSSDetachState(), and that you’re not re-using states.

To export HSS keys for storage or transmission:

...
size_t public_key_data_size = 0;
result = iqr_HSSGetPublicKeySize(params, &public_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key_data = calloc(1, public_key_data_size);
if (public_key_data == NULL) {
    // Handle error.
}

result = iqr_HSSExportPublicKey(public_key, public_key_data,
    public_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

size_t private_key_data_size = 0;
result = iqr_HSSGetPrivateKeySize(params, &private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *private_key_data = calloc(1, private_key_data_size);
if (private_key_data == NULL) {
    // Handle error.
}

result = iqr_HSSExportPrivateKey(private_key, private_key_data,
    private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

size_t state_size = 0;
result = iqr_HSSGetStateSize(params, &state_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *state_data = calloc(1, state_size);
if (state_data == NULL) {
    // Handle error.
}

result = iqr_HSSExportState(state, state_data, state_size);
if (result != IQR_OK) {
    // Handle error.
}

To import HSS keys from buffers:

...
// Create an iqr_HSSParams object using the same parameters that were used
// to create the keys.
...

iqr_HSSPublicKey *public_key = NULL;
result = iqr_HSSImportPublicKey(params, public_key_data, public_key_data_size,
    &public_key);
if (result != IQR_OK) {
    // Handle error.
}

iqr_HSSPrivateKey *private_key = NULL;
result = iqr_HSSImportPrivateKey(params, private_key_data,
    private_key_data_size, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

iqr_HSSPrivateKeyState *state = NULL;
result = iqr_HSSImportState(params, state_data, state_data_size, &state);
if (result != IQR_OK) {
    // Handle error.
}

Public Key Format

HSS public keys are stored in the format defined in the Hash-Based Signatures IETF Draft 15 document.

Signature Format

HSS signatures are stored in the format defined in the Hash-Based Signatures IETF Draft 15 document.

Rainbow Signature Scheme

The toolkit provides an implementation of the Rainbow signature scheme as defined in the Rainbow document.

Rainbow parameter sets are formatted as IQR_RAINBOW_GFf_V1_O1_O2, where "f" indicates the Galois field, "V1" is the number of vinegar variables in the first layer, "O1" is the number of oil variables in the first layer, and "O2" is the number of oil variables in the second layer.

These are organized in increasing strength, according to the NIST Post Quantum Cryptography contest groups; group "VI" in the Rainbow specification should be read as group "V" as there is no group "VI" and these parameters are at least as strong as (GF(256), 92, 48, 48).

  • IIIb (F, v1, o1, o2) = (GF(31), 64, 32, 48)

  • IIIc (F, v1, o1, o2) = (GF(256), 68, 36, 36)

  • IVa (F, v1, o1, o2) = (GF(16), 56, 48, 48)

  • Vc (F, v1, o1, o2) = (GF(256), 92, 48, 48)

  • VIa (F, v1, o1, o2) = (GF(16), 76, 64, 64)

  • VIb (F, v1, o1, o2) = (GF(31), 84, 56, 56)

Creating Keys

The toolkit lets you create Rainbow keys after creating and initializing a random number generator.

To create a Rainbow key pair:

#include "iqr_context.h"
#include "iqr_rainbow.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
// Create Rainbow parameters using one of the variants from iqr_rainbow.h.
iqr_RainbowParams *params = NULL;
iqr_retval result = iqr_RainbowCreateParams(context, variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

// Create the key pair.
iqr_RainbowPublicKey *public_key = NULL;
iqr_RainbowPrivateKey *private_key = NULL;
result = iqr_RainbowCreateKeyPair(params, rng, &public_key, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

Signing

To sign a message using the Rainbow private key:

...
size_t signature_size = 0;
result = iqr_RainbowGetSignatureSize(params, &signature_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *signature = calloc(1, signature_size);
if (signature == NULL) {
    // Handle error.
}

result = iqr_RainbowSign(private_key, rng, digest, digest_size,
    signature, signature_size);
if (result != IQR_OK) {
    // Handle error.
}

Verifying Signatures

To verify a signature using the Rainbow public key:

...
result = iqr_RainbowVerify(public_key, digest, digest_size, signature,
    signature_size);
if (result != IQR_OK) {
    // Handle error.
}

Managing Keys

To export Rainbow keys for storage or transmission:

...
size_t public_key_data_size = 0;
result = iqr_RainbowGetPublicKeySize(params, &public_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key_data = calloc(1, public_key_data_size);
if (public_key_data == NULL) {
    // Handle error.
}

result = iqr_RainbowExportPublicKey(public_key, public_key_data,
    public_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

size_t private_key_data_size = 0;
result = iqr_RainbowGetPrivateKeySize(params, &private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *private_key_data = calloc(1, private_key_data_size);
if (private_key_data == NULL) {
    // Handle error.
}

result = iqr_RainbowExportPrivateKey(private_key, private_key_data,
    private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

To import Rainbow keys from buffers:

...
// Create an iqr_RainbowParams object.
...

iqr_RainbowPublicKey *public_key = NULL;
result = iqr_RainbowImportPublicKey(params, public_key_data,
    public_key_data_size, &public_key);
if (result != IQR_OK) {
    // Handle error.
}

iqr_RainbowPrivateKey *private_key = NULL;
result = iqr_RainbowImportPrivateKey(params, private_key_data,
    private_key_data_size, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

Public Key Format

The data produced by iqr_RainbowExportPublicKey() follows the format specified in the Rainbow document.

Signature Format

The signature produced by iqr_RainbowSign() follows the format specified in the Rainbow document.

SPHINCS+ Signature Scheme

The toolkit provides an implementation of the SPHINCS+ signature scheme as defined in the SPHINCS+ document.

SPHINCS+ is a stateless hash based signature scheme. Unlike HSS and XMSS, there’s no state that needs to be carefully maintained while signing messages.

The toolkit currently supports four variants, all using SHAKE-256 as the internal hash:

  • IQR_SPHINCS_SHAKE_256_192F — 192 bits of security, fast variant.

  • IQR_SPHINCS_SHAKE_256_192S — 192 bits of security, small variant.

  • IQR_SPHINCS_SHAKE_256_256F — 256 bits of security, fast variant.

  • IQR_SPHINCS_SHAKE_256_256S — 256 bits of security, small variant.

The fast variants sign messages faster in exchange for larger signatures. The small variants sign slower but produce smaller signatures. See SPHINCS+ keys and signatures for sizes.

Signature verification speeds are unaffected by the variant.

Creating Keys

The toolkit lets you create SPHINCS+ keys after creating and initializing a random number generator.

To create a SPHINCS+ key pair:

#include "iqr_context.h"
#include "iqr_rng.h"
#include "iqr_sphincs.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
// Create SPHINCS+ parameters using one of the variants from iqr_sphincs.h.
iqr_SPHINCSParams *params = NULL;
iqr_retval result = iqr_SPHINCSCreateParams(context, variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

// Create the key pair.
iqr_SPHINCSPublicKey *public_key = NULL;
iqr_SPHINCSPrivateKey *private_key = NULL;
result = iqr_SPHINCSCreateKeyPair(params, rng, &public_key, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

Signing

To sign a message using the SPHINCS+ private key:

...
size_t signature_size = 0;
result = iqr_SPHINCSGetSignatureSize(params, &signature_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *signature = calloc(1, signature_size);
if (signature == NULL) {
    // Handle error.
}

result = iqr_SPHINCSSign(private_key, rng, digest, digest_size,
    signature, signature_size);
if (result != IQR_OK) {
    // Handle error.
}

Verifying Signatures

To verify a signature using the SPHINCS+ public key:

...
result = iqr_SPHINCSVerify(public_key, digest, digest_size, signature,
    signature_size);
if (result != IQR_OK) {
    // Handle error.
}

Managing Keys

To export SPHINCS+ keys for storage or transmission:

...
size_t public_key_data_size = 0;
result = iqr_SPHINCSGetPublicKeySize(params, &public_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key_data = calloc(1, public_key_data_size);
if (public_key_data == NULL) {
    // Handle error.
}

result = iqr_SPHINCSExportPublicKey(public_key, public_key_data,
    public_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

size_t private_key_data_size = 0;
result = iqr_SPHINCSGetPrivateKeySize(params, &private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *private_key_data = calloc(1, private_key_data_size);
if (private_key_data == NULL) {
    // Handle error.
}

result = iqr_SPHINCSExportPrivateKey(private_key, private_key_data,
    private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

To import SPHINCS+ keys from buffers:

...
// Create an iqr_SPHINCSParams object.
...

iqr_SPHINCSPublicKey *public_key = NULL;
result = iqr_SPHINCSImportPublicKey(params, public_key_data,
    public_key_data_size, &public_key);
if (result != IQR_OK) {
    // Handle error.
}

iqr_SPHINCSPrivateKey *private_key = NULL;
result = iqr_SPHINCSImportPrivateKey(params, private_key_data,
    private_key_data_size, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

Public Key Format

The data produced by iqr_SPHINCSExportPublicKey() follows the format specified in the SPHINCS+ document.

Signature Format

The signature produced by iqr_SPHINCSSign() follows the format specified in the SPHINCS+ document.

XMSS (eXtended Merkle Signature Scheme)

The toolkit provides an implementation of the eXtended Merkle Signature Scheme (XMSS) scheme as defined in the XMSS: eXtended Merkle Signature Scheme document.

XMSS is a one time signature scheme that has several major differences from classical digital signature schemes:

  • An XMSS private key can only be used to sign a finite number of items.

  • You need to carefully maintain the state.

When you create an XMSS key pair, you specify the variant (iqr_XMSSVariant) parameter, which specifies the tree height. The height controls the number of one time signatures available in the private key. The variant can be one of:

  • IQR_XMSS_2E10 — 210 (1024) one time signatures

  • IQR_XMSS_2E16 — 216 (65,536) one time signatures

  • IQR_XMSS_2E20 — 220 (1,048,576) one time signatures

It’s up to the user to manage domain parameters; the parameter data is not exposed in stored keys or signatures.

The state, specified when signing, tracks the one time signature tree state. Re-using a one time signature destroys the security of the scheme, so be careful to:

  • Not re-use a state when signing.

  • Safely and securely store your state before publishing the signature to protect against software crashes or power loss.

XMSS state is larger depending on the tree height; see the Technical Information section for sizes. XMSS signatures grow relative to the full tree height.

XMSSMT (multi-tree XMSS) is the multi-tree version of XMSS. It lets you produce more signatures without having to generate keys for huge single trees.

Creating Keys

The toolkit lets you create XMSS keys by specifying individual parameters.

To create an XMSS key pair:

#include "iqr_context.h"
#include "iqr_hash.h"
#include "iqr_rng.h"
#include "iqr_xmss.h"
...
// Create an iqr_Context, context.
// Register a SHA2-256 hash algorithm.
// Create and initialize a Random Number Generator, rng.
...
// Create XMSS parameters.
iqr_XMSSParams *params = NULL;
iqr_retval result = iqr_XMSSCreateParams(context, &IQR_XMSS_FULL_TREE_STRATEGY,
    variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

// Create the key pair.
iqr_XMSSPublicKey *public_key = NULL;
iqr_XMSSPrivateKey *private_key = NULL;
iqr_XMSSPrivateKeyState *state = NULL;
result = iqr_XMSSCreateKeyPair(params, rng, &public_key, &private_key,
    &state);
if (result != IQR_OK) {
    // Handle error.
}

Detaching State

In some cases, you may need to split a key’s state. For example:

  • For disaster recovery, you can reserve some one time signatures and store the remaining state securely. If you lose power, the reserved signatures are lost, but the state can be recovered.

  • For load balancing, you can split the state, then share the unique sets of one time signatures across two or more systems.

This is accomplished by detaching the key state. Using iqr_XMSSDetachState() will create a new state containing the number of signatures requested, and remove those signatures from the original state.

To detach num_sigs one time signatures from an XMSS key’s state:

#include "iqr_xmss.h"
...
// This assumes you've already got an XMSS private key and state, and that
// you've registered a SHA2-256 hash implementation.
...
iqr_XMSSPrivateKeyState *new_state = NULL;
iqr_retval result = iqr_XMSSDetachState(private_key, original_state,
    num_sigs, &new_state)
if (result != IQR_OK) {
    // Handle error.
}

After calling iqr_XMSSDetachState(), num_sigs one time signatures will have been removed from original_state. The new_state will have the removed signatures.

Signing

To sign a message using the XMSS private key:

...
size_t signature_size = 0;
result = iqr_XMSSGetSignatureSize(params, &signature_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *signature = calloc(1, signature_size);
if (signature == NULL) {
    // Handle error.
}

result = iqr_XMSSSign(private_key, rng, message, message_size, state, signature,
    signature_size);
if (result != IQR_OK) {
    // Handle error.
}

/************************* CRITICALLY IMPORTANT STEP *************************

  If the state hasn't been split with iqr_XMSSDetachState(), the state must be
  written to non-volatile memory before continuing.

  If the state has been split, discard the state after a catastrophic failure
  rather than re-using it.

  Failure to do so could result in a SECURITY BREACH as it could lead to the
  re-use of a one time signature.

  This step has been omitted for brevity.

  For more information about this property of the XMSS private key, please
  refer to the XMSS specification.

 ****************************************************************************/

Verifying Signatures

To verify a signature using the XMSS public key:

...
result = iqr_XMSSVerify(public_key, message, message_size, signature,
    signature_size);
if (result != IQR_OK) {
    // Handle error.
}

Managing Keys

Be sure you’re not using the same private key on different systems unless you’ve used iqr_XMSSDetachState(), and that you’re not re-using states.

To export XMSS keys for storage or transmission:

...
size_t public_key_data_size = 0;
result = iqr_XMSSGetPublicKeySize(params, &public_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key_data = calloc(1, public_key_data_size);
if (public_key_data == NULL) {
    // Handle error.
}

result = iqr_XMSSExportPublicKey(public_key, public_key_data,
    public_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

size_t private_key_data_size = 0;
result = iqr_XMSSGetPrivateKeySize(params, &private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *private_key_data = calloc(1, private_key_data_size);
if (private_key_data == NULL) {
    // Handle error.
}

result = iqr_XMSSExportPrivateKey(private_key, private_key_data,
    private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

size_t state_size = 0;
result = iqr_XMSSGetStateSize(params, &state_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *state_data = calloc(1, state_size);
if (state_data == NULL) {
    // Handle error.
}

result = iqr_XMSSExportState(state, state_data, state_size);
if (result != IQR_OK) {
    // Handle error.
}

To import XMSS keys from buffers:

...
// Create an iqr_XMSSParams object using the same parameters that were used
// to create the keys.
...

iqr_XMSSPublicKey *public_key = NULL;
result = iqr_XMSSImportPublicKey(params, public_key_data, public_key_data_size,
    &public_key);
if (result != IQR_OK) {
    // Handle error.
}

iqr_XMSSPrivateKey *private_key = NULL;
result = iqr_XMSSImportPrivateKey(params, private_key_data,
    private_key_data_size, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

iqr_XMSSPrivateKeyState *state = NULL;
result = iqr_XMSSImportState(params, state_data, state_data_size, &state);
if (result != IQR_OK) {
    // Handle error.
}

Public Key Format

The data produced by iqr_XMSSExportPublicKey() follows the format documented in section 4.1.7 of the XMSS RFC.

Supported oid values for the toolkit match these values from section 8 of the IETF document:

  • XMSS-SHA2_10_256

  • XMSS-SHA2_16_256

  • XMSS-SHA2_20_256

Signature Format

The signature produced by iqr_XMSSSign() follows the format documented in section 4.1.8 of the XMSS RFC.

XMSSMT (Multi-tree XMSS)

The toolkit provides an implementation of the multi-tree eXtended Merkle Signature Scheme (XMSSMT) scheme as defined in the XMSS: eXtended Merkle Signature Scheme document.

XMSSMT is a one time signature scheme that has several major differences from classical digital signature schemes:

  • An XMSSMT private key can only be used to sign a finite number of items.

  • You need to carefully maintain the state.

When you create an XMSSMT key pair, you specify the tree parameters (iqr_XMSSMTTreeParams). The tree parameters control the number of one time signatures available in the private key by specifying the number of layers and the number of trees. The tree parameters can be one of these variants:

  • IQR_XMSSMT_2E20_2D — 220 (1,048,576) one time signatures with two layers.

  • IQR_XMSSMT_2E20_4D — 220 (1,048,576) one time signatures with four layers.

  • IQR_XMSSMT_2E40_2D — 240 (1,099,511,627,776) one time signatures with two layers.

  • IQR_XMSSMT_2E40_4D — 240 (1,099,511,627,776) one time signatures with four layers.

  • IQR_XMSSMT_2E40_8D — 240 (1,099,511,627,776) one time signatures with eight layers.

  • IQR_XMSSMT_2E60_3D — 260 (1,152,921,504,606,846,976) one time signatures with three layers.

  • IQR_XMSSMT_2E60_6D — 260 (1,152,921,504,606,846,976) one time signatures with six layers.

  • IQR_XMSSMT_2E60_12D — 260 (1,152,921,504,606,846,976) one time signatures with twelve layers.

With these variants, the first number (20, 40, or 60) is the total height of the tree, and the second number is the number of layers. So, the IQR_XMSSMT_2E60_6D variant creates a hyper-tree made up of 6 layers of trees; each of those sub- trees has a height of 10, for a full height of 60. More layers mean smaller individual sub-trees, which makes key generation faster.

As the signatures in a smaller sub-tree are used up, the next sub-tree is being generated at a smaller incremental cost. This layering of trees makes it possible to provide a very large number of signatures without having to construct an enormous tree at key generation time.

It’s up to the user to manage domain parameters; the parameter data is not exposed in stored keys or signatures.

The state, specified when signing, tracks the one time signature tree state. Re-using a one time signature destroys the security of the scheme, so be careful to:

  • Not re-use a state when signing.

  • Safely and securely store your state before publishing the signature to protect against software crashes or power loss.

XMSSMT state is larger depending on the tree height; see the Technical Information section for sizes. XMSSMT signatures grow relative to the full tree height.

You can use XMSS (single-tree XMSS) if you need a smaller number of signatures.

Creating Keys

The toolkit lets you create XMSSMT keys by specifying individual parameters.

To create an XMSSMT key pair:

#include "iqr_context.h"
#include "iqr_hash.h"
#include "iqr_rng.h"
#include "iqr_xmssmt.h"
...
// Create an iqr_Context, context.
// Register a SHA2-256 hash algorithm.
// Create and initialize a Random Number Generator, rng.
...
// Create XMSS^MT^ parameters.
iqr_XMSSMTParams *params = NULL;
iqr_retval result = iqr_XMSSMTCreateParams(context, &IQR_XMSS_FULL_TREE_STRATEGY,
    variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

// Create the key pair.
iqr_XMSSMTPublicKey *public_key = NULL;
iqr_XMSSMTPrivateKey *private_key = NULL;
iqr_XMSSMTPrivateKeyState *state = NULL;
result = iqr_XMSSMTCreateKeyPair(params, rng, &public_key, &private_key,
    &state);
if (result != IQR_OK) {
    // Handle error.
}

Detaching State

In some cases, you may need to split a key’s state. For example:

  • For disaster recovery, you can reserve some one time signatures and store the remaining state securely. If you lose power, the reserved signatures are lost, but the state can be recovered.

  • For load balancing, you can split the state, then share the unique sets of one time signatures across two or more systems.

This is accomplished by detaching the key state. Using iqr_XMSSMTDetachState() will create a new state containing the number of signatures requested, and remove those signatures from the original state.

To detach num_sigs one time signatures from an XMSSMT key’s state:

#include "iqr_xmssmt.h"
...
// This assumes you've already got an XMSS^MT^ private key and state, and that
// you've registered a SHA2-256 hash implementation.
...
iqr_XMSSMTPrivateKeyState *new_state = NULL;
iqr_retval result = iqr_XMSSMTDetachState(private_key, original_state,
    num_sigs, &new_state)
if (result != IQR_OK) {
    // Handle error.
}

After calling iqr_XMSSMTDetachState(), num_sigs one time signatures will have been removed from original_state. The new_state will have the removed signatures.

Signing

To sign a message using the XMSSMT private key:

...
size_t signature_size = 0;
result = iqr_XMSSMTGetSignatureSize(params, &signature_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *signature = calloc(1, signature_size);
if (signature == NULL) {
    // Handle error.
}

result = iqr_XMSSMTSign(private_key, rng, message, message_size, state,
    signature, signature_size);
if (result != IQR_OK) {
    // Handle error.
}

/************************* CRITICALLY IMPORTANT STEP *************************

  If the state hasn't been split with iqr_XMSSMTDetachState(), the state must be
  written to non-volatile memory before continuing.

  If the state has been split, discard the state after a catastrophic failure
  rather than re-using it.

  Failure to do so could result in a SECURITY BREACH as it could lead to the
  re-use of a one time signature.

  This step has been omitted for brevity.

  For more information about this property of the XMSS^MT^ private key, please
  refer to the XMSS specification.

 ****************************************************************************/

Verifying Signatures

To verify a signature using the XMSSMT public key:

...
result = iqr_XMSSMTVerify(public_key, message, message_size, signature,
    signature_size);
if (result != IQR_OK) {
    // Handle error.
}

Managing Keys

Be sure you’re not using the same private key on different systems unless you’ve used iqr_XMSSMTDetachState(), and that you’re not re-using states.

To export XMSSMT keys for storage or transmission:

...
size_t public_key_data_size = 0;
result = iqr_XMSSMTGetPublicKeySize(params, &public_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key_data = calloc(1, public_key_data_size);
if (public_key_data == NULL) {
    // Handle error.
}

result = iqr_XMSSMTExportPublicKey(public_key, public_key_data,
    public_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

size_t private_key_data_size = 0;
result = iqr_XMSSMTGetPrivateKeySize(params, &private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *private_key_data = calloc(1, private_key_data_size);
if (private_key_data == NULL) {
    // Handle error.
}

result = iqr_XMSSMTExportPrivateKey(private_key, private_key_data,
    private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

size_t state_size = 0;
result = iqr_XMSSMTGetStateSize(params, &state_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *state_data = calloc(1, state_size);
if (state_data == NULL) {
    // Handle error.
}

result = iqr_XMSSMTExportState(state, state_data, state_size);
if (result != IQR_OK) {
    // Handle error.
}

To import XMSSMT keys from buffers:

...
// Create an iqr_XMSSMTParams object using the same parameters that were used
// to create the keys.
...

iqr_XMSSMTPublicKey *public_key = NULL;
result = iqr_XMSSMTImportPublicKey(params, public_key_data,
    public_key_data_size, &public_key);
if (result != IQR_OK) {
    // Handle error.
}

iqr_XMSSMTPrivateKey *private_key = NULL;
result = iqr_XMSSMTImportPrivateKey(params, private_key_data,
    private_key_data_size, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

iqr_XMSSMTPrivateKeyState *state = NULL;
result = iqr_XMSSMTImportState(params, state_data, state_data_size, &state);
if (result != IQR_OK) {
    // Handle error.
}

Public Key Format

The data produced by iqr_XMSSMTExportPublicKey() follows the format documented in section 4.2.2 of the XMSS RFC.

Supported oid values for the toolkit match these values from section 8 of the IETF document:

  • XMSSMT-SHA2_20/2_256

  • XMSSMT-SHA2_20/4_256

  • XMSSMT-SHA2_40/2_256

  • XMSSMT-SHA2_40/4_256

  • XMSSMT-SHA2_40/8_256

  • XMSSMT-SHA2_60/3_256

  • XMSSMT-SHA2_60/6_256

  • XMSSMT-SHA2_60/12_256

Signature Format

The signature produced by iqr_XMSSMTSign() follows the format documented in section 4.2.3 of the XMSS RFC.

Key Agreement

The toolkit provides four key agreement schemes:

  • FrodoDH — a lattice based key agreement scheme

  • NewHopeDH — an ideal lattice based key agreement scheme

  • SIDH (Supersingular-Isogeny Diffie-Hellman) — an isogeny based key agreement scheme

FrodoDH and NewHopeDH differ from standard Diffie-Hellman. In these schemes, whoever starts the key exchange protocol is the Initiator, and the other is the Responder. The Initiator and the Responder then exchange keys in the following modified manner:

  1. The Initiator and the Responder agree on a set of public parameters.

  2. The Initiator creates a public/private key pair.

  3. The Initiator sends their public key to the Responder.

  4. The Responder creates a private key, then uses it and the Initiator’s public key to create their own public key.

  5. The Responder sends their public key to the Initiator.

  6. The Initiator uses their private key and the Responder’s public key to calculate the shared secret.

  7. The Responder uses their private key to calculate the shared secret.

The last two steps can occur simultaneously.

SIDH also differs from standard Diffie-Hellman. In SIDH, one end of the connection must be Alice, the other Bob. It doesn’t matter which is which, as long as they’re not the same.

  1. Alice and Bob agree on a set of public parameters.

  2. Alice creates a private key.

  3. Alice uses her private key to derive a public key.

  4. Alice sends the public key to Bob.

  5. Bob creates a private key.

  6. Bob uses his private key to derive a public key.

  7. Bob sends the public key to Alice.

  8. Alice uses her private key and Bob’s public key to derive the shared secret.

  9. Bob uses his private key and Alice’s public key to calculate the shared secret.

Steps 2-4 for Alice and 5-7 for Bob can be done simultaneously, since the calculations are done independent of the other.

Note
The shared secrets generated by these key agreement schemes are ephemeral. You cannot re-use keys to generate additional shared secrets.

Key Agreement Usage

  1. Decide who gets to be Alice (or the Initiator), and who gets to be Bob (or the Responder).

  2. Alice creates a new private key and uses it to derive a public key.

    • Alice and Bob must create a new private key and public key every time.

  3. Alice sends her public key to Bob.

  4. Bob creates a new private key and uses it, plus Alice’s public key, to derive his own public key.

  5. Bob uses his private key to derive the shared secret.

  6. Bob sends his public key to Alice.

  7. Alice uses her private key and Bob’s public key to derive the shared secret.

  8. Feed the shared secret to a Key Derivation Function to get a shared key.

  9. Use the shared key for symmetric encryption (for example).

Note
After the agreement, be sure to destroy the keys. Depending on the algorithm, it might not be safe to re-use them for additional key agreements.
How key agreement schemes work.

Perfect Forward Secrecy

To preserve perfect forward secrecy, use a new private key for each transaction. Treat them as ephemeral.

To help enforce this, the private key Export() and Import() functions will be removed in a future version of the toolkit.

FrodoDH

The toolkit provides two variants of the FrodoDH lattice based key agreement scheme:

  • The paper’s "recommended" implementation.

  • The paper’s "paranoid" implementation.

Depending on the protocol you’re implementing, either side of the agreement could be acting as Initiator or Responder.

It’s up to the user to manage domain parameters; the parameter data is not exposed in stored keys or secrets.

There is a small (2-34) chance that a FrodoDH key agreement will fail due to the math involved. To the algorithm, this looks like any other key agreement failure. Start the agreement process over with a new Initiator private key.

FrodoDH’s variants choose the algorithm used internally to generate a pseudo-random function (AES or cSHAKE). Both variants provide the same level of security.

Initiator

To create a key pair:

#include "iqr_context.h"
#include "iqr_frododh.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
// The variant can be &IQR_FRODODH_RECOMMENDED or &IQR_FRODODH_PARANOID
// depending on which implementation the Responder is using.
iqr_FrodoDHParams *params = NULL;
iqr_retval result = iqr_FrodoDHCreateParams(context, variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

iqr_FrodoDHInitiatorPrivateKey *private_key = NULL;
result = iqr_FrodoDHCreateInitiatorPrivateKey(params, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

size_t public_key_size = 0;
result = iqr_FrodoDHGetInitiatorPublicKeySize(params, &public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key = calloc(1, public_key_size);
if (public_key == NULL) {
    // Handle error.
}

result = iqr_FrodoDHGetInitiatorPublicKey(private_key, rng, public_key,
        public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The public_key is now sent to the Responder.
...

To calculate the shared secret using the Responder’s public key:

...
size_t responder_public_key_size = 0;
result = iqr_FrodoDHGetResponderPublicKeySize(params, &responder_public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *responder_public_key = calloc(1, responder_public_key_size);
if (responder_public_key == NULL) {
    // Handle error.
}

// Receive the Responder's public key.
...

size_t shared_secret_size = 0;
result = iqr_FrodoDHGetSecretSize(prams, &shared_secret_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_secret = calloc(1, shared_secret_size);
if (shared_secret == NULL) {
    // Handle error.
}

result = iqr_FrodoDHGetInitiatorSecret(private_key, responder_public_key,
    responder_public_key_size, shared_secret, shared_secret_size);
if (result != IQR_OK) {
    // Handle error.
}

Responder

To create a key pair using the Initiator’s public key:

#include "iqr_context.h"
#include "iqr_frododh.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
// The variant can be &IQR_FRODODH_RECOMMENDED or &IQR_FRODODH_PARANOID
// depending on which implementation the Initiator is using.
iqr_FrodoDHParams *params = NULL;
iqr_retval result = iqr_FrodoDHCreateParams(context, variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

size_t initiator_public_key_size = 0;
result = iqr_FrodoDHGetInitiatorPublicKeySize(params, &public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *initiator_public_key = calloc(1, initiator_public_key_size);
if (inititator_public_key == NULL) {
    // Handle error.
}

// Receive the Initiator's public key.
...

iqr_FrodoDHResponderPrivateKey *private_key = NULL;
result = iqr_FrodoDHCreateResponderPrivateKey(params, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

size_t responder_public_key_size = 0;
result = iqr_FrodoDHGetResponderPublicKeySize(params, &responder_public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

result = iqr_FrodoDHGetResponderPublicKey(private_key, rng,
        initiator_public_key, initiator_public_key_size, responder_public_key,
        responder_public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// Send the Responder's public key to the Initiator.
...

To calculate the shared secret using the Responder’s private key:

...
size_t shared_secret_size = 0;
result = iqr_FrodoDHGetSecretSize(params, &shared_secret_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_secret = calloc(1, shared_secret_size);
if (shared_secret == NULL) {
    // Handle error.
}

result = iqr_FrodoDHGetResponderSecret(private_key, shared_secret,
        shared_secret_size);
if (result != IQR_OK) {
    // Handle error.
}
...

NewHopeDH

The toolkit provides the NewHopeDH lattice based key agreement scheme.

Depending on the protocol you’re implementing, either side of the agreement could be acting as Initiator or Responder.

It’s up to the user to manage domain parameters; the parameter data is not exposed in stored keys or secrets.

There is a small (2-60) chance that a NewHopeDH key agreement will fail due to the math involved. To the algorithm, this looks like any other key agreement failure. Start the agreement process over with a new Initiator private key.

Initiator

To create a key pair:

#include "iqr_context.h"
#include "iqr_newhopedh.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
iqr_NewHopeDHParams *params = NULL;
iqr_retval result = iqr_NewHopeDHCreateParams(context, &params);
if (result != IQR_OK) {
    // Handle error.
}

iqr_NewHopeDHInitiatorPrivateKey *private_key = NULL;
result = iqr_NewHopeDHCreateInitiatorPrivateKey(params, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

size_t public_key_size = 0;
result = iqr_NewHopeDHGetInitiatorPublicKeySize(params, &public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key = calloc(1, public_key_size);
if (public_key == NULL) {
    // Handle error.
}

result = iqr_NewHopeDHGetInitiatorPublicKey(private_key, rng,
    public_key, public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The public_key is now sent to the Responder.
...

To calculate the shared secret using the Responder’s public key:

...
size_t responder_public_key_size = 0;
result = iqr_NewHopeDHGetResponderPublicKeySize(params, &responder_public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *responder_public_key = calloc(1, responder_public_key_size);
if (responder_public_key == NULL) {
    // Handle error.
}

// Receive the Responder's public key.
...

size_t shared_secret_size = 0;
result = iqr_NewHopeDHGetSecretSize(params, &shared_secret_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_secret = calloc(1, shared_secret_size);
if (shared_secret == NULL) {
    // Handle error.
}

result = iqr_NewHopeDHGetInitiatorSecret(private_key, responder_public_key,
    responder_public_key_size, shared_secret, shared_secret_size);
if (result != IQR_OK) {
    // Handle error.
}

Responder

To create a key pair using the Initiator’s public key:

#include "iqr_context.h"
#include "iqr_newhopedh.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
size_t initiator_public_key_size = 0;
result = iqr_NewHopeDHGetInitiatorPublicKeySize(params, &initiator_public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *initiator_public_key = calloc(1, initiator_public_key_size);
if (inititator_public_key == NULL) {
    // Handle error.
}

// Receive the Initiator's public key.
...

iqr_NewHopeDHParams *params = NULL;
iqr_retval result = iqr_NewHopeDHCreateParams(context, &params);
if (result != IQR_OK) {
    // Handle error.
}

iqr_NewHopeDHResponderPrivateKey *private_key = NULL;
result = iqr_NewHopeDHCreateResponderPrivateKey(params, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

size_t responder_public_key_size = 0;
result = iqr_NewHopeDHGetResponderPublicKeySize(params, &responder_public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *responder_public_key = calloc(1, responder_public_key_size);
if (responder_public_key == NULL) {
    // Handle error.
}

result = iqr_NewHopeDHGetResponderPublicKey(private_key, rng,
    initiator_public_key, initiator_public_key_size, responder_public_key,
    responder_public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// Send the Responder's public key to the Initiator.
...

To calculate the shared secret using the Responder’s private key:

...
size_t shared_secret_size = 0;
result = iqr_NewHopeDHGetSecretSize(params, &shared_secret_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_secret = calloc(1, shared_secret_size);
if (shared_secret == NULL) {
    // Handle error.
}

result = iqr_NewHopeDHGetResponderSecret(private_key, shared_secret,
    shared_secret_size);
if (result != IQR_OK) {
    // Handle error.
}
...

SIDH

The toolkit provides two variants of the SIDH isogeny based key agreement scheme:

  • IQR_SIDH_P503 - Parameters using the p503 curve.

  • IQR_SIDH_P751 - NIST submission parameters using the p751 curve.

Depending on the protocol you’re implementing, either side of the agreement could be acting as Alice or Bob. It doesn’t matter which is which, as long as they’re not the same.

It’s up to the user to manage domain parameters; the parameter data is not exposed in stored keys or secrets.

Unlike the lattice based key agreement schemes, Alice and Bob can generate their keys simultaneously, without input from the other side of the transaction.

Alice and Bob are, of course, famous.

Per the paper On The Cost Of Computing Isogenies Between Supersingular Elliptic Curves by Adj, Cervantes-Vázquez, Chi-Domínguez, and Rodríguez-Henríquez, the p503 parameters provide 128 bits of quantum security, while the p751 curve provides 188 bits. Based on the NIST submission, these curves provide 84 and 128 bits of quantum security respectively. We provide both so you can choose appropriately for your threat model; select the p503 curve for greater efficiency, or the p751 curve for greater assurance of security.

Alice

To create a key pair for Alice:

#include "iqr_context.h"
#include "iqr_rng.h"
#include "iqr_sidh.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
// Create SIDH parameters using one of the variants from iqr_sidh.h.
iqr_SIDHParams *params = NULL;
iqr_retval result = iqr_SIDHCreateParams(context, variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

iqr_SIDHAlicePrivateKey *alice_private_key = NULL;
result = iqr_SIDECreateAlicePrivateKey(params, rng, &alice_private_key);
if (result != IQR_OK) {
    // Handle error.
}

size_t alice_public_key_size = 0;
result = iqr_SIDHGetPublicKeySize(params, &alice_public_key_size);
uint8_t *alice_public_key = calloc(1, alice_public_key_size);
if (alice_public_key == NULL) {
    // Handle error.
}

result = iqr_SIDHGetAlicePublicKey(alice_private_key,
    alice_public_key, alice_public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The alice_public_key is now sent to Bob.
...

To calculate the shared secret using Bob’s public key:

...
size_t bob_public_key_size = 0;
result = iqr_SIDHGetPublicKeySize(params, &bob_public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *bob_public_key = calloc(1, bob_public_key_size);
if (bob_public_key == NULL) {
    // Handle error.
}

// Receive Bob's public key.
...

size_t shared_secret_size = 0;
result = iqr_SIDHGetSecretSize(params, &shared_secret_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_secret = calloc(1, shared_secret_size);
if (shared_secret == NULL) {
    // Handle error.
}

result = iqr_SIDHGetAliceSecret(alice_private_key, bob_public_key,
    bob_public_key_size, shared_secret, shared_secret_size);
if (result != IQR_OK) {
    // Handle error.
}

Bob

To create a key pair for Bob:

#include "iqr_context.h"
#include "iqr_rng.h"
#include "iqr_sidh.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
iqr_SIDHParams *params = NULL;
iqr_retval result = iqr_SIDHCreateParams(context, &params);
if (result != IQR_OK) {
    // Handle error.
}

iqr_SIDHBobPrivateKey *bob_private_key = NULL;
result = iqr_SIDECreateBobPrivateKey(params, rng, &bob_private_key);
if (result != IQR_OK) {
    // Handle error.
}

size_t bob_public_key_size = 0;
result = iqr_SIDHGetPublicKeySize(params, &bob_public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *bob_public_key = calloc(1, bob_public_key_size);
if (bob_public_key == NULL) {
    // Handle error.
}

result = iqr_SIDHGetBobPublicKey(bob_private_key,
    bob_public_key, bob_public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The bob_public_key is now sent to Alice.
...

To calculate the shared secret using Alice’s public key:

...
size_t alice_public_key_size = 0;
result = iqr_SIDHGetPublicKeySize(params, &alice_public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *alice_public_key = calloc(1, alice_public_key_size);
if (alice_public_key == NULL) {
    // Handle error.
}

// Receive Alice's public key.
...

size_t shared_secret_size = 0;
result = iqr_SIDHGetSecretSize(params, &shared_secret_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_secret = calloc(1, shared_secret_size);
if (shared_secret == NULL) {
    // Handle error.
}

result = iqr_SIDHGetBobSecret(bob_private_key, alice_public_key,
    alice_public_key_size, shared_secret, shared_secret_size);
if (result != IQR_OK) {
    // Handle error.
}

Key Encapsulation

The toolkit provides four key encapsulation mechanisms (KEMs):

  • Classic McEliece — a code based KEM

  • FrodoKEM — a lattice based KEM

  • Kyber — a lattice based KEM

  • NTRUPrime — a lattice based KEM

  • SIKE — (Supersingular-Isogeny Key Encapsulation) — an isogeny based KEM

These schemes let you create and cryptographically encapsulate a symmetric key for safe transmission. They work similar to public key encryption schemes.

To show how Alice and Bob would use a KEM:

  1. Bob generates a KEM key pair.

  2. Bob sends the KEM public key to Alice.

  3. Alice uses Bob’s public key to generate a shared secret and a ciphertext.

  4. Alice sends the ciphertext to Bob.

  5. Bob uses the KEM’s private key to decapsulate the ciphertext, giving him the original shared secret.

  6. Alice and Bob independently derive an encryption key from the shared secret.

Note
If your protocol requires perfect forward secrecy, you must treat the KEM private keys as ephemeral keys; don’t re-use them.

Key Encapsulation Mechanism Usage

  1. Alice generates a keypair.

    • Alice makes her public key available to anyone.

    • Alice keeps her private key secret.

  2. Bob uses Alice’s public key to generate a shared secret and encapsulate it as a ciphertext.

    • Bob sends the ciphertext back to Alice.

  3. Alice uses her private key and the ciphertext to decapsulate the shared secret.

    • When they’re done, both Alice and Bob have the same shared secret.

  4. Use the shared secret for symmetric encryption (for example).

Depending on your protocol, you might also feed the shared secret to a Key Derivation Function to get a shared encryption key.

How key encapsulation mechanisms work.

Classic McEliece

The toolkit provides two variants of Classic McEliece:

  • IQR_CLASSICMCELIECE_6 - The "6960119" variant from the specification.

  • IQR_CLASSICMCELIECE_8 - The "8192128" variant from the specification.

Both provide the same general level of security. IQR_CLASSICMCELIECE_8 produces larger keys and ciphertext and requires more CPU resources in exchange for stronger security.

It’s up to the user to manage domain parameters (the Classic McEliece variant); the parameter data is not exposed in stored keys or ciphertexts.

Creating Keys

To create a key pair:

#include "iqr_classicmceliece.h"
#include "iqr_context.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
// The variant can be IQR_CLASSICMCELIECE_6 or IQR_CLASSICMCELIECE_8.
iqr_ClassicMcElieceParams *params = NULL;
iqr_retval result = iqr_ClassicMcElieceCreateParams(context, variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

iqr_ClassicMcEliecePrivateKey *private_key = NULL;
iqr_ClassicMcEliecePublicKey *public_key = NULL;
result = iqr_ClassicMcElieceCreateKeyPair(params, rng, &public_key,
    &private_key);
if (result != IQR_OK) {
    // Handle error.
}

size_t public_key_size = 0;
result = iqr_ClassicMcElieceGetPublicKeySize(params, &public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key_buffer = calloc(1, public_key_size);
if (public_key == NULL) {
    // Handle error.
}

result = iqr_ClassicMcElieceExportPublicKey(public_key, public_key_buffer,
    public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The public_key can now be sent to other users.
...

Encapsulating the Secret

To encapsulate a shared secret using the public key:

size_t shared_key_size = 0;
result = iqr_ClassicMcElieceGetSharedKeySize(params, &shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_key = calloc(1, shared_key_size);
if (shared_key == NULL) {
    // Handle error.
}

size_t ciphertext_size = 0;
result = iqr_ClassicMcElieceGetCiphertextSize(params, &ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *ciphertext = calloc(1, ciphertext_size);
if (ciphertext == NULL) {
    // Handle error.
}

result = iqr_ClassicMcElieceEncapsulate(public_key, rng, ciphertext,
    ciphertext_size, shared_key, shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The ciphertext can now be sent to the recipient.
...

Decapsulating the Secret

To decapsulate the shared secret using the private key:

// Get the ciphertext from the sender.
...

size_t ciphertext_size = 0;
result = iqr_ClassicMcElieceGetCiphertextSize(params, &ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

if (ciphertext_size != received_ciphertext_size) {
    // Handle error.
}

size_t shared_key_size = 0;
result = iqr_ClassicMcElieceGetSharedKeySize(params, &shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_key = calloc(1, shared_key_size);
if (shared_key == NULL) {
    // Handle error.
}

result = iqr_ClassicMcElieceDecapsulate(private_key, ciphertext,
    ciphertext_size, shared_key, shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

FrodoKEM

The toolkit provides two variants of FrodoKEM:

  • IQR_FRODOKEM_976_AES - Use AES for internal permutations.

  • IQR_FRODOKEM_976_CSHAKE - Use cSHAKE for internal permutations.

Both have the same security strength.

It’s up to the user to manage domain parameters (the FrodoKEM variant); the parameter data is not exposed in stored keys or ciphertexts.

FrodoKEM’s variants choose the algorithm used internally to generate a pseudo-random function (AES or cSHAKE). Both variants provide the same level of security.

Creating Keys

To create a key pair:

#include "iqr_context.h"
#include "iqr_frodokem.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
// The variant can be IQR_FRODOKEM_976_AES or IQR_FRODOKEM_976_CSHAKE.
iqr_FrodoKEMParams *params = NULL;
iqr_retval result = iqr_FrodoKEMCreateParams(context, variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

iqr_FrodoKEMPrivateKey *private_key = NULL;
iqr_FrodoKEMPublicKey *public_key = NULL;
result = iqr_FrodoKEMCreateKeyPair(params, rng, &public_key, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

size_t public_key_size = 0;
result = iqr_FrodoKEMGetPublicKeySize(params, &public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key_buffer = calloc(1, public_key_size);
if (public_key == NULL) {
    // Handle error.
}

result = iqr_FrodoKEMExportPublicKey(public_key, public_key_buffer, public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The public_key can now be sent to other users.
...

Encapsulating the Secret

To encapsulate a shared secret using the public key:

size_t shared_key_size = 0;
result = iqr_FrodoKEMGetSharedKeySize(params, &shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_key = calloc(1, shared_key_size);
if (shared_key == NULL) {
    // Handle error.
}

size_t ciphertext_size = 0;
result = iqr_FrodoKEMGetCiphertextSize(params, &ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *ciphertext = calloc(1, ciphertext_size);
if (ciphertext == NULL) {
    // Handle error.
}

result = iqr_FrodoKEMEncapsulate(public_key, rng, ciphertext, ciphertext_size,
    shared_key, shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The ciphertext can now be sent to the recipient.
...

Decapsulating the Secret

To decapsulate the shared secret using the private key:

// Get the ciphertext from the sender.
...

size_t ciphertext_size = 0;
result = iqr_FrodoKEMGetCiphertextSize(params, &ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

if (ciphertext_size != received_ciphertext_size) {
    // Handle error.
}

size_t shared_key_size = 0;
result = iqr_FrodoKEMGetSharedKeySize(params, &shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_key = calloc(1, shared_key_size);
if (shared_key == NULL) {
    // Handle error.
}

result = iqr_FrodoKEMDecapsulate(private_key, ciphertext, ciphertext_size,
    shared_key, shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

Kyber

The toolkit provides two variants of Kyber:

  • IQR_KYBER_128 - 128 bits of quantum security

  • IQR_KYBER_224 - 224 bits of quantum security

It’s up to the user to manage domain parameters (the Kyber variant); the parameter data is not exposed in stored keys or ciphertexts.

There is a small (2-80) chance that a Kyber key encapsulation will fail due to the math involved. To the algorithm, this looks like any other decapsulation failure.

Creating Keys

To create a key pair:

#include "iqr_context.h"
#include "iqr_kyber.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
// The variant can be IQR_KYBER_128 or IQR_KYBER_224.
iqr_KyberParams *params = NULL;
iqr_retval result = iqr_KyberCreateParams(context, variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

iqr_KyberPrivateKey *private_key = NULL;
iqr_KyberPublicKey *public_key = NULL;
result = iqr_KyberCreateKeyPair(params, rng, &public_key, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

size_t public_key_size = 0;
result = iqr_KyberGetPublicKeySize(params, &public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key_buffer = calloc(1, public_key_size);
if (public_key == NULL) {
    // Handle error.
}

result = iqr_KyberExportPublicKey(public_key, public_key_buffer, public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The public_key can now be sent to other users.
...

Encapsulating the Secret

To encapsulate a shared secret using the public key:

size_t shared_key_size = 0;
result = iqr_KyberGetSharedKeySize(params, &shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_key = calloc(1, shared_key_size);
if (shared_key == NULL) {
    // Handle error.
}

size_t ciphertext_size = 0;
result = iqr_KyberGetCiphertextSize(params, &ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *ciphertext = calloc(1, ciphertext_size);
if (ciphertext == NULL) {
    // Handle error.
}

result = iqr_KyberEncapsulate(public_key, rng, ciphertext, ciphertext_size,
    shared_key, shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The ciphertext can now be sent to the recipient.
...

Decapsulating the Secret

To decapsulate the shared secret using the private key:

// Get the ciphertext from the sender.
...

size_t ciphertext_size = 0;
result = iqr_KyberGetCiphertextSize(params, &ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

if (ciphertext_size != received_ciphertext_size) {
    // Handle error.
}

size_t shared_key_size = 0;
result = iqr_KyberGetSharedKeySize(params, &shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_key = calloc(1, shared_key_size);
if (shared_key == NULL) {
    // Handle error.
}

result = iqr_KyberDecapsulate(private_key, ciphertext, ciphertext_size,
    shared_key, shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

NTRUPrime

The toolkit provides an NTRUPrime key encapsulation mechanism.

No domain parameters are necessary for NTRUPrime; the parameter data is not exposed in stored keys or ciphertexts.

Creating Keys

To create a key pair:

#include "iqr_context.h"
#include "iqr_hash.h"
#include "iqr_ntruprime.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Register a SHA2-512 hash implementation.
// Create and initialize a Random Number Generator, rng.
...
iqr_NTRUPrimeParams *params = NULL;
iqr_retval result = iqr_NTRUPrimeCreateParams(context, &params);
if (result != IQR_OK) {
    // Handle error.
}

iqr_NTRUPrimePrivateKey *private_key = NULL;
iqr_NTRUPrimePublicKey *public_key = NULL;
result = iqr_NTRUPrimeCreateKeyPair(params, rng, &public_key, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

size_t public_key_size = 0;
result = iqr_NTRUPrimeGetPublicKeySize(params, &public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key_buffer = calloc(1, public_key_size);
if (public_key == NULL) {
    // Handle error.
}

result = iqr_NTRUPrimeExportPublicKey(public_key, public_key_buffer,
    public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The public_key can now be sent to other users.
...

Encapsulating the Secret

To encapsulate a shared secret using the public key:

size_t shared_key_size = 0;
result = iqr_NTRUPrimeGetSharedKeySize(params, &shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_key = calloc(1, shared_key_size);
if (shared_key == NULL) {
    // Handle error.
}

size_t ciphertext_size = 0;
result = iqr_NTRUPrimeGetCiphertextSize(params, &ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *ciphertext = calloc(1, ciphertext_size);
if (ciphertext == NULL) {
    // Handle error.
}

result = iqr_NTRUPrimeEncapsulate(public_key, rng, ciphertext,
    ciphertext_size, shared_key, shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The ciphertext can now be sent to the recipient.
...

Decapsulating the Secret

To decapsulate the shared secret using the private key:

// Get the ciphertext from the sender.
...

size_t ciphertext_size = 0;
result = iqr_NTRUPrimeGetCiphertextSize(params, &ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

if (received_ciphertext_size != ciphertext_size) {
    // Handle error.
}

size_t shared_key_size = 0;
result = iqr_NTRUPrimeGetSharedKeySize(params, &shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_key = calloc(1, shared_key_size);
if (shared_key == NULL) {
    // Handle error.
}

result = iqr_NTRUPrimeDecapsulate(private_key, ciphertext,
    ciphertext_size, shared_key, shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

SIKE

The toolkit provides two variants of the SIKE isogeny based KEM:

  • IQR_SIKE_P503 - Parameters using the NIST p503 curve.

  • IQR_SIKE_P751 - Parameters using the NIST p751 curve.

It’s up to the user to manage domain parameters (the variant); the parameter data is not exposed in stored keys or ciphertexts.

Per the paper On The Cost Of Computing Isogenies Between Supersingular Elliptic Curves by Adj, Cervantes-Vázquez, Chi-Domínguez, and Rodríguez-Henríquez, the p503 parameters provide 128 bits of quantum security, while the p751 curve provides 188 bits. Based on the NIST submission, these curves provide 84 and 128 bits of quantum security respectively. We provide both so you can choose appropriately for your threat model; select the p503 curve for greater efficiency, or the p751 curve for greater assurance of security.

Creating Keys

To create a key pair:

#include "iqr_context.h"
#include "iqr_rng.h"
#include "iqr_sike.h"
...
// Create an iqr_Context, context.
// Create and initialize a Random Number Generator, rng.
...
// The variant can be IQR_SIKE_P503 or IQR_SIKE_P751.
iqr_SIKEParams *params = NULL;
iqr_retval result = iqr_SIKECreateParams(context, variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

iqr_SIKEPrivateKey *private_key = NULL;
iqr_SIKEPublicKey *public_key = NULL;
result = iqr_SIKECreateKeyPair(params, rng, &public_key, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

size_t public_key_size = 0;
result = iqr_SIKEGetPublicKeySize(params, &public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *public_key_buffer = calloc(1, public_key_size);
if (public_key == NULL) {
    // Handle error.
}

result = iqr_SIKEExportPublicKey(public_key, public_key_buffer, public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The public_key can now be sent to other users.
...

Encapsulating the Key

To encapsulate a shared key using the public key:

size_t shared_key_size = 0;
result = iqr_SIKEGetSharedKeySize(params, &shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_key = calloc(1, shared_key_size);
if (shared_key == NULL) {
    // Handle error.
}

size_t ciphertext_size = 0;
result = iqr_SIKEGetCiphertextSize(params, &ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *ciphertext = calloc(1, ciphertext_size);
if (ciphertext == NULL) {
    // Handle error.
}

result = iqr_SIKEEncapsulate(public_key, rng, ciphertext, ciphertext_size,
    shared_key, shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

// The ciphertext can now be sent to the recipient.
...

Decapsulating the Key

To decapsulate the shared key using the private key:

// Get the ciphertext from the sender.
...

size_t ciphertext_size = 0;
result = iqr_SIKEGetCiphertextSize(params, &ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

if (ciphertext_size != received_ciphertext_size) {
    // Handle error.
}

size_t shared_key_size = 0;
result = iqr_SIKEGetSharedKeySize(params, &shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

uint8_t *shared_key = calloc(1, shared_key_size);
if (shared_key == NULL) {
    // Handle error.
}

result = iqr_SIKEDecapsulate(private_key, ciphertext, ciphertext_size,
    shared_key, shared_key_size);
if (result != IQR_OK) {
    // Handle error.
}

Technical Information

This section provides further information about some technical aspects of the toolkit.

Key and Signature Sizes

Digital signature schemes:

Key agreement schemes:

Key encapsulation mechanisms:

Note
These sizes are provided as a reference only; use the various iqr_*Get*Size() functions to find the correct sizes at run-time.

Dilithium Keys and Signatures

Dilithium provides 128 or 160 bits of quantum security depending on which variant you use.

Dilithium digital signature scheme key sizes:

Table 3. Dilithium Key Sizes
Variant Public Key (bytes) Private Key (bytes)

128 bit

1472

15,896

160 bit

1760

19,256

Dilithium signs a message, producing these signature sizes:

Table 4. Dilithium Signature Sizes
Variant Signature (bytes)

128 bit

2701

160 bit

3366

HSS Keys and Signatures

HSS is believed to have 256 bits of classical security and 128 bits of quantum security, due to its use of SHA2-256 internally.

HSS digital signature scheme key sizes:

Table 5. HSS Key Sizes
Variant Public Key (bytes) Private Key (bytes) Memory Constrained State (bytes) CPU Constrained State (bytes) Full Tree State (bytes)

2E20 Fast

60

72

5,048

65,884

67,108,924

2E20 Small

60

72

5,048

65,884

67,108,924

2E25 Fast

60

72

7,948

393,628

2,147,483,708

2E25 Small

60

72

7,948

393,628

2,147,483,708

2E30 Fast

60

72

8,689

33,721

4,194,424

2E30 Small

60

72

8,689

33,721

4,194,424

2E45 Fast

60

72

14,586

54,902

6,291,636

2E45 Small

60

72

14,586

54,902

6,291,636

2E65 Fast

60

72

21,772

69,328

6,422,828

2E65 Small

60

72

21,772

69,328

6,422,828

The tree strategy has no effect on HSS signature sizes:

Table 6. HSS Signature Sizes
Variant Signature (bytes)

2E20 Fast

4,944

2E20 Small

1,776

2E25 Fast

5,104

2E25 Small

1,936

2E30 Fast

9,620

2E30 Small

3,284

2E45 Fast

14,456

2E45 Small

4,952

2E65 Fast

23,808

2E65 Small

7,968

Rainbow Keys and Signatures

Table 7. Rainbow Key Sizes
Variant Public Key (bytes) Private Key (bytes)

IIIb (GF(31), 64, 32, 48)

564,534

409,470

IIIc (GF(256), 68, 36, 36)

720,792

537,788

IVa (GF(16), 56, 48, 48)

565,488

376,148

Vc (GF(256), 92, 48, 48)

1,723,680

1,274,324

VIa (GF(16), 76, 64, 64)

1,351,360

892,086

VIb (GF(31), 84, 56, 56)

1,456,224

1,016,875

Rainbow signs a message producing these signature sizes:

Table 8. Rainbow Signature Sizes
Variant Signature (bytes)

IIIb (GF(31), 64, 32, 48)

112

IIIc (GF(256), 68, 36, 36)

156

IVa (GF(16), 56, 48, 48)

92

Vc (GF(256), 92, 48, 48)

204

VIa (GF(16), 76, 64, 64)

118

VIb (GF(31), 84, 56, 56)

147

SPHINCS+ Keys and Signatures

Table 9. SPHINCS+ Key Sizes
Variant Public Key (bytes) Private Key (bytes)

192f

48

104

192s

48

104

256f

64

136

256s

64

136

SPHINCS+ signs a message producing these signature sizes:

Table 10. SPHINCS+ Signature Sizes
Variant Signature (bytes)

192f

35,664

192s

17,064

256f

49,216

256s

29,792

XMSS Keys and Signatures

XMSS is believed to have 256 bits of classical security and 128 bits of quantum security, due to its use of SHA2-256 internally.

Tree strategy has no effect on XMSS key sizes.

XMSS digital signature scheme key sizes:

Table 11. XMSS Key Sizes
Variant Public Key (bytes) Private Key (bytes) Memory Constrained State (bytes) CPU Constrained State (bytes) Full Tree State (bytes)

IQR_XMSS_2E10

68

104

1276

2,208

65,568

IQR_XMSS_2E16

68

104

3152

16,640

4,194,336

IQR_XMSS_2E20

68

104

5020

65,856

67,108,896

Tree strategy has no effect on XMSS signature sizes:

Table 12. XMSS Signature Sizes
Variant Signature (bytes)

IQR_XMSS_2E10

2500

IQR_XMSS_2E16

2692

IQR_XMSS_2E20

2820

XMSSMT Keys and Signatures

XMSSMT is believed to have 256 bits of classical security and 128 bits of quantum security, due to its use of SHA2-256 internally.

Tree strategy has no effect on XMSSMT key sizes.

XMSSMT digital signature scheme key sizes:

Table 13. XMSSMT Key Sizes
Variant Public Key (bytes) Private Key (bytes) Memory Constrained State (bytes) CPU Constrained State (bytes) Full Tree State (bytes)

IQR_XMSSMT_2E20_2D

68

104

4,025

5,729

131,136

IQR_XMSSMT_2E20_4D

68

104

3,131

2,923

9,296

IQR_XMSSMT_2E40_2D

68

104

15,545

165,089

134,217,792

IQR_XMSSMT_2E40_4D

68

104

9,499

12,747

262,248

IQR_XMSSMT_2E40_8D

68

104

6,719

6,207

16,568

IQR_XMSSMT_2E60_3D

68

104

26,058

264,310

201,326,676

IQR_XMSSMT_2E60_6D

68

104

14,973

19,765

393,360

IQR_XMSSMT_2E60_12D

68

104

10,307

9,491

24,840

Tree strategy has no effect on XMSSMT signature sizes:

Table 14. XMSS Signature Sizes
Variant Signature (bytes)

IQR_XMSSMT_2E20_2D

4,963

IQR_XMSSMT_2E20_4D

9,251

IQR_XMSSMT_2E40_2D

5,605

IQR_XMSSMT_2E40_4D

9,893

IQR_XMSSMT_2E40_8D

18,469

IQR_XMSSMT_2E60_3D

8,392

IQR_XMSSMT_2E60_6D

14,824

IQR_XMSSMT_2E60_12D

27,688

FrodoDH Keys

FrodoDH supports two variants, Recommended and Paranoid.

FrodoDH key and shared secret sizes:

Table 15. FrodoDH Key Sizes
Variant Initiator Public Key (bytes) Responder Public Key (bytes) Shared Secret (bytes)

Paranoid

12,992

12,968

32

Recommended

11,312

11,288

32

NewHopeDH Keys

NewHopeDH key (show as Initiator/Responder) and shared secret sizes:

Table 16. NewHopeDH Key Sizes
Variant Initiator Public Key (bytes) Responder Public Key (bytes) Shared Secret (bytes)

(none)

1824

2048

32

SIDH Keys

SIDH key (shown as Alice/Bob) and secret sizes:

Table 17. SIDH Key Sizes
Variant Alice Public Key (bytes) Bob Public Key (bytes) Shared Secret (bytes)

P503

378

378

126

P751

564

564

188

Classic McEliece Keys and Ciphertexts

Classic McEliece key encapsulation mechanismkey sizes:

Table 18. Classic McEliece Key Sizes
Variant Public Key (bytes) Private Key (bytes)

6

1,047,319

738,218

8

1,357,824

853,832

Classic McEliece encapsulates a 32 byte shared secret producing these ciphertext sizes:

Table 19. Classic McEliece Ciphertext Sizes
Variant Ciphertext (bytes)

6

226

8

240

FrodoKEM Keys and Ciphertexts

FrodoKEM’s variants affect the internal permutations but not the key and ciphertext sizes.

Table 20. FrodoKEM Key Sizes
Variant Public Key (bytes) Private Key (bytes)

967 AES

15,632

31,280

967 cSHAKE

15,632

31,280

FrodoKEM encapsulates a 24 byte shared secret producing these ciphertext sizes:

Table 21. FrodoKEM Ciphertext Sizes
Variant Ciphertext (bytes)

967 AES

15,768

967 cSHAKE

15,768

Kyber Keys and Ciphertexts

Kyber provides 160 or 224 bits of quantum security depending on which variant you use.

Kyber key encapsulation mechanism key sizes:

Table 22. Kyber Key Sizes
Variant Public Key (bytes) Private Key (bytes)

Kyber768

1088

2664

Kyber1024

1440

3528

Kyber encapsulates a 24 byte shared secret producing these ciphertext sizes:

Table 23. Kyber Ciphertext Sizes
Variant Ciphertext (bytes)

Kyber768

1152

Kyber1024

1504

NTRUPrime Keys and Ciphertexts

Table 24. NTRUPrime Key Sizes
Variant Public Key (bytes) Private Key (bytes)

(none)

1225

3680

NTRUPrime encapsulates a 32 byte shared secret producing this ciphertext size:

Table 25. NTRUPrime Ciphertext Sizes
Variant Ciphertext (bytes)

(none)

1050

SIKE KEM Keys and Ciphertexts

Table 26. SIKE Key Sizes
Variant Public Key (bytes) Private Key (bytes)

P503

378

442

P751

564

652

SIKE encapsulates a 16 or 24 byte shared secret producing these ciphertext sizes:

Table 27. SIKE Ciphertext Sizes
Variant Shared Key (bytes) Ciphertext (bytes)

P503

16

402

P751

24

596

Build Options

During development of the toolkit, builds use many compiler and linker flags intended to help reduce errors and improve overall code quality.

Compiler Options

When building with clang:

  • -Weverything -Wno-deprecated -Wno-deprecated-declarations -Wno-vla -Wno-packed -Wno-padded -Wno-disabled-macro-expansion -Wno-documentation-unknown-command -Wno-missing-field-initializers

  • -Winline (for everything except Android)

  • -Wno-reserved-id-macro (for everything except FreeBSD)

  • -fno-stack-protector (for Windows, and Cygwin)

  • -fvisibility=hidden

  • -fPIC (for everything except Cygwin)

  • -std=c99

  • -O3

  • -DNDEBUG -D_FORTIFY_SOURCE=2

  • -D__USE_MINGW_ANSI_STDIO=1 (Windows only)

  • -Werror

When building with gcc:

  • -Wall -Wextra -Waggregate-return -Wbad-function-cast -Wcast-align -Wcast-qual -Wfloat-equal -Wformat-security -Wformat=2 -Winit-self -Wmissing-include-dirs -Wmissing-noreturn -Wmissing-prototypes -Wnested-externs -Wno-deprecated -Wno-deprecated-delcarations -Wold-style-definition -Wpedantic -Wredundant-decls -Wshadow -Wstrict-prototypes -Wswitch-default -Wuninitialized -Wunreachable-code -Wunused -Wvarargs -Wwrite-strings

  • -Winline (for everything except Android)

  • -fstrict-aliasing -fstrict-overflow -funsafe-loop-optimizations -fstack-check -fstack-protector-all -fvisibility=hidden -fdata-sections -ffunction-sections

  • -fno-stack-protector (for Windows, and Cygwin)

  • -fPIC (for everything except Cygwin)

  • -pedantic

  • -pipe

  • -std=c99

  • -O3

  • -DNDEBUG -D_FORTIFY_SOURCE=2

  • -D__USE_MINGW_ANSI_STDIO=1 (Windows only)

  • -Werror

Linker Options

When building with clang:

  • -Wl,-undefined,error (for macOS) or -Wl,--no-undefined (others)

When building with gcc:

  • -Wl,-dead_strip (for macOS) or -Wl,--gc-sections (others)

  • -Wl,-undefined,error (for macOS) or -Wl,--no-undefined (others)

Apple Bitcode

The toolkit does not support Apple’s Bitcode for iOS, tvOS, or watchOS binaries. For security reasons, the toolkit needs fine control over optimizations and the life cycle of memory buffers, which isn’t possible when producing Bitcode.

Deprecated APIs

As standards for quantum-safe algorithms evolve, the toolkit’s APIs will change to reflect these changes.

If an API is likely to change in the next version of the toolkit, it’ll be marked with IQR_DEPRECATED in the header. For example:

// From iqr_kdf.h:

#ifndef IQR_IGNORE_1_5_DEPRECATED
IQR_DEPRECATED_MSG("The KDF API will be changing in the next toolkit release.")
#endif
IQR_API
iqr_retval iqr_RFC5869HKDFDeriveKey(const iqr_Context *ctx,
    iqr_HashAlgorithmType hash_algo, const uint8_t *salt, size_t salt_size,
    const uint8_t *ikm, size_t ikm_size, const uint8_t *info, size_t info_size,
    uint8_t *key, size_t key_size);

Compiling code that uses iqr_kdf.h will generate a warning (or error if you’re building with -Werror) so you can plan for the upcoming change.

After you’ve created a task (or user story, or bug) to update the code when the next version of the toolkit is released, you can add -DIQR_IGNORE_1_5_DEPRECATED to your compiler flags. This will suppress the warnings for deprecations in version 1.5 of the toolkit.

Code Stripping

The toolkit has been designed to maximize code stripping, to help when deploying to embedded systems. This is the reasoning behind having no pre-registered default for the hash algorithms. If you provide your own implementation, or only use a subset of the toolkit’s implementations, your executable won’t include the unused toolkit code.

Internally, algorithms with variants (such as Rainbow) use the same technique to include only the code required to implement the specific variants you use in your applications.

If your compiler/linker supports it, use the -flto option to enable full link-time optimization.

Linux libc

The Linux version of the toolkit is built and tested against the following versions of the libc library:

  • 2.23 (Ubuntu, 64 bit)

  • 2.24 (Arch, 64 bit)

  • 2.24 (Raspbian, 32 bit)

Building on Windows

When building an application that links against the toolkit static library (libiqr_toolkit_static.lib), add the library under Linker > Input > Additional Dependencies in Visual Studio. No other changes to your Solution file are necessary.

Adding a static library to the solution

When linking dynamically, you must specify the import library (libiqr_toolkit.lib) under Linker > Input > Additional Dependencies. Additionally, you must add IQR_DLL to the preprocessor listing under C/C++ > Preprocessor > Preprocessor Definitions. This will ensure that extern symbols are imported correctly. The toolkit DLL (libiqr_toolkit.dll) must reside in a location where it can be found by the linker.

Adding IQR_DLL to preprocessor definitions

The ISARA Radiate Quantum-safe Toolkit is licensed for use:

Copyright © 2015-2019, ISARA Corporation, All Rights Reserved.

The code and other content set out herein is not in the public domain, is considered a trade secret and is confidential to ISARA Corporation. Use, reproduction or distribution, in whole or in part, of such code or other content is strictly prohibited except by express written permission of ISARA Corporation. Please contact ISARA Corporation at info@isara.com for more information.

Please refer to your sales/support contract for more information about technical support and upgrade entitlements.

Trademarks

ISARA Radiate™ is a trademark of ISARA Corporation.

Sample Code License

Sample code (and only the sample code) is covered by the Apache 2.0 license:

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Patent Information