Introduction
The ISARA Radiate Security Solution Suite 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 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 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.
-
Hashing — How to use the toolkit’s BLAKE2b, 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.
-
Symmetric Encryption — How to use the ChaCha20 symmetric-key encryption algorithm.
-
Digital Signatures — How to use the Dilithium, HSS, Rainbow, and XMSS digital signature schemes.
-
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 ZIP 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. -
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.
-
ISARA Radiate Security Solution Suite Signature Edition 1.5 Documentation
-
1-800-319-8576 Toll-free (please refer to your support contract)
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:
Algorithm | Digest |
---|---|
MD5 |
|
SHA1 |
|
SHA2-256 |
|
SHA2-512 |
|
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. -
dilithium
— Generate Dilithium keys, sign a file’s data with a Dilithium key, and verify a Dilithium signature. -
hash
— Hash a file’s data using BLAKE2b-256, BLAKE2b-512, 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. -
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. -
xmss
— Generate keys, sign a file’s data, detach signatures from a private key state, and verify a signature using the XMSS algorithm.
To compile the samples, you will need:
-
C99 compliant compiler; recent versions of
clang
orgcc
are preferred -
cmake
3.7 or newer -
GNU
make
3.8 or newer (or some other build tool supported by your version ofcmake
)
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:
-
cd
to thesample
directory. -
mkdir build
-
cd
to thebuild
directory. -
cmake ..
-
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:
-
Create object.
-
Do things with the object.
-
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()
.
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 BLAKE2b, SHA2 and SHA3 hash algorithms:
-
IQR_HASH_DEFAULT_BLAKE2B_256
- C implementation of BLAKE2b-256 -
IQR_HASH_DEFAULT_BLAKE2B_512
- C implementation of BLAKE2b-512 -
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
andIQR_VERSION_MINOR
provide the major and minor versions for the toolkit. They can be combined asmajor.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:
-
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 perform symmetric encryption using a shared secret, see Symmetric Encryption.
-
To digitally sign and verify messages, see Digital Signatures.
-
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.
Hashing
Getting a hash digest for a message is a basic building block of many cryptographic algorithms. The toolkit provides implementations of BLAKE2b, 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:
-
BLAKE2b-256 (
IQR_HASHALGO_BLAKE2B_256
) —IQR_HASH_DEFAULT_BLAKE2B_256
-
BLAKE2b-512 (
IQR_HASHALGO_BLAKE2B_512
) —IQR_HASH_DEFAULT_BLAKE2B_512
-
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 BLAKE2b, 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.
}
Randomized Hashing
The toolkit lets you use the randomized hashing algorithm described in
Strengthening
Digital Signatures via Randomized Hashing (by Shai Halevi and Hugo Krawczyk)
by adding salt to your iqr_Hash
object.
Note
|
The Randomized Hashing algorithm only supports Merkle-Damgård hashes, such as BLAKE2b and SHA2. You cannot use it with SHA3. |
The salt data is added to the hash’s state whenever additional data is added to the hash. The salt data must be at least 16 bytes, and will be expanded or truncated to match the hash’s block length.
To add salt to your 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.
}
result = iqr_HashSetSalt(hash, salt_buffer, salt_buffer_size);
if (result != IQR_OK) {
// Handle error.
}
You can call iqr_HashSetSalt()
before iqr_HashBegin()
or after
iqr_HashEnd()
, and the salt data is applied to all hashing operations
performed using the same iqr_Hash
object.
Because the salt can be expanded or truncated, you can use
iqr_HashGetSaltSize()
and iqr_HashGetSalt()
to retrieve the modified salt
data:
...
size_t salt_size = 0;
result = iqr_HashGetSaltSize(hash, &salt_size);
if (result != IQR_OK) {
// Handle error.
}
uint8_t *salt_data = calloc(1, salt_size);
if (salt_data == NULL) {
// Handle error.
}
result = iqr_HashGetSalt(hash, salt_data, salt_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 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.
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 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 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:
-
Create the MAC using
iqr_MACCreateHMAC()
oriqr_MACCreatePoly1305()
. -
Begin the MAC operation.
-
Update it with data.
-
End the MAC and get the tag.
-
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.
}
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:
If you’re signing hash digests rather than full messages, use a hash that provides at least 48 bytes of output:
-
BLAKE2b-512
-
SHA2-384, SHA2-512
-
SHA3-384, SHA3-512
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.
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.
iqr_DilithiumParams *params = NULL;
iqr_retval result = iqr_DilithiumCreateParams(context, variant, ¶ms);
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, rng, index, 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(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_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(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_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 height (iqr_HSSHeight
) and
Winternitz (iqr_HSSWinternitz
) parameters. The height controls the number of
one-time signatures available in the private key. The height can be one of:
-
IQR_HSS_HEIGHT_5
— 25 (32) one-time signatures -
IQR_HSS_HEIGHT_10
— 210 (1024) one-time signatures -
IQR_HSS_HEIGHT_15
— 215 (32,768) one-time signatures -
IQR_HSS_HEIGHT_20
— 220 (1,048,576) one-time signatures -
IQR_HSS_HEIGHT_25
— 225 (33,554,432) 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_HSS_WINTERNITZ_1
-
IQR_HSS_WINTERNITZ_2
-
IQR_HSS_WINTERNITZ_4
— Suggested best time/space trade-off. -
IQR_HSS_WINTERNITZ_8
The hyper-tree levels parameter (iqr_HSSLevels
) creates a binary tree of
Merkle trees of the specified height (iqr_HSSHeight
). Each level of the
hyper-tree is signed with the level above it (except the top-most tree).
The HSS signature is a concatenation of Winternitz one-time signatures, one
per level. Thus, signatures grow in relation to the number of levels within
the hyper-tree. In this release, IQR_HSS_LEVEL_1
is the only supported HSS
hyper-tree parameter.
As an example, a two-level hyper-tree of height 5 on each level provides
210 signatures and is equivalent to using IQR_HSS_HEIGHT_10
and
IQR_HSS_LEVEL_1
. The signature size doubles due to the fact that
each level produces a Winternitz signature.
It’s up to the user to manage domain parameters; the parameter data is not exposed in stored keys or signatures.
HSS state is larger depending on the height and the Winternitz value used; see the Technical Information section for key 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_BDS_STRATEGY,
winternitz, height, IQR_HSS_LEVEL_1, ¶ms);
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(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_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(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_HSSExportPrivateKey(private_key, private_key_data,
private_key_data_size);
if (result != IQR_OK) {
// Handle error.
}
size_t state_size = 0;
result = iqr_HSSGetStateSize(state, &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 13 document.
Signature Format
HSS signatures are stored in the format defined in the Hash-Based Signatures IETF Draft 13 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.
iqr_RainbowParams *params = NULL;
iqr_retval result = iqr_RainbowCreateParams(context, variant, ¶ms);
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, index, 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, &signature_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.
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 height (iqr_XMSSHeight
)
parameter. The height controls the number of one-time signatures available in
the private key. The height can be one of:
-
IQR_XMSS_HEIGHT_10
— 210 (1024) one-time signatures -
IQR_XMSS_HEIGHT_16
— 216 (65,536) one-time signatures -
IQR_XMSS_HEIGHT_20
— 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.
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_STRATEGY,
height, ¶ms);
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(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_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(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_XMSSExportPrivateKey(private_key, private_key_data,
private_key_data_size);
if (result != IQR_OK) {
// Handle error.
}
size_t state_size = 0;
result = iqr_XMSSGetStateSize(state, &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.
Technical Information
This section provides further information about some technical aspects of the toolkit.
Key and Signature Sizes
Digital signature schemes:
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:
Variant | Public Key (bytes) | Private Key (bytes) |
---|---|---|
128 bit |
1472 |
15816 |
160 bit |
1760 |
19176 |
Dilithium signs a message, producing these signature sizes:
Variant | Signature (bytes) |
---|---|
128 bit |
2700 |
160 bit |
3365 |
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.
The Winternitz parameter and tree strategy have no effect on HSS key sizes. The Winternitz parameter has no effect on HSS private key state sizes.
HSS digital signature scheme key sizes:
Params | Public Key (bytes) | Private Key (bytes) | BDS State (bytes) | Full Tree State (bytes) |
---|---|---|---|---|
h = 5 |
60 |
56 |
412 |
2,064 |
h = 10 |
60 |
56 |
1260 |
65,552 |
h = 15 |
60 |
56 |
2748 |
2,097,168 |
h = 20 |
60 |
56 |
5004 |
67,108,880 |
h = 25 |
60 |
56 |
7904 |
2,147,483,664 |
The tree strategy has no effect on HSS signature sizes:
Params | Signature (bytes) | |
---|---|---|
h = 5 |
w = 1 |
8688 |
h = 5 |
w = 2 |
4464 |
h = 5 |
w = 4 |
2352 |
h = 5 |
w = 8 |
1296 |
h = 10 |
w = 1 |
8848 |
h = 10 |
w = 2 |
4624 |
h = 10 |
w = 4 |
2512 |
h = 10 |
w = 8 |
1456 |
h = 15 |
w = 1 |
9008 |
h = 15 |
w = 2 |
4784 |
h = 15 |
w = 4 |
2672 |
h = 15 |
w = 8 |
1616 |
h = 20 |
w = 1 |
9168 |
h = 20 |
w = 2 |
4944 |
h = 20 |
w = 4 |
2832 |
h = 20 |
w = 8 |
1776 |
h = 25 |
w = 1 |
9328 |
h = 25 |
w = 2 |
5104 |
h = 25 |
w = 4 |
2992 |
h = 25 |
w = 8 |
1936 |
Rainbow Keys and Ciphertexts
Variant | Public Key (bytes) | Private Key (bytes) |
---|---|---|
IIIb (GF(31), 64, 32, 48) |
564,534 |
409,462 |
IIIc (GF(256), 68, 36, 36) |
720,792 |
537,780 |
IVa (GF(16), 56, 48, 48) |
565,488 |
376,140 |
Vc (GF(256), 92, 48, 48) |
1,723,680 |
1,274,316 |
VIa (GF(16), 76, 64, 64) |
1,351,360 |
892,078 |
VIb (GF(31), 84, 56, 56) |
1,456,224 |
1,016,867 |
Rainbow signs a message producing this signature size:
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 |
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:
Params | Public Key (bytes) | Private Key (bytes) | BDS State (bytes) | Full Tree State (bytes) |
---|---|---|---|---|
h = 10 |
68 |
104 |
1276 |
65,568 |
h = 16 |
68 |
104 |
3152 |
4,194,336 |
h = 20 |
68 |
104 |
5020 |
67,108,896 |
Tree strategy has no effect on XMSS signature sizes:
Params | Signature (bytes) |
---|---|
h = 10 |
2500 |
h = 16 |
2692 |
h = 20 |
2820 |
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)
Building on Windows
When building an application that links against the toolkit static library
(libiqr_toolkit_signature_static.lib
), add the library under Linker > Input > Additional
Dependencies in Visual Studio. No other changes to your Solution file are
necessary.
When linking dynamically, you must specify the import library
(libiqr_toolkit_signature.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_signature.dll
)
must reside in a location where it can be found by the linker.
Legal
The ISARA Radiate Security Solution Suite is licensed for use:
Copyright © 2015-2018, 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.