ASCON Suite
Using the ASCON SnP Permutation API

The core of the ASCON Suite of algorithms is the ASCON permutation. The ascon_permute() function operates on a 320-bit (40-byte) input through a number of rounds, producing a 320-bit output. The number of rounds is configurable between 1 and 12.

Standard ASCON modes typically use 6, 8, or 12 rounds so there are convenient macros for those values: ascon_permute6(), ascon_permute8(), and ascon_permute12().

The permutation implementations for each platform are located in the src/core directory. Each backend provides a "state and permutation" (SnP) interface to the rest of the library and user applications.

Most user applications have no need for the SnP API because they can use the relevant encryption or hashing mode from the library directly. The public SnP API is provided just in case an application has a specific requirement that isn't met by the standard modes. This page describes how to use the permutation directly should the application need to do so.

Initializing and finalizing the permutation

The permutation API is declared in the header permutation.h, included into application code as follows:

Direct access to the ASCON permutation primitive.

The 320-bit input (and output) to the permutation is held in a structure called ascon_state_t. The state must be initialized before it can be used by the application:

void ascon_init(ascon_state_t *state)
Initializes the words of the ASCON permutation state to zero.
ascon_state_t state
[snippet_key]
Definition: snippets.c:2
Structure of the internal state of the ASCON permutation.
Definition: permutation.h:63

Initialization sets all of the bits of the ASCON state to zero.

Once the application is finished with the state, it must finalize it with ascon_free():

void ascon_free(ascon_state_t *state)
Frees an ASCON permutation state and attempts to destroy any sensitive material.

Finalization will attempt to destroy any sensitive material still in the "state" structure. But the compiler's optimizer may interfere with the execution order of the program and defeat the attempt. Or there may still be fragments of the sensitive material elsewhere on the stack or in registers. All we can do is a "best effort" to clean up the state given the system's constraints.

For buffers in your own code, use ascon_clean() to destroy sensitive material when you no longer need it. The same caveats about the compiler apply.

Absorbing data into the permutation state

Modes that use ASCON typically start by absorbing an initialization vector value into the state and then calling the permutation. The following is the initialization sequence for the ASCON-HASHA algorithm:

static unsigned char const iv[8] = {
0x00, 0x40, 0x0c, 0x04, 0x00, 0x00, 0x01, 0x00
};
#define ascon_permute12(state)
Permutes the ASCON state with 12 rounds of the permutation.
Definition: permutation.h:199
void ascon_overwrite_bytes(ascon_state_t *state, const uint8_t *data, unsigned offset, unsigned size)
Overwrites existing bytes in the ASCON state.

We can use either ascon_add_bytes() or ascon_overwrite_bytes() to absorb the initialization vector; ascon_overwrite_bytes() is slightly faster for this use case because we know that ascon_init() set the state to all-zeroes.

ASCON-HASHA absorbs the incoming data in blocks of 8 bytes in length, invoking the permutation for 8 rounds between each block:

for (posn = 0; (posn + 8) <= len; posn += 8) {
ascon_add_bytes(&state, data + posn, 0, 8);
}
void ascon_add_bytes(ascon_state_t *state, const uint8_t *data, unsigned offset, unsigned size)
Adds bytes to the ASCON state by XOR'ing them with existing bytes.
#define ascon_permute8(state)
Permutes the ASCON state with 8 rounds of the permutation.
Definition: permutation.h:206
unsigned char data[8]
[snippet_key]
Definition: snippets.c:14

The last partial input block is padded with a 0x80 byte and then the permutation is run for a full 12 rounds:

static unsigned char const pad[1] = {0x80};
ascon_add_bytes(&state, data + posn, 0, len - posn);
ascon_add_bytes(&state, pad, len - posn, 1);

PRNG algorithms based on a sponge function protect themselves from being run backwards by extracting some data and XOR'ing it back into the state to zero out those bytes. The state is protected from being run backwards because an attacker would need to guess the bytes before they were zeroed:

unsigned char data[8];
ascon_clean(data, sizeof(data));
void ascon_extract_bytes(const ascon_state_t *state, uint8_t *data, unsigned offset, unsigned size)
Extracts bytes from the ASCON state.
void ascon_clean(void *buf, unsigned size)
Cleans a buffer that contains sensitive material.
Definition: ascon-clean.c:38

A more efficient method that doesn't need a temporary buffer is to use the function ascon_overwrite_with_zeroes() instead:

void ascon_overwrite_with_zeroes(ascon_state_t *state, unsigned offset, unsigned size)
Overwrites a part of the ASCON state with zeroes.

Squeezing data out of the permutation state

ASCON-HASHA extracts 32 bytes from the permutation state for the hash value, 8 bytes at a time. This is done with the ascon_extract_bytes() function:

