libaegis

repository·main·Indexed 40 days ago

https://github.com/aegis-aead/libaegis

A high-performance, portable C implementation of the AEGIS family of authenticated ciphers. It supports variants including AEGIS-128L, AEGIS-128X2, AEGIS-128X4, AEGIS-256, AEGIS-256X2, and AEGIS-256X4. Key features include a Random-Access File (RAF) API for encrypted filesystems, incremental encryption/decryption, AEGIS-MAC, and hardware acceleration for AES-NI, ARM Crypto, AVX2, and AVX512.

Tokens
5.7K
Snippets
10
Records
13
Agent score
50%

What's inside libaegis

  1. Overview of libaegis features

    main

    libaegis provides portable C implementations of the AEGIS family of high-performance authenticated ciphers. It supports runtime CPU detection to utilize hardware acceleration where available.

    Supported AEGIS Variants:

    • AEGIS-128L: 16 and 32 bytes tags (software, AES-NI, ARM Crypto, Altivec).
    • AEGIS-128X2: 16 and 32 bytes tags (software, VAES + AVX2, AES-NI, ARM Crypto, Altivec).
    • AEGIS-128X4: 16 and 32 bytes tags (software, AVX512, VAES + AVX2, AES-NI, ARM Crypto, Altivec).
    • AEGIS-256: 16 and 32 bytes tags (software, AES-NI, ARM Crypto, Altivec).
    • AEGIS-256X2: 16 and 32 bytes tags (software, VAES + AVX2, AES-NI, ARM Crypto, Altivec).
    • AEGIS-256X4: 16 and 32 bytes tags (software, AVX512, VAES + AVX2, AES-NI, ARM Crypto, Altivec).

    Key Capabilities:

    • AEGIS-MAC with incremental updates.
    • Encryption/decryption with attached or detached tags.
    • Incremental encryption and decryption.
    • Random-Access File (RAF) API for encrypted filesystems and databases.
    • Unauthenticated encryption/decryption (stream cipher mode).
    • Deterministic pseudorandom stream generation.
  2. Use Merkle trees for RAF file integrity

    main

    The optional Merkle tree feature maintains a live hash commitment over the entire file's plaintext content. This allows you to generate a single digest representing the current state of the file, useful for remote attestation or detecting out-of-band modifications.

    Implementation Details:

    • Leaf Hashing: Leaves are derived from your hash_leaf callback over plaintext chunk data (not the AEGIS AEAD tags). To keep hashing stable, depend on chunk, chunk_len, and chunk_idx.
    • Configuration: You must provide a aegis_raf_merkle_config containing callbacks for hash_leaf, hash_parent, hash_empty, and hash_commitment.
    • Verification: Use aegis128l_raf_merkle_verify() to check for corruption.
    • Commitment: Use aegis128l_raf_merkle_commitment() to retrieve the root commitment, which includes the file's content, version, algorithm, chunk size, and identity.
    // Provide hash callbacks and a caller-allocated buffer
    aegis_raf_merkle_config merkle = {
        .hash_leaf       = my_hash_leaf,       // hash plaintext chunk data (not the RAF auth tag)
        .hash_parent     = my_hash_parent,     // combine two child digests
        .hash_empty      = my_hash_empty,      // digest for missing/empty nodes
        .hash_commitment = my_hash_commitment, // hash(root, ctx, file_size)
        .hash_len        = 32,                 // digest size (8..64 bytes)
        .max_chunks      = 1024,
        .buf             = merkle_buf,
        .len             = sizeof merkle_buf,
    };
    
    // Pass merkle config when creating the RAF context
    aegis_raf_config cfg = {
        .scratch = &scratch, .chunk_size = 4096,
        .flags = AEGIS_RAF_CREATE, .merkle = &merkle,
    };
    
    // After writes, verify Merkle state against current file contents or read the root commitment
    aegis128l_raf_merkle_verify(&ctx, &corrupted_chunk);
    uint8_t root[32];
    aegis128l_raf_merkle_commitment(&ctx, root, sizeof root);
  3. Initialize libaegis with aegis_init()

    main

    Before using any part of the library, you must call aegis_init(). This function performs CPU capability checks to ensure the library uses the fastest available hardware implementations for subsequent cryptographic operations.

    #include <aegis.h>
    
    int main(void) {
        aegis_init();
        // Proceed with cryptographic operations
        return 0;
    }
  4. Install libaegis using CMake

    main

    To build libaegis using CMake, follow the standard build and install procedure. You can specify an installation prefix using DCMAKE_INSTALL_PREFIX.

    Standard Build and Install:

    mkdir build
    cd build
    cmake -DCMAKE_INSTALL_PREFIX=/install/prefix ..
    make install

    Shared Library Build: To build as a shared library, use the -DBUILD_SHARED_LIBS=On flag.

    Performance Optimization: To favor performance over side-channel mitigations on devices without hardware acceleration, add -DFAVOR_PERFORMANCE.

    WebAssembly (WASM) Configuration: To disable relaxed SIMD in a CMake build, use -DAEGIS_WASM_RELAXED_SIMD=OFF.

  5. Use the Random-Access File (RAF) API

    main

    The RAF API allows for working with encrypted files as if they were regular files. It supports reading/writing arbitrary byte ranges, extending, or truncating files without loading the entire file into memory. This is ideal for encrypted filesystems or databases.

    Core Components:

    • Pluggable I/O: You must provide callbacks for read_at, write_at, get_size, set_size, and sync via an aegis_raf_io struct.
    • RNG: You must provide a random number generator callback via aegis_raf_rng.
    • Scratch Buffer: To enable zero-allocation operation, callers must provide a scratch buffer via aegis_raf_scratch. The size is determined by AEGIS128L_RAF_SCRATCH_SIZE(chunk_size) (or the variant-specific equivalent).
    • Contexts: Use aegis*_raf_create() for new files and aegis*_raf_open() for existing files.

    Opening an existing file:

    1. Use aegis_raf_probe() to read the file's algorithm ID and chunk size.
    2. Allocate a scratch buffer based on the probed chunk_size.
    3. Call the appropriate *_raf_open() function.
    #include <aegis.h>
    
    // Allocate scratch buffer (can be stack, heap, or static)
    CRYPTO_ALIGN(64) uint8_t scratch_buf[AEGIS128L_RAF_SCRATCH_SIZE(4096)];
    aegis_raf_scratch scratch = { .buf = scratch_buf, .len = sizeof scratch_buf };
    
    aegis128l_raf_ctx ctx;
    aegis_raf_config cfg = { .scratch = &scratch, .chunk_size = 4096, .flags = AEGIS_RAF_CREATE };
    aegis_raf_io io = { /* your I/O callbacks */ };
    aegis_raf_rng rng = { /* your RNG callback */ };
    
    aegis128l_raf_create(&ctx, &io, &rng, &cfg, master_key);
    aegis128l_raf_write(&ctx, &written, data, len, offset);
    aegis128l_raf_read(&ctx, &bytes_read, len, offset);
    aegis128l_raf_close(&ctx);  // automatically calls sync
  6. Install libaegis using Zig

    main

    To build libaegis using the Zig build system, use the zig build command. The library and headers are installed in the zig-out folder.

    Standard Release Build:

    zig build -Drelease

    Shared Object (Dynamic Linkage):

    zig build -Dlinkage=dynamic -Drelease

    Performance Optimization: On devices without hardware acceleration, you can favor performance over side-channel mitigations by adding -Dfavor-performance:

    zig build -Drelease -Dfavor-performance

    Benchmark Build: To build the included benchmarks:

    zig build -Drelease -Dfavor-performance -Dwith-benchmark

    WebAssembly (WASM) Support: libaegis supports wasm32-freestanding without extensions. simd128 and relaxed_simd are enabled by default for performance. To disable relaxed SIMD:

    zig build -Dwasm-relaxed-simd=false
  7. Compute a MAC incrementally

    main

    AEGIS can function as a standalone Message Authentication Code (MAC). The API supports incremental updates, allowing you to process data in chunks using a state object.

    Usage Rules:

    • Never use the same key for both MAC and encryption.
    • If you need to authenticate multiple messages with the same key, do not re-initialize. Instead, use aegis256_mac_state_clone() to clone the state or aegis256_mac_reset() to reset it.
    • A NULL nonce in aegis256_mac_init() results in an all-zero nonce.
    #include <aegis.h>
    #include <string.h>
    
    int main(void) {
        aegis_init();
    
        uint8_t key[aegis256_KEYBYTES];
        memset(key, 0x42, sizeof key);
    
        /* Initialize the MAC state */
        aegis256_mac_state st;
        aegis256_mac_init(&st, key, NULL);
    
        /* Feed data in chunks */
        aegis256_mac_update(&st, (const uint8_t *) "hello, ", 7);
        aegis256_mac_update(&st, (const uint8_t *) "world", 5);
    
        /* Finalize and get the tag */
        uint8_t tag[32];
        aegis256_mac_final(&st, tag, sizeof tag);
    
        return 0;
    }
  8. Encrypt and decrypt messages using detached mode

    main

    Use the *_encrypt_detached() and *_decrypt_detached() functions when you want the ciphertext and the authentication tag to be stored in separate buffers.

    Important:

    • The ciphertext buffer must be at least the size of the message.
    • The tag buffer must be large enough to hold the authentication tag (e.g., 32 bytes for aegis256).
    • aegis256_decrypt_detached() returns a non-zero value if authentication fails (indicating tampering).

    All AEGIS variants (e.g., aegis128l_, aegis256_, aegis128x2_) follow this same API pattern; you only need to adjust the prefix and the sizes of the key and nonce.

    #include <aegis.h>
    #include <string.h>
    
    int main(void) {
        aegis_init();
    
        uint8_t key[aegis256_KEYBYTES];     /* 32 bytes */
        uint8_t nonce[aegis256_NPUBBYTES];  /* 32 bytes */
        memset(key, 0x42, sizeof key);
        memset(nonce, 0x00, sizeof nonce);
    
        const char *message = "hello, world";
        size_t message_len = strlen(message);
    
        uint8_t ciphertext[128]; 
        uint8_t tag[32];         
    
        /* Encrypt (detached mode) */
        aegis256_encrypt_detached(ciphertext, tag, sizeof tag,
                                  (const uint8_t *) message, message_len,
                                  NULL, 0,   /* no additional data */
                                  nonce, key);
    
        /* Decrypt and verify */
        uint8_t decrypted[128];
    
        if (aegis256_decrypt_detached(decrypted, ciphertext, message_len,
                                      tag, sizeof tag,
                                      NULL, 0,
                                      nonce, key) != 0) {
            /* Authentication failed */
            return 1;
        }
    
        return 0;
    }
  9. Encrypt and decrypt messages using one-shot mode

    main

    The *_encrypt() and *_decrypt() functions (one-shot mode) append the authentication tag directly to the end of the ciphertext.

    Requirement: The output buffer must be at least message_len + maclen bytes to accommodate both the ciphertext and the tag.

  10. Derive context-bound subkeys for RAF files

    main

    To isolate different files or file families using the same master key, use aegis_raf_derive_master_key(). This derives a unique subkey based on a context string.

    Details:

    • Different contexts produce different RAF keys.
    • Opening a file requires the exact same context used during creation; a mismatch will cause header authentication to fail.
    • Context size limits: up to 120 bytes for 128-bit key variants, and up to 72 bytes for 256-bit variants.
    • The caller is responsible for zeroizing the derived raf_key after use.
    uint8_t raf_key[16];
    aegis_raf_derive_master_key(raf_key, sizeof raf_key,
                                app_master_key, sizeof app_master_key,
                                (const uint8_t *) "my-context", 10);
    
    aegis128l_raf_create(&ctx, &io, &rng, &cfg, raf_key);
    // caller is responsible for zeroizing raf_key after use
  11. Use unauthenticated stream encryption

    main

    If your protocol provides its own authentication and only requires a stream cipher, use aegis256_stream_xor(). This function performs both encryption and decryption.

    Warning:

    • This mode does not detect changes to the data. Use it only when an external mechanism authenticates the data.
    • Never reuse a nonce with the same key.
    • aegis256_encrypt_unauthenticated() and aegis256_decrypt_unauthenticated() are deprecated and use a different format; use aegis256_stream_xor() instead.
    • The operation can be performed in-place by passing the same buffer for input and output.
    /* Encrypt in place */
    aegis256_stream_xor(buf, buf, buf_len, nonce, key);
    
    /* Decrypt in place */
    aegis256_stream_xor(buf, buf, buf_len, nonce, key);