Introduction

The ISARA Quantum Resistant 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 resistant solutions, please visit www.isara.com.

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

  • use McEliece Quasi-Cyclic Moderate Density Parity-Check (QC-MDPC) error-correcting codes

  • use the Leighton-Micali Signature (LMS) scheme for digital signatures

  • use "NewHope" lattice-based key agreement, or LUKE (Lattice-based Unique Key Exchange), an ISARA speed-optimized variant

  • provide your own implementations of generic algorithms, such as hashes and random number generators

The Toolkit’s API is designed around generic algorithms; you create an instance of the algorithm you want (say, SHA-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.

The Developer’s Guide covers the following topics:

  • Getting Started — Foundational information for using the Toolkit

  • Hashing — How to use the Toolkit’s SHA-2 and SHA-3 implementations, and how to provide your own implementation (or use OpenSSL’s)

  • Random Numbers — How to generate pseudorandom bytes

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

  • Key Derivation Functions — How to use the KDF implementations

  • Symmetric Encryption — How to use the symmetric-key encryption algorithms in the Toolkit

  • Digital Signatures — How to use the digital signature schemes in the Toolkit

  • Key Agreement — How to use the key agreement schemes supported by the Toolkit

  • Asymmetric Encryption — How to use the Toolkit’s asymmetric public-key encryption scheme

  • 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

Packaging

The Toolkit ZIP archive contains the following files and directories:

  • README.html — Information about the Toolkit package

  • doc — API documentation and this Developer’s Guide

  • include — Toolkit headers

  • lib — Toolkit static and shared libraries

  • samples — Sample programs demonstrating how to use the Toolkit

Cryptographic signatures for the installation archives can be found on the ISARA website.

Getting Help

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

Samples

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

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

  • chacha20 — Encrypt/decrypt using ChaCha20.

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

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

  • kdf — Derive a key (some pseudorandom data) using the specified key derivation function.

  • lms — Generate LMS keys, sign a file’s data with an LMS key, and verify an LMS signature.

  • luke — Agree on a shared secret using the LUKE variant of the NewHope scheme.

  • mceliece — Generate McEliece keys, encrypt and decrypt data using McEliece QC-MDPC with CCA2 Gamma conversion.

  • newhope — Agree on a shared secret using the reference variant of the NewHope scheme or a variant compatible with BoringSSL.

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

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

To compile the samples, you will need:

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

  • cmake 3.0 or newer

  • GNU make 3.8 or newer

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 with:

  1. cd to the sample’s directory, for example cd hash.

  2. cmake ..

  3. make

Getting Started

This section gives you an overview of things you’ll need to know to effectively use the IQR 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, LMS (a digital signature system) has Sign() and Verify(), while QC-MDPC (an encryption scheme) has Encrypt() and Decrypt().

The Context

To create objects and algorithms in the Tookit, you need a Context. The iqr_Context object keeps track of the registered hashing and random number generation 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). If you’re writing a threaded application, use one Context per thread.

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 and RNGs

Many algorithms require hash implementations and random number generators. 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() and iqr_RNGRegisterCallbacks() functions in iqr_hash.h and iqr_rng.h.

Note
You don’t need to provide a set of random number generator callbacks if you’re using the HMAC-DRBG or SHAKE algorithms (created with iqr_RNGCreateHMACDRBG and iqr_RNGCreateSHAKE respectively).

The Toolkit provides implementations of the quantum-safe SHA-2 and SHA-3 hash algorithms:

  • IQR_HASH_DEFAULT_SHA2_256 - Toolkit’s C implementation of SHA-256

  • IQR_HASH_DEFAULT_SHA2_512 - Toolkit’s C implementation of SHA-512

  • IQR_HASH_DEFAULT_SHA3_256 - Toolkit’s C implementation of SHA3-256

  • IQR_HASH_DEFAULT_SHA3_512 - Toolkit’s C implementation of SHA3-512

For example, to use the Toolkit implementation of SHA-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 SHA-256 object will create one using the Toolkit’s built-in SHA-256 implementation.

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.

The iqr_Context object is intended to be unique per thread.

What Next?

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

Hashing

Getting a hash digest for a message is a basic building block of many cryptographic algorithms. The Toolkit provides implementations of SHA-2 and SHA-3.

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.

#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:

  • SHA-256 (IQR_HASHALGO_SHA2_256) — IQR_HASH_DEFAULT_SHA2_256

  • SHA-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 four of the supported SHA-2 and SHA-3 algorithms; you can register implementations for all of them on a single iqr_Context.

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(), 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 (*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_HashCallbacks;

Your begin() function is passed an empty pointer, which it can use to store any necessary state. Allocate any memory you need, 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;
    }

    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;

    // 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;
}

Finally, the end() function gets a pointer to 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. Before returning, deallocate any state you allocated during begin() and set the state pointer to NULL.

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

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

    // If digest is NULL and digest_size is 0, end() is being called just to
    // clean up any allocated state.
    if (digest == NULL && digest_size == 0) {
        goto cleanup;
    }

    // Finish processing the hash.
    ...

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

cleanup:
    // Clean up and deallocate any state you allocated.
    ...
    memset(*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 SHA-256

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

First, we’ll write the hash’s begin(), update(), and end() 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 sha256_begin(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;
    }

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

    // Assign the initialized context and we're done.
    *state = ctx;

    return IQR_OK;

fail:
    if (ctx != NULL) {
        free(ctx);
    }

    // Failed to initialize.
    return IQR_ENOTINIT;
}

static iqr_retval sha256_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 sha256_end(void **state, uint8_t *digest,
    size_t digest_size)
{
    // Sanity-check input.
    if (state == NULL) {
        return IQR_ENULLPTR;
    }

    SHA256_CTX *ctx = (SHA256_CTX *)*state;
    iqr_retval result = IQR_OK;

    // Clean up only.
    if (digest == NULL && digest_size == 0) {
        goto cleanup;
    }

    // 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) {
        result = IQR_EINVOBJECT;
    }

cleanup:
    // Before we exit, the user context must be dealt with.
    memset(ctx, 0, sizeof(SHA256_CTX));
    free(ctx);
    *state = NULL;

    return result;
}

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 SHA-256 hash:

// Create the callback structure.
const iqr_HashCallbacks openssl_sha256 = {
    .begin = sha256_begin,
    .update = sha256_update,
    .end = sha256_end
};

// Register the OpenSSL implementation.
result = iqr_HashRegisterCallbacks(context, IQR_HASHALGO_SHA2_256,
    &openssl_sha256);
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_SHA256, &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 and SHAKE algorithms 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.

Registering an RNG

As mentioned in the Getting Started section, you can register an implementation. The Toolkit doesn’t automatically associate its own RNG APIs with the Context when you create one.

#include "iqr_context.h"
#include "iqr_rng.h"
...
// Create iqr_Context.
...
iqr_retval result = iqr_RNGRegisterCallbacks(context, implementation);

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", the initialization buffer would be "passwordrandom".

To create a SHAKE object using any of the IQR_SHAKE_* constants from iqr_rng.h:

#include "iqr_context.h"
#include "iqr_rng.h"
...
// Create iqr_Context.
...
iqr_RNG *rng = NULL;
iqr_retval result = iqr_RNGCreateSHAKE(context, IQR_SHAKE_256_SIZE, &rng);
if (result != IQR_OK) {
    // Handle error.
}

To create an RNG object using a registered implementation:

#include "iqr_context.h"
#include "iqr_rng.h"
...
// Create iqr_Context.
// Register RNG implementation.
...
iqr_RNG *rng = NULL;
iqr_retval result = iqr_RNGCreate(context, &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

Because the Toolkit doesn’t have a default RNG implementation associated with the iqr_Context object, you can use the iqr_RNGCallbacks structure (from iqr_rng.h) to provide your own code. 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.
    ...
    memset(*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 documentation for /dev/urandom 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/random", 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_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 + bytes_read, buffer_size);
        if (bytes_read == -1) {
            return IQR_EINVDATA;
        }
        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, register it so the rest of the Toolkit 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
};

// Register the RNG implementation.
result = iqr_RNGRegisterCallbacks(context, &devrandom_rng);
if (result != IQR_OK) {
    // Handle error.
}

After that, any RNG object you create with iqr_RNGCreate() will use this implementation:

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

Message Authentication Codes

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

HMAC

The hash-based message authentication code algorithm (found in iqr_hmac.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.

Using HMAC is similar to using a hash:

  1. Create the HMAC.

  2. Begin the HMAC operation.

  3. Update it with data.

  4. End the HMAC and get the tag.

  5. Destroy the HMAC, or begin again.

To create an HMAC:

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

To use an HMAC object:

...
// Call Begin() to initialize the HMAC.
result = iqr_HMACBegin(hmac);
if (result != IQR_OK) {
    // Handle error.
}

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

// Use iqr_HMACGetTagSize() to get the tag size for this hash.
uint8_t *tag = NULL;
size_t tag_size = 0;
result = iqr_HMACGetTagSize(hmac, &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_HMACEnd(hmac, tag, tag_size);

To destroy an HMAC object:

...
result = iqr_HMACDestroy(&hmac);
if (result != IQR_OK) {
    // Handle error.
}

There’s also an iqr_HMACMessage() function that combines the iqr_HMACBegin(), iqr_HMACUpdate(), iqr_HMACEnd() process into one call:

...
result = iqr_HMACMessage(hmac, buffer, buffer_size, tag, tag_size);
if (result != IQR_OK) {
    // Handle error.
}

Poly1305

The Poly1305 message authentication code algorithm (found in iqr_poly1305.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).

Using Poly1305 is similar to using a hash:

  1. Create the Poly1305 object.

  2. Begin the Poly1305 operation.

  3. Update it with data.

  4. End the Poly1305 operation and get the tag.

  5. Destroy the Poly1305 object, or begin again.

To create a Poly1305 object:

#include "iqr_poly1305.h"
...
// Create iqr_Context.
...
iqr_Poly1305 *poly1305 = NULL;
iqr_retval result = iqr_Poly1305Create(context, &poly1305);
if (result != IQR_OK) {
    // Handle error.
}

To use a Poly1305 object:

...
// Call Begin() to initialize the Poly1305 object.
result = iqr_Poly1305Begin(poly1305, key, key_size);
if (result != IQR_OK) {
    // Handle error.
}

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

// Tags are IQR_POLY1305_TAG_SIZE bytes long.
uint8_t *tag = calloc(1, IQR_POLY1305_TAG_SIZE);
if (tag == NULL) {
    // Handle out-of-memory.
}

// Call End() to finish the operation and get the tag.
result = iqr_Poly1305End(hmac, tag, tag_size);

To destroy a Poly1305 object:

...
result = iqr_Poly1305Destroy(&hmac);
if (result != IQR_OK) {
    // Handle error.
}

There’s also an iqr_Poly1305Message() function that combines the iqr_Poly1305Begin(), iqr_Poly1305Update(), iqr_Poly1305End() process into one call:

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

Key Derivation Functions

The IQR Toolkit provides three standard key derivation functions (KDFs):

  • RFC-5869’s HMAC-based extract-and-expand KDF (HKDF)

  • NIST SP 800-56A Alternative 1 Concatenation

  • RFC-2898’s password Based Key Derivation Function 2 (PBKDF2)

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

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-56A Alternative 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 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.

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.

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 IQR Toolkit provides one symmetric algorithm, RFC-7539’s ChaCha20.

ChaCha20

ChaCha20 (see iqr_chacha20.h) is an easy to use cipher that doesn’t require additional parameter or key objects.

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 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 IQR Toolkit provides an implementation of the Leighton-Micali Signature (LMS) scheme as defined in the Hash-Based Signatures IETF Draft document.

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

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

  • You need to maintain a one-time signature index, q, carefully.

When you create an LMS key pair, you specify iqr_LMSHeight and Winternitz (iqr_LMSWinternitz) parameters. The height controls the number of one-time signatures available in the private key. The height can be one of:

  • IQR_LMS_HEIGHT_5 — 25 (32) one-time signatures

  • IQR_LMS_HEIGHT_10 — 210 (1024) one-time signatures

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

The Winternitz parameter lets you choose a trade-off between speed and size; a larger Winternitz value will give you smaller keys and signatures, but with slower key generation, signing, and verification. The Winternitz value can be one of:

  • IQR_LMS_WINTERNITZ_1

  • IQR_LMS_WINTERNITZ_2

  • IQR_LMS_WINTERNITZ_4 — Suggested best time/space trade-off.

  • IQR_LMS_WINTERNITZ_8

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

The q value, specified when signing, is an index into the one-time signatures. Re-using a one-time signature destroys the security of the LMS scheme, so be careful to:

  • Not re-use q values when signing.

  • Safely and securely store your q value to protect against software crashes or power loss.

LMS private keys grow larger depending on their height and the Winternitz value used; see the Technical Information section for key sizes.

Creating Keys

The Toolkit lets you create LMS keys by specifying individual parameters, or using the LMS Algorithm Type and OTS Algorithm Type constants defined in the IETF document.

In both cases, the secret security string is a 31-byte buffer containing an application-specific identifier (denoted by I in the IETF document).

To create an LMS key pair:

#include "iqr_context.h"
#include "iqr_hash.h"
#include "iqr_lms.h"
#include "iqr_rng.h"
...
// Create iqr_Context, context.
// Register a SHA-256 hash algorithm.
// Create a Random Number Generator, rng.
...
// Create LMS parameters.
iqr_LMSParams *params = NULL;
iqr_retval result = iqr_LMSCreateParams(context, winternitz, height,
    security_string, security_string_size, &params);
if (result != IQR_OK) {
    // Handle error.
}

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

To create an LMS key pair using the IETF’s LMS Algorithm Type (iqr_LMSAlgorithmType) and OTS Algorithm Type (iqr_LMOTSAlgorithmType) parameters:

#include "iqr_context.h"
#include "iqr_hash.h"
#include "iqr_lms.h"
#include "iqr_rng.h"
...
// Create iqr_Context, context.
// Register a SHA-256 hash algorithm.
// Create a Random Number Generator, rng.
...
// Create LMS parameters.
iqr_LMSParams *params = NULL;
iqr_retval result = iqr_LMSCreateParamsIETF(context, lms_algorithm_type,
    ots_algorithm_type, secret_string, secret_string_size, &params);
if (result != IQR_OK) {
    // Handle error.
}

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

The iqr_LMSCreateParams() and iqr_LMSCreateParamsIETF() functions will create the same parameters object when using equivalent arguments:

Table 1. LMS Algorithm Types
LMS Algorithm Type Height Equivalent

IQR_LMS_SHA256_N16_H5

(not supported)

IQR_LMS_SHA256_N16_H10

(not supported)

IQR_LMS_SHA256_N16_H20

(not supported)

IQR_LMS_SHA256_N32_H5

IQR_LMS_HEIGHT_5

IQR_LMS_SHA256_N32_H10

IQR_LMS_HEIGHT_10

IQR_LMS_SHA256_N32_H20

IQR_LMS_HEIGHT_20

Table 2. OTS Algorithm Types
OTS Algorithm Type Winternitz Equivalent

IQR_OTS_SHA256_N16_W1

(not supported)

IQR_OTS_SHA256_N16_W2

(not supported)

IQR_OTS_SHA256_N16_W4

(not supported)

IQR_OTS_SHA256_N16_W8

(not supported)

IQR_OTS_SHA256_N32_W1

IQR_LMS_WINTERNITZ_1

IQR_OTS_SHA256_N32_W2

IQR_LMS_WINTERNITZ_2

IQR_OTS_SHA256_N32_W4

IQR_LMS_WINTERNITZ_4

IQR_OTS_SHA256_N32_W8

IQR_LMS_WINTERNITZ_8

Signing

To sign a message using the LMS private key:

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

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

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

  Before signing, the value of q + 1 must be written to non-volatile memory.
  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. Next time you sign, use q+1.

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

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

// Retrieve q's current value.
...

// Store q + 1.
...

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

Verifying Signatures

To verify a signature using the LMS public key:

...
result = iqr_LMSVerify(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, and that you’re not re-using q values.

To export LMS keys for storage or transmission:

...
size_t public_key_data_size = 0;
result = iqr_LMSGetPublicKeySize(public_key, &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_LMSExportPublicKey(public_key, public_key_data,
    public_key_data_size);
if (public_key_data == NULL) {
    // Handle error.
}

size_t private_key_data_size = 0;
result = iqr_LMSGetPrivateKeySize(private_key, &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_LMSExportPrivateKey(private_key, private_key_data,
    private_key_data_size);
if (result != IQR_OK) {
    // Handle error.
}

To import LMS keys from buffers:

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

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

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

Public Key Format

The data produced by iqr_LMSExportPublicKey() follows the XDR-inspired format documented in the IETF document:

uint32_t ots_type;     // A value from iqr_LMOTSAlgorithmType
uint8_t I[31];         // 31-byte security identifier
uint8_t padding;       // A single byte of padding, expected 0x00.
uint8_t public_key[];  // n bytes of public key

Note that n is always 32 for the Toolkit, as it doesn’t support the less secure 16-byte partial hash mode of LMS.

Signature Format

The signature produced by iqr_LMSSign() follows the XDR-inspired format documented in the IETF document:

uint32_t lms_type;  // A value from iqr_LMSAlgorithmType
uint32_t ots_type;  // A value from iqr_LMOTSAlgorithmType

uint8_t C[n];       // n-byte randomizer
uint32_t q;         // The all-important OTS index

uint8_t y[];        // n * p bytes OTS signature

uint8_t path[];     // Height * n bytes of signing path

Note that n is always 32 for the Toolkit, as it doesn’t support the less secure 16-byte partial hash mode of LMS.

The p value is determined by the n and Winternitz parameters, as defined in the table of section 4.3 of the IETF document.

Key Agreement

The IQR Toolkit provides three variants of the NewHope lattice-based key agreement scheme:

  • NewHope variant compatible with the reference implementation

  • NewHope variant compatible with BoringSSL’s implementation

  • LUKE (Lattice-based Unique Key Exchange), an ISARA speed-optimized variant of the NewHope algorithm

All three key agreement schemes work in the same manner:

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

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

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

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

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

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

The last two steps can occur simultaneously.

Note
The shared secrets generated by the NewHope and LUKE schemes are ephemeral. You cannot re-use keys to generate additional shared secrets.

NewHope

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.

Initiator

To create a key pair:

#include "iqr_contex.h"
#include "iqr_newhope.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create a Random Number Generator, rng.
...
// The variant can be &IQR_NEWHOPE_REFERENCE or &IQR_NEWHOPE_BORINGSSL
// depending on which implementation the Responder is using.
iqr_NewHopeParams *params = NULL;
iqr_retval result = iqr_NewHopeCreateParams(ctx, variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

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

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

result = iqr_NewHopeCreateInitiatorPublicKey(params, rng, private_key,
    public_key, public_key_size);

// 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 = IQR_NEWHOPE_RESPONDER_KEY_SIZE;
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 = IQR_NEWHOPE_SECRET_SIZE;
uint8_t shared_secret = calloc(1, shared_secret_size);
if (shared_secret == NULL) {
    // Handle error.
}

result = iqr_NewHopeGetInitiatorSecret(params, responder_public_key,
    responder_public_key_size, private_key, 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_contex.h"
#include "iqr_newhope.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create a Random Number Generator, rng.
...
size_t initiator_public_key_size = IQR_NEWHOPE_INITIATOR_KEY_SIZE;
uint8_t *initiator_public_key = calloc(1, initiator_public_key_size);
if (inititator_public_key == NULL) {
    // Handle error.
}

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

// The variant can be &IQR_NEWHOPE_REFERENCE or &IQR_NEWHOPE_BORINGSSL
// depending on which implementation the Initiator is using.
iqr_NewHopeParams *params = NULL;
iqr_retval result = iqr_NewHopeCreateParams(ctx, variant, &params);
if (result != IQR_OK) {
    // Handle error.
}

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

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

result = iqr_NewHopeCreateResponderPublicKey(params, rng, initiator_public_key,
    initiator_public_key_size, private_key, 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 their private key:

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

result = iqr_NewHopeGetResponderSecret(params, private_key, shared_secret,
    shared_secret_size);

LUKE

LUKE (Lattice-based Unique Key Establishment) is ISARA’s speed-optimized variant of the key agreement scheme presented in the NewHope paper.

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.

Initiator

To create a key pair:

#include "iqr_contex.h"
#include "iqr_luke.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create a Random Number Generator, rng.
...
iqr_LUKEParams *params = NULL;
iqr_retval result = iqr_LUKECreateParams(context, &params);
if (result != IQR_OK) {
    // Handle error.
}

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

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

result = iqr_LUKECreateInitiatorPublicKey(params, rng, private_key,
    public_key, public_key_size);

// 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 = IQR_LUKE_RESPONDER_KEY_SIZE;
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 = IQR_LUKE_SECRET_SIZE;
uint8_t shared_secret = calloc(1, shared_secret_size);
if (shared_secret == NULL) {
    // Handle error.
}

result = iqr_LUKEGetInitiatorSecret(params, responder_public_key,
    responder_public_key_size, private_key, 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_contex.h"
#include "iqr_luke.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Create a Random Number Generator, rng.
...
size_t initiator_public_key_size = IQR_LUKE_INITIATOR_KEY_SIZE;
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_NewHopeParams *params = NULL;
iqr_retval result = iqr_NewHopeCreateParams(context, &params);
if (result != IQR_OK) {
    // Handle error.
}

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

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

result = iqr_LUKECreateResponderPublicKey(params, rng, initiator_public_key,
    initiator_public_key_size, private_key, 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 their private key:

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

result = iqr_LUKEGetResponderSecret(params, private_key, shared_secret,
    shared_secret_size);

Asymmetric Encryption

The IQR Toolkit provides one asymmetric (or public-key) encryption scheme, McEliece QC-MDPC (Quasi-Cyclic Medium-Density Parity Check) with CCA2 Conversion Gamma.

McEliece QC-MDPC with CCA2 Conversion Gamma

The McEliece encryption scheme (see iqr_mceliece.h) is based on error-correcting codes, and works like other asymmetric schemes:

  • Keys are a pair consisting of public and private keys.

  • Data encrypted with the public key can be decrypted using the private key.

  • The integrity of the scheme depends on keeping the private key secret.

Unlike classical public-key schemes, McEliece is probabilistic. There is a small chance that decryption will fail. When this happens, the plaintext data needs to be encrypted again before making another decryption attempt. The chance of a decryption failure is higher for larger key sizes in the same security level (for example, IQR_MCELIECE_PUBKEY61449 would have a higher chance of failing than IQR_MCELIECE_PUBKEY32771).

A iqr_McElieceParams object is used to encapsulate the algorithm’s domain parameters, specifically:

  • Hash algorithm used by the CCA2 Conversion Gamma encoding.

  • Size of the public key (as specified in the iqr_McElieceKeySize enum), which controls a number of additional domain parameters.

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

Note
Because of its probabilistic nature, McElice is not secure against timing-based side-channel attacks.

Creating Keys

To create a McEliece key pair:

#include "iqr_context.h"
#include "iqr_hash.h"
#include "iqr_mceliece.h"
#include "iqr_rng.h"
...
// Create an iqr_Context, context.
// Register hash algorithm.
// Create a Random Number Generator, rng.
...
// Create McEliece parameters.
iqr_McElieceParams *params = NULL;
iqr_retval result = iqr_McElieceCreateParams(context, hash_algorithm,
    public_key_size, &params);
if (result != IQR_OK) {
    // Handle error.
}

// Create a key pair.
iqr_McEliecePublicKey *public_key = NULL;
iqr_McEliecePrivateKey *private_key = NULL;

result = iqr_McElieceCreateKeyPair(params, rng, &public_key, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

Generating a key pair for large keys can take a long time.

If iqr_McElieceCreateKeyPair() fails, you may need to reseed your random number generator (see Random Number Generators).

There are three supported public key sizes for each of two security strengths (128-bit and 256-bit classical security). For 128-bit security:

  • IQR_MCELIECE_PUBKEY9857

  • IQR_MCELIECE_PUBKEY14866

  • IQR_MCELIECE_PUBKEY20409

For 256-bit security:

  • IQR_MCELIECE_PUBKEY32771

  • IQR_MCELIECE_PUBKEY45062

  • IQR_MCELIECE_PUBKEY61449

The larger key sizes provide more reliability during decryption at the cost of slower key generation and larger key sizes (see Technical Information for specific key sizes).

Using McEliece

To encrypt data:

...
// Find out how large the ciphertext will be.
size_t ciphertext_size = 0;
result = iqr_McElieceGetCiphertextSize(params, plaintext_size,
    &ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

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

// Encrypt the plaintext.
result = iqr_McElieceEncrypt(public_key, rng, plaintext_buffer, plaintext_size,
    ciphertext_buffer, &ciphertext_size);
if (result != IQR_OK) {
    // Handle error.
}

To decrypt data:

// Find out how large the plaintext will be.
size_t plaintext_size = 0;
result = iqr_McElieceGetPlaintextSize(params, ciphertext_size, &plaintext_size);
if (result != IQR_OK) {
    // Handle error.
}

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

// Attempt to decrypt the ciphertext.
result = iqr_McElieceDecrypt(private_key, ciphertext_buffer, ciphertext_size,
    plaintext_buffer, &plaintext_size);
if (result == IQR_EDECRYPTIONFAILED) {
    // Handle decryption failure.
} else if (result != IQR_OK) {
    // Handle error.
}

If iqr_McElieceDecrypt() fails with IQR_EDECRYPTIONFAILED the data must be encrypted again before making another decryption attempt.

Managing Keys

To export McEliece keys for storage or transmission:

...
// Find out how large the keys are.
size_t public_key_size = 0;
result = iqr_McElieceGetPublicKeySize(public_key, &public_key_size);
if (result != IQR_OK) {
    // Handle error.
}

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

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

size_t private_key_size = 0;
result = iqr_McElieceGetPrivateKeySize(private_key, &private_key_size);
if (result != IQR_OK) {
    // Handle error.
}

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

result = iqr_McElieceExportPrivateKey(private_key, private_key_buffer,
    private_key_size);
if (result != IQR_OK) {
    // Handle error.
}

To import McEliece keys from buffers:

...
// Create an iqr_McElieceParams object using the same hash algorithm and
// public key size used to create these keys.
...
iqr_McEliecePublicKey *public_key = NULL;
result = iqr_McElieceImportPublicKey(params, public_key_buffer, public_key_size,
    &public_key);
if (result != IQR_OK) {
    // Handle error.
}

iqr_McEliecePrivateKey *private_key = NULL;
result = iqr_McElieceImportPrivateKey(params, private_key_buffer,
    private_key_size, &private_key);
if (result != IQR_OK) {
    // Handle error.
}

Technical Information

This section provides further information about some technical aspects of the IQR Toolkit.

Key Sizes

LMS is believed to have 256-bits of classical security, giving it 128-bits of quantum security.

LMS digital signature scheme key sizes:

Table 3. LMS Key Sizes
Params Public Key (bytes) Private Key (bytes)

h = 5, w = 1

68

32,784

h = 5, w = 2

68

17,040

h = 5, w = 4

68

8,592

h = 5, w = 8

68

4,368

h = 10, w = 1

68

1,049,088

h = 10, w = 2

68

545,280

h = 10, w = 4

68

274,944

h = 10, w = 8

68

139,776

h = 20, w = 1

68

1,074,266,112

h = 20, w = 2

68

558,366,720

h = 20, w = 4

68

281,542,656

h = 20, w = 8

68

143,130,624

McEliece QC-MDPC with CCA2 Conversion Gamma key sizes:

Table 4. McEliece Key Sizes
Public Key (bytes) Private Key (bytes) Classical Security Quantum Security

1,233

2,465

128-bit

64-bit

1,859

2,788

128-bit

64-bit

2,552

3,402

128-bit

64-bit

4,097

8,193

256-bit

128-bit

5,633

8,450

256-bit

128-bit

7,682

10,242

256-bit

128-bit

Build Options

During development of the IQR 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-vla -Wno-packed -Wno-padded -Wno-reserved-id-macro -Wno-disabled-macro-expansion -Wno-documentation-unknown-command -Wno-missing-field-initializers

  • -fvisibility=hidden -fPIC

  • -std=c99

  • -O3

  • -DNDEBUG -D_FORTIFY_SOURCE=2

  • -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 -Wold-style-definition -Wpedantic -Wredundant-decls -Wshadow -Wstrict-prototypes -Wswitch-default -Wuninitialized -Wunreachable-code -Wunused -Wvarargs -Wwrite-strings -Wstack-usage=1024 -Wframe-larger-than=512

  • -fstrict-aliasing -fstrict-overflow -funsafe-loop-optimizations -fstack-check -fstack-protector-all -fvisibility=hidden -fdata-sections -ffunction-sections -fPIC

  • -pedantic

  • -pipe

  • -std=c99

  • -O3

  • -DNDEBUG -D_FORTIFY_SOURCE=2

  • -Werror

  • -D_GNU_SOURCE=1 (for Linux, Windows, and Cygwin)

Linker Options

When building with clang:

  • -Wl,-undefined,error (for Mac OS X) or -Wl,--no-undefined (others)

When building with gcc:

  • -Wl,-dead_strip (for Mac OS X) or -Wl,--gc-sections (others)

  • -Wl,-undefined,error (for Mac OS X) or -Wl,--no-undefined (others)

Code Stripping

The IQR 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 and RNG 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.

Linux libc

The Linux version of the IQR Toolkit is built and tested against the following versions of the libc library:

  • 2.23 (Ubuntu, 64-bit)

  • 2.24 (Arch, 64-bit)

The ISARA Toolkit is licensed for use:

Copyright © 2015-2016, 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.

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.