unsigned char hash[32];
ascon_extract_bytes(&state, hash, 0, 8);
ascon_extract_bytes(&state, hash + 8, 0, 8);
ascon_extract_bytes(&state, hash + 16, 0, 8);
ascon_extract_bytes(&state, hash + 24, 0, 8);

Full source code for the hashing example

Encrypting and decrypting with a permutation state

If we were implementing an encryption mode, we would start by absorbing the 16-byte key and the 16-byte nonce after the initialization vector, but before the permutation call:

static unsigned char const iv[8] = {
0x80, 0x40, 0x0c, 0x06, 0xFF, 0xFF, 0xFF, 0xFF
};
ascon_overwrite_bytes(&state, nonce, 24, 16);

There are many ways to encrypt using a permutation like ASCON. The library contains several for AEAD modes. We will explore two methods, loosely based on the Output FeedBack (OFB) and Cipher FeedBack (CFB) modes for block ciphers.

In OFB mode, the permutation is iterated and extracted output is XOR'ed with the plaintext m to produce the ciphertext c:

void ofb_encrypt(ascon_state_t *state, unsigned char *c, const unsigned char *m, int len)
{
int posn;
for (posn = 0; (posn + 16) <= len; posn += 16) {
ascon_extract_and_add_bytes(state, m + posn, c + posn, 0, 16);
}
if (posn < len) {
ascon_extract_and_add_bytes(state, m + posn, c + posn, 0, len - posn);
}
}
void ascon_extract_and_add_bytes(const ascon_state_t *state, const uint8_t *input, uint8_t *output, unsigned offset, unsigned size)
Extracts bytes from the ASCON state and XOR's them with input bytes to produce output bytes.
void ofb_encrypt(ascon_state_t *state, unsigned char *c, const unsigned char *m, int len)
[snippet_zero2]
Definition: snippets.c:25

The ascon_extract_and_add_bytes() function extracts bytes from the permutation state and XOR's them with the input buffer in its second argument to produce the bytes in the output buffer in its third argument. The permutation state itself is unmodified.

The OFB encryption function can also be used for decryption by swapping the m and c arguments. It is its own inverse.

In CFB mode, we incorporate the plaintext into the state. Future blocks depend not only on the permutation state but also all of the plaintext to date. This is a common construction for ciphers when we want authenticate the plaintext in addition to encrypting it:

void cfb_encrypt(ascon_state_t *state, unsigned char *c, const unsigned char *m, int len)
{
int posn;
for (posn = 0; (posn + 16) <= len; posn += 16) {
ascon_add_bytes(state, m + posn, 0, 16);
ascon_extract_bytes(state, c + posn, 0, 16);
}
if (posn < len) {
ascon_add_bytes(state, m + posn, 0, len - posn);
ascon_extract_bytes(state, c + posn, 0, len - posn);
}
}
void cfb_encrypt(ascon_state_t *state, unsigned char *c, const unsigned char *m, int len)
[snippet_encrypt_ofb]
Definition: snippets.c:39

The ascon_add_bytes() call XOR's the plaintext block with the state to encrypt it and incorporate it into the state. The ascon_extract_bytes() call pulls out the encrypted data.

CFB decryption has a slightly different structure to encryption:

void cfb_decrypt(ascon_state_t *state, unsigned char *m, const unsigned char *c, int len)
{
int posn;
for (posn = 0; (posn + 16) <= len; posn += 16) {
ascon_extract_and_add_bytes(state, m + posn, c + posn, 0, 16);
ascon_overwrite_bytes(state, c + posn, 0, 16);
}
if (posn < len) {
ascon_extract_and_add_bytes(state, m + posn, c + posn, 0, len - posn);
ascon_overwrite_bytes(state, c + posn, 0, len - posn);
}
}
void cfb_decrypt(ascon_state_t *state, unsigned char *m, const unsigned char *c, int len)
[snippet_encrypt_cfb]
Definition: snippets.c:55

The ascon_extract_and_add_bytes() call extracts some of the state and XOR's it with the ciphertext block to decrypt it. The original ciphertext block is then incorporated into the state for the next step.

There is a problem with this code however. This will not work if m and c are the same buffer for in-place decryption. We could save the ciphertext block in a temporary location before the call to ascon_extract_and_add_bytes(). A more efficient method is to use ascon_extract_and_overwrite_bytes():

void cfb_decrypt(ascon_state_t *state, unsigned char *m, const unsigned char *c, int len)
{
int posn;
for (posn = 0; (posn + 16) <= len; posn += 16) {
ascon_extract_and_overwrite_bytes(state, c + posn, m + posn, 0, 16);
}
if (posn < len) {
ascon_extract_and_overwrite_bytes(state, c + posn, m + posn, 0, len - posn);
}
}
void ascon_extract_and_overwrite_bytes(ascon_state_t *state, const uint8_t *input, uint8_t *output, unsigned offset, unsigned size)
Extracts bytes from the ASCON state and XOR's them with input bytes to produce output bytes....

