LibAscon is an ISO C99/C11 cryptographic library wrapping the reference C implementation of the Ascon family of lightweight authenticated encryption schemes with associated data (AEAD) and hashing functions, but it also includes Init-Update-Final processing and variable tag length. Heavily tested and ready for embedded systems!
This is not a security-hardened implementation, just a simple one, focused mostly on usability, portability, and high(er) set of features compared to the reference implementation. There is no explicit protection against side-channel attacks other than what the Ascon algorithm itself provides by design.
Nevertheless, this implementation:
- uses constant-time operations (should help against timing attacks),
- tries to force the compiler to actually clear sensitive data instead of optimising the operations away (this is hard to achieve properly),
- has 100% line and 100% branch test coverage.
LibAscon provides:
-
3 AEAD ciphers:
- Ascon128 v1.2 (128 bit key, 64 bit rate)
- Ascon128a v1.2 (128 bit key, 128 bit rate)
- Ascon80pq v1.2 (160 bit key, 64 bit rate)
-
4 hashing functions
- Ascon-Hash v1.2 (fixed-length output, 12-rounds absorption/squeeze)
- Ascon-XOF v1.2 (variable-length output, 12-rounds absorption/squeeze)
- Ascon-Hasha v1.2 (fixed-length output, 8-rounds absorption/squeeze)
- Ascon-XOFa v1.2 (variable-length output, 8-rounds absorption/squeeze)
-
Online processing (Init-Update-Final paradigm) for hashing and encryption/decryption. This means that the data can be processed one chunk at the time. Useful when operating on large amounts of data that are not available contiguously in memory, e.g. a too-large file or data in transmission.
-
Offline processing (whole data contiguous in memory) is also available with a simple wrapper.
-
Variable tag length for authenticated encryption: can generate any tag length. Of course at least 16 bytes (128 bits) is recommended.
Note: the tag bytes above 16 bytes are an extension of the original Ascon algorithm using the same sponge squeezing technique as for the XOF;
-
Encryption/decryption can be performed in-place, without the need of a second output buffer.
-
AEAD tag may be provided to a separate location, i.e. not concatenated to the ciphertext.
-
Same performance as the original reference (unoptimised) C implementation in Release mode, about 2x slower in MinSizeRel mode.
-
Hashing functions that can also automatically validate the digest against a known one when the hashing process is completed.
-
A heavily documented developer-friendly API, making it easier to compile and add to your project, both through static and dynamic inclusion.
-
Tested with 100% line and branch* coverage, with CI running on Linux, macOS and Windows with GCC, Clang and CL (MSVC) (*: branch coverage excludes the debugging-asserts).
#include "ascon.h"
// Initialisation
// We need the key and the nonce, both 128 bits.
// Note: Ascon80pq uses longer keys
const uint8_t secret_key[ASCON_AEAD128_KEY_LEN] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6
};
const uint8_t unique_nonce[ASCON_AEAD_NONCE_LEN] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6
};
ascon_aead_ctx_t ctx;
ascon_aead128_init(&ctx, secret_key, unique_nonce);
// Now we feed any associated data into the cipher first
// Our data is fragmented into 2 parts, so we feed one at the time.
const char associated_data_pt1[] = "2 messages will foll";
const char associated_data_pt2[] = "ow, but they are both secret.";
ascon_aead128_assoc_data_update(&ctx, (uint8_t*) associated_data_pt1, strlen
(associated_data_pt1));
ascon_aead128_assoc_data_update(&ctx, (uint8_t*) associated_data_pt2, strlen
(associated_data_pt2));
// Next, we feed the plaintext, which is also fragmented in 2 parts.
const char plaintext_pt1[] = "Hello, I'm a secret mes";
const char plaintext_pt2[] = "sage and I should be encrypted!";
uint8_t buffer[100];
// The ciphertext is generated block-wise, so we need the return value
// to know how to offset the pointer to where the next ciphertext
// part should be written.
size_t ciphertext_len = 0;
ciphertext_len += ascon_aead128_encrypt_update(
&ctx, buffer + ciphertext_len,
(uint8_t*) plaintext_pt1, strlen(plaintext_pt1));
ciphertext_len += ascon_aead128_encrypt_update(
&ctx, buffer + ciphertext_len,
(uint8_t*) plaintext_pt2, strlen(plaintext_pt2));
// Finally, we wrap up the encryption and generate the tag.
// There may still be some trailing ciphertext to be produced.
// The tag length can be specified. ASCON_AEAD_TAG_MIN_SECURE_LEN is
// the minimum recommended (128 b)
uint8_t tag[ASCON_AEAD_TAG_MIN_SECURE_LEN];
ciphertext_len += ascon_aead128_encrypt_final(
&ctx, buffer + ciphertext_len,
tag, sizeof(tag));
// The final function zeroes out the context automatically.
// Now the buffer contains our ciphertext, long ciphertext_len bytes.
// Now we can decrypt, reusing the same key, nonce and associated data
ascon_aead128_init(&ctx, secret_key, unique_nonce);
ascon_aead128_assoc_data_update(&ctx, (uint8_t*) associated_data_pt1,
strlen(associated_data_pt1));
ascon_aead128_assoc_data_update(&ctx, (uint8_t*) associated_data_pt2,
strlen(associated_data_pt2));
// This time, we perform the decryption in-place, in the same buffer
// where the ciphertext it: to do so, we pass the same pointer for
// plaintext and ciphertext.
size_t plaintext_len = 0;
plaintext_len += ascon_aead128_decrypt_update(
&ctx, buffer,
buffer, ciphertext_len);
// The final decryption step automatically checks the tag
bool is_tag_valid = false;
plaintext_len += ascon_aead128_decrypt_final(
&ctx, buffer + plaintext_len,
&is_tag_valid, tag, sizeof(tag));
// The final function zeroes out the context automatically.
buffer[plaintext_len] = '\0'; // Null terminated, because it's text
printf("Decrypted msg: %s, tag is valid: %d\n", buffer, is_tag_valid);
// The macros ASCON_TAG_OK=true and ASCON_TAG_INVALID=false are also
// available if you prefer them over booleans for is_tag_valid.
// We need the key and the nonce, both 128 bits.
// Note: Ascon80pq uses longer keys
const uint8_t secret_key[ASCON_AEAD128_KEY_LEN] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6
};
const uint8_t unique_nonce[ASCON_AEAD_NONCE_LEN] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6
};
// For the "offline" operation, we need all data to be already contiguous
// in memory. The operation is just one Ascon function call for the user.
const char associated_data[] = "1 contiguous message will follow.";
const char plaintext[] = "Hello, I'm a secret message and I should be encrypted!";
uint8_t buffer[100];
uint8_t tag[42];
// To showcase the LibAscon extensions, we will generate an arbitrary-length
// tag. Here is 42 bytes, but shorter-than-default (16) tags are also
// possible e.g. 12 bytes for systems with heavy limitations.
ascon_aead128_encrypt(buffer,
tag,
secret_key,
unique_nonce,
(uint8_t*) associated_data,
(uint8_t*) plaintext,
strlen(associated_data),
strlen(plaintext),
sizeof(tag));
// The function zeroes out the context automatically.
// The decryption looks almost the same. Just for fun, we will again
// reuse the same buffer where the ciphertext is to write the plaintext into.
bool is_tag_valid = ascon_aead128_decrypt(buffer, // Output plaintext
secret_key,
unique_nonce,
(uint8_t*) associated_data,
(uint8_t*) buffer, // Input ciphertext,
tag, // Expected tag the ciphertext comes with
strlen(associated_data),
strlen(plaintext),
sizeof(tag));
// This time we get a boolean as a return value, which is true when
// the tag is OK. To avoid confusion, it can also be compared to
// two handy macros
if (is_tag_valid == ASCON_TAG_OK) {
puts("Correct decryption!");
} else { // ASCON_TAG_INVALID
puts("Something went wrong!");
}
// The function zeroes out the context automatically.
// We want to authenticate a message with a keyed hash and transmit it.
const uint8_t secret_key[] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6
};
const char message_pt1[] = "Hello, I'm some data we need the digest of";
const char message_pt2[] = " but I'm very long and I was not transmit";
const char message_pt3[] = "ted in one contiguous block.";
// Preparing the Ascon-XOF function. The XOF allows arbitrary digest size,
// while Ascon-Hash (ascon_hash_init) has a fixed digest length.
// NOTE: Ascon-Hash and Ascon-XOF produce different outputs!
ascon_hash_ctx_t ctx;
ascon_hash_xof_init(&ctx);
// If we want a Message Authentication Code that also proves authenticity,
// not only integrity of the message, we can simply prepend the key to the
// message to hash, just like we would for SHA-3. Length extension attacks
// are not a problem for Ascon-Hash/XOF, so the HMAC scheme is not needed.
ascon_hash_xof_update(&ctx, (uint8_t*) secret_key, sizeof(secret_key));
// Feeding chunk by chunk
ascon_hash_xof_update(&ctx, (uint8_t*) message_pt1, strlen(message_pt1));
ascon_hash_xof_update(&ctx, (uint8_t*) message_pt2, strlen(message_pt2));
ascon_hash_xof_update(&ctx, (uint8_t*) message_pt3, strlen(message_pt3));
// Finally, we get a digest of arbitrary length
uint8_t digest[21]; // Choose any length from 0 to SIZE_MAX
ascon_hash_xof_final(&ctx, digest, sizeof(digest));
// The final function zeroes out the context automatically.
// Now let's imagine we transmit the message alongside with the digest.
// The receiver also has the secret key and can easily verify the keyed hash.
ascon_hash_xof_init(&ctx);
ascon_hash_xof_update(&ctx, (uint8_t*) secret_key, sizeof(secret_key));
ascon_hash_xof_update(&ctx, (uint8_t*) message_pt1, strlen(message_pt1));
ascon_hash_xof_update(&ctx, (uint8_t*) message_pt2, strlen(message_pt2));
ascon_hash_xof_update(&ctx, (uint8_t*) message_pt3, strlen(message_pt3));
// A handy function computing the obtained digest and validating it
// against the obtained.
bool is_tag_valid = ascon_hash_xof_final_matches(&ctx, digest, sizeof(digest));
if (is_tag_valid == ASCON_TAG_OK) {
puts("Correct decryption!");
} else { // ASCON_TAG_INVALID
puts("Something went wrong!");
}
For more examples, check the test suite and how the Ascon API is used in there.
Only the C standard library, mostly C99 features:
stdint.h
:uint8_t
,uint_fast8_t
,uint64_t
stddef.h
:size_t
,NULL
stdbool.h
:bool
,true
,false
Optional dependency: assert.h
, for assert()
. Used to add runtime checks of
the Ascon API input for debugging (NULL pointers and incorrect order of
function calling). The CMake script uses it only if assert.h
is found and
only in the debug build type (CMAKE_BUILD_TYPE=Debug
). If compiled without
the included CMakeLists.txt
, it's not used unless
ASCON_INPUT_ASSERTS
is defined at compile time. The assertion function
ASCON_ASSERT
can also be changed, if assert()
is not available.
-
Q: May I use this library for-
A: YES! :D The library (and also the Ascon reference implementation) is released under the Creative Commons Zero (CC0) license which basically(*) means you can do whatever you want with it. Commercial purposes, personal projects, modifications of it, anything goes.
(*) for the legally-binding text check the license file.
-
Q: Where is the documentation?
A: The library header file
inc/ascon.h
is documented with Doxygen and you can find it compiled here. -
Q: Why should I use Ascon-AEAD instead of, say, AES?
A: Ascon is designed to be lightweight (great for embedded systems) and natively supports authenticated encryption of data of any length, while AES must be wrapped in an AEAD mode such as AES-GCM, which is well-proven but heavier.
-
Q: Why should I use Ascon-Hash instead of, say, SHA-256?
A: Ascon is designed to be lightweight (great for embedded systems) and does not suffer from length-extension attacks as SHA-256, given its sponge construction (similarly to what SHA-3 does). Additionally Ascon offers also the XOF hashing producing digests of variable length.
-
Q: Who designed Ascon? Who wrote this library?
A: Check the Authors file for details.
-
Q: I don't trust Ascon.
A: Good. You should not keep your guard up for everything, but Ascon has been selected as the primary choice for lightweight authenticated encryption in the final portfolio of the CAESAR competition (2014–2019) and is currently competing in the NIST Lightweight Cryptography competition (2019–) . Cryptographers like it and that's a good sign, right?
-
Q: I don't trust this implementation.
A: Good, again. You can read the source code to see what it does to be sure ;) If you find any bugs or possible improvements, open a pull request or an issue. I would like to make as clear and as good as possible.
- There is no architecture-specific optimisation, only a generic portable
implementation using mostly
uint64_t
data types.
The project's compilation has been tested with GCC, Clang and CL (MSVC). Most compiler warnings have already been mitigated, so they hopefully don't occur on your platform.
The project is relatively small, so you can simply include it into yours as a
Git Subtree, Git Submodule or by simply copy-pasting the inc
and src
folders. Be sure to:
- Add
inc
andsrc
to the include folders list (the internal header is insrc
). - Add
src
to the sources folders list. - Compile.
A note if you are compiling on Windows from the command line:
- you will need the environment variables to use the MSVC toolchain from the command line
- be sure to select the generator
NMake Makefiles
when configuring CMake
Compile an out of source build:
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release # On Windows add: -G "NMake Makefiles"
cmake --build . --parallel 8
This will build all useful targets:
- static libraries:
asconfull
with full feature setascon128
with only Ascon128ascon128a
with only Ascon128aascon80pq
with only Ascon80pqasconhash
with only Ascon-hash and Ascon-XOFascon128hash
with only Ascon128, Ascon-hash and Ascon-XOFascon128ahash
with only Ascon128a, Ascon-hash and Ascon-XOFascon80pqhash
with only Ascon80pq, Ascon-hash and Ascon-XOF for a smaller build result when not all features are needed
ascon
a shared library (.dll
or.dylib
or.so
) with full feature set (likeasconfull
, but shared)testascon
a test runner executable , which test all features of the static librarytestasconshared
a test runner executable , which test all features of the shared library
Doxygen (if installed) is built separately to avoid recompiling it for any library change:
cmake --build . --target ascon_doxygen
To compile only a single target, for example ascon80pq
, run
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . --target ascon80pq
To compile with the optimisation for size, use the
-DCMAKE_BUILD_TYPE=MinSizeRel
flag instead.
To run the test executables, call ctest
in the build directory.