If we were building an authenticated cipher based around CFB mode, we would next extract some bytes from the permutation state to act as the authentication tag:

void cfb_auth_encrypt(ascon_state_t *state, unsigned char *c, const unsigned char *m, int len, unsigned char tag[16])
{
static unsigned char const pad[1] = {0x80};
int posn;
for (posn = 0; (posn + 16) <= len; posn += 16) {
ascon_add_bytes(state, m + posn, 0, 16);
ascon_extract_bytes(state, c + posn, 0, 16);
}
if (posn < len) {
ascon_add_bytes(state, m + posn, 0, len - posn);
ascon_extract_bytes(state, c + posn, 0, len - posn);
}
ascon_add_bytes(state, pad, len - posn, 1); /* padding */
ascon_extract_bytes(state, tag, 24, 16); /* extract the tag */
}
void cfb_auth_encrypt(ascon_state_t *state, unsigned char *c, const unsigned char *m, int len, unsigned char tag[16])
[snippet_decrypt_cfb2]
Definition: snippets.c:85

Full source code for the CFB encryption example

Suspending use of the permutation

Normally you initialize the ASCON state, perform the add or extract operations you need, and then free the state in a single sequence. Combined high-level functions in the API like ascon_hash() and ascon128_aead_encrypt() do this.

For incremental API's, there may be an unknown amount of time between uses of the permutation. Incremental functions like ascon_hash_update() and ascon_xof_squeeze() do this.

If the backend is using a shared accelerated hardware module then you don't want to keep it tied up indefinitely. Other tasks on the system may want to use it.

The appropriate thing to do is to call ascon_release() to return the hardware module to the system, and then call ascon_acquire() when you want to resume using ASCON:

// perform some operations with the state
...;
// go off and do something else
...;
// continue using the state
...;
void ascon_release(ascon_state_t *state)
Temporarily releases access to any shared hardware resources that a permutation state was using.
void ascon_acquire(ascon_state_t *state)
Re-acquires access to any shared hardware resources that a permutation state was using.

The ASCON state is implicitly acquired when ascon_init() is called and implicitly released when ascon_free() is called. The ascon_release() and ascon_acquire() functions are not recursive and do not "count" how many acquisitions are in effect by the current task. The behaviour is undefined if they are called in the wrong order.

Incremental API's in the library release and acquire the state automatically so you don't have to do anything if you are using the library's modes.

Copying the permutation state

Sometimes you may want to copy the entire state of the permutation to a new object. The ISAP mode in the library makes use of this. The ISAP key state is precomputed ahead of time. When a message is encrypted, the per-message state is initialized by copying the precomputed state.

ascon_state_t precomputed;
...;
ascon_copy(&copy, &precomputed);
void ascon_copy(ascon_state_t *dest, const ascon_state_t *src)
Copies the entire ASCON permutation state from a source to a destination.

It is important that the "precomputed" state be released. The copy process will create a brand new state in "copy" and acquire it.

State representation

The current backends store the 320 bits of the ASCON state directly in the ascon_state_t structure. But this may not be the case in the future. Accelerated backends may need more memory than 320 bits, or need handles to access secure hardware modules. Thus, some versions of ascon_init() may allocate memory from the heap and store a pointer into the ascon_state_t structure. The memory is freed when ascon_free() is called.

It is very important that the contents of ascon_state_t be treated as opaque. You may be tempted to directly XOR your data with the state, but it won't work. Even with the current backends, the 320 bits are not guaranteed to be stored in the canonical ASCON bit order.

For efficiency reasons, the backend is allowed to store the bits of the state in any order it wishes. Some use the canonical order (e.g. avr5), others use little-endian 64-bit words (e.g. x86-64 and armv8a), and still others use a 32-bit bit-sliced representation where the even and odd bits of the state are split into separate words (e.g. armv6, armv7m, i386, m68k, etc).

There is no method outside of the library to detect which bit order is being used. Applications should use the "add", "overwrite", and "extract" functions in the SnP API to manipulate the permutation state. This design choice simplifies the public permutation API and the applications that use it.

The modes that are implemented inside the library are aware of which backend is in use and can optimize "add", "overwrite", and "extract" operations accordingly. This is why it is usually better to use the library's modes than invent your own.

Use ascon_extract_bytes() with a 40-byte buffer if you want to extract the entire ASCON state in the canonical bit order. And use ascon_overwrite_bytes() to populate the entire ASCON state from a 40-byte buffer in the canonical bit order. The following example permutes a test vector with 6 rounds of the permutation:

unsigned char test_vector[40] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27
};
ascon_overwrite_bytes(&state, test_vector, 40);
ascon_extract_bytes(&state, test_vector, 40);
#define ascon_permute6(state)
Permutes the ASCON state with 6 rounds of the permutation.
Definition: permutation.h:213