luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

EasyCrypto.h (28396B)


      1 /*
      2  * EasyCrypto - Simple API for Authenticated Encryption
      3  *
      4  * Provides easy-to-use macros for encryption with proper defaults.
      5  * All encryption includes authentication (AEAD).
      6  *
      7  * Usage:
      8  *   ENCRYPT(key, data, length) - Uses AES-256-GCM by default (hardware accelerated)
      9  *   DECRYPT(key, encrypted_data, length) - Automatically verifies and decrypts
     10  *
     11  * With algorithm selection:
     12  *   ENCRYPT_AES(key, data, length) - AES-256-GCM (fastest with AES-NI)
     13  *   ENCRYPT_CHACHA(key, data, length) - ChaCha20-Poly1305 (fast without hardware)
     14  *   ENCRYPT_XCHACHA(key, data, length) - XChaCha20-Poly1305 (192-bit nonces)
     15  *   ENCRYPT_SERPENT(key, data, length) - Serpent-256-GCM (conservative)
     16  *   ENCRYPT_TWOFISH(key, data, length) - Twofish-256-GCM (Bruce Schneier's design)
     17  */
     18 
     19 #ifndef EASYCRYPTO_H
     20 #define EASYCRYPTO_H
     21 
     22 #include "ChaCha20-Poly1305.h"
     23 #include "XChaCha20-Poly1305.h"
     24 #include "Salsa20.h"
     25 #include "AES-256-GCM.h"
     26 #include "AES-128-GCM.h"
     27 #include "Serpent-256-GCM.h"
     28 #include "Twofish-256-GCM.h"
     29 #include "CSPRNG.h"
     30 #include <stdlib.h>
     31 #include <string.h>
     32 
     33 #ifdef __cplusplus
     34 extern "C" {
     35 #endif
     36 
     37 /* Encrypted data structure */
     38 typedef struct {
     39     uint8_t *nonce;          /* 12 bytes for ChaCha20/AES/Serpent/Twofish */
     40     uint8_t *ciphertext;     /* Same length as plaintext */
     41     uint8_t *tag;            /* 16 bytes authentication tag */
     42     size_t nonce_len;
     43     size_t ciphertext_len;
     44     size_t tag_len;
     45     int algorithm;           /* 0=ChaCha20, 1=AES, 2=Serpent, 3=Twofish */
     46 } encrypted_data;
     47 
     48 /* Algorithm identifiers */
     49 #define ALGO_CHACHA20 0
     50 #define ALGO_AES 1
     51 #define ALGO_SERPENT 2
     52 #define ALGO_TWOFISH 3
     53 
     54 /**
     55  * Encrypt data with AES-128-GCM
     56  * Returns serialized format: [Nonce(12)][Tag(16)][Ciphertext(var)]
     57  *
     58  * @param key 128-bit (16 byte) encryption key
     59  * @param plaintext Plaintext data
     60  * @param plaintext_len Plaintext length
     61  * @param output_len Output parameter for encrypted data length
     62  * @return Encrypted data [Nonce][Tag][Ciphertext] (caller must free) or NULL on failure
     63  */
     64 static inline void* easycrypto_encrypt_aes128(
     65     const uint8_t *key, const uint8_t *plaintext, size_t plaintext_len, size_t *output_len) {
     66 
     67     if (!key || !plaintext || !output_len) return NULL;
     68 
     69     /* Calculate output size: 12 (nonce) + 16 (tag) + plaintext_len */
     70     size_t total_len = 12 + 16 + plaintext_len;
     71     uint8_t *output = malloc(total_len);
     72     if (!output) return NULL;
     73 
     74     /* Generate random nonce */
     75     random_bytes(output, 12);
     76 
     77     /* Encrypt */
     78     aes128_gcm_context ctx;
     79     if (aes128_gcm_init(&ctx, key) != 0) {
     80         free(output);
     81         return NULL;
     82     }
     83 
     84     /* output layout: [nonce(12)][tag(16)][ciphertext] */
     85     uint8_t *tag = output + 12;
     86     uint8_t *ciphertext = output + 28;
     87 
     88     if (aes128_gcm_encrypt(&ctx, output, 12, NULL, 0,
     89                           plaintext, plaintext_len,
     90                           ciphertext, tag, 16) != 0) {
     91         aes128_gcm_cleanup(&ctx);
     92         free(output);
     93         return NULL;
     94     }
     95 
     96     aes128_gcm_cleanup(&ctx);
     97     *output_len = total_len;
     98     return output;
     99 }
    100 
    101 /**
    102  * Decrypt data with AES-128-GCM
    103  * Expects format: [Nonce(12)][Tag(16)][Ciphertext(var)]
    104  *
    105  * @param key 128-bit (16 byte) encryption key
    106  * @param encrypted Encrypted data [Nonce][Tag][Ciphertext]
    107  * @param encrypted_len Length of encrypted data
    108  * @param output_len Output parameter for plaintext length
    109  * @return Decrypted data (caller must free) or NULL on failure
    110  */
    111 static inline void* easycrypto_decrypt_aes128(
    112     const uint8_t *key, const void *encrypted, size_t encrypted_len, size_t *output_len) {
    113 
    114     if (!key || !encrypted || !output_len || encrypted_len < 28) return NULL;
    115 
    116     const uint8_t *data = (const uint8_t*)encrypted;
    117 
    118     /* Parse input: [nonce(12)][tag(16)][ciphertext] */
    119     const uint8_t *nonce = data;
    120     const uint8_t *tag = data + 12;
    121     const uint8_t *ciphertext = data + 28;
    122     size_t ciphertext_len = encrypted_len - 28;
    123 
    124     /* Allocate output */
    125     uint8_t *plaintext = malloc(ciphertext_len);
    126     if (!plaintext) return NULL;
    127 
    128     /* Decrypt */
    129     aes128_gcm_context ctx;
    130     if (aes128_gcm_init(&ctx, key) != 0) {
    131         free(plaintext);
    132         return NULL;
    133     }
    134 
    135     if (aes128_gcm_decrypt(&ctx, nonce, 12, NULL, 0,
    136                           ciphertext, ciphertext_len,
    137                           tag, 16, plaintext) != 0) {
    138         aes128_gcm_cleanup(&ctx);
    139         memset(plaintext, 0, ciphertext_len);
    140         free(plaintext);
    141         return NULL;
    142     }
    143 
    144     aes128_gcm_cleanup(&ctx);
    145     *output_len = ciphertext_len;
    146     return plaintext;
    147 }
    148 
    149 /**
    150  * Encrypt data with AES-256-GCM (default)
    151  * Returns serialized format: [Nonce(12)][Tag(16)][Ciphertext(var)]
    152  *
    153  * @param key 256-bit (32 byte) encryption key
    154  * @param plaintext Plaintext data
    155  * @param plaintext_len Plaintext length
    156  * @param output_len Output parameter for encrypted data length
    157  * @return Encrypted data [Nonce][Tag][Ciphertext] (caller must free) or NULL on failure
    158  */
    159 static inline void* easycrypto_encrypt_aes(
    160     const uint8_t *key, const uint8_t *plaintext, size_t plaintext_len, size_t *output_len) {
    161 
    162     if (!key || !plaintext || !output_len) return NULL;
    163 
    164     /* Calculate output size: 12 (nonce) + 16 (tag) + plaintext_len */
    165     size_t total_len = 12 + 16 + plaintext_len;
    166     uint8_t *output = malloc(total_len);
    167     if (!output) return NULL;
    168 
    169     /* Generate random nonce */
    170     random_bytes(output, 12);
    171 
    172     /* Encrypt */
    173     aes256_gcm_context ctx;
    174     if (aes256_gcm_init(&ctx, key) != 0) {
    175         free(output);
    176         return NULL;
    177     }
    178 
    179     /* output layout: [nonce(12)][tag(16)][ciphertext] */
    180     uint8_t *tag = output + 12;
    181     uint8_t *ciphertext = output + 28;
    182 
    183     if (aes256_gcm_encrypt(&ctx, output, 12, NULL, 0,
    184                           plaintext, plaintext_len,
    185                           ciphertext, tag, 16) != 0) {
    186         aes256_gcm_cleanup(&ctx);
    187         free(output);
    188         return NULL;
    189     }
    190 
    191     aes256_gcm_cleanup(&ctx);
    192     *output_len = total_len;
    193     return output;
    194 }
    195 
    196 /**
    197  * Decrypt data with AES-256-GCM
    198  * Expects format: [Nonce(12)][Tag(16)][Ciphertext(var)]
    199  *
    200  * @param key 256-bit (32 byte) encryption key
    201  * @param encrypted Encrypted data [Nonce][Tag][Ciphertext]
    202  * @param encrypted_len Length of encrypted data
    203  * @param output_len Output parameter for plaintext length
    204  * @return Decrypted data (caller must free) or NULL on failure
    205  */
    206 static inline void* easycrypto_decrypt_aes(
    207     const uint8_t *key, const void *encrypted, size_t encrypted_len, size_t *output_len) {
    208 
    209     if (!key || !encrypted || !output_len || encrypted_len < 28) return NULL;
    210 
    211     const uint8_t *data = (const uint8_t*)encrypted;
    212 
    213     /* Parse input: [nonce(12)][tag(16)][ciphertext] */
    214     const uint8_t *nonce = data;
    215     const uint8_t *tag = data + 12;
    216     const uint8_t *ciphertext = data + 28;
    217     size_t ciphertext_len = encrypted_len - 28;
    218 
    219     /* Allocate output */
    220     uint8_t *plaintext = malloc(ciphertext_len);
    221     if (!plaintext) return NULL;
    222 
    223     /* Decrypt */
    224     aes256_gcm_context ctx;
    225     if (aes256_gcm_init(&ctx, key) != 0) {
    226         free(plaintext);
    227         return NULL;
    228     }
    229 
    230     if (aes256_gcm_decrypt(&ctx, nonce, 12, NULL, 0,
    231                           ciphertext, ciphertext_len,
    232                           tag, 16, plaintext) != 0) {
    233         aes256_gcm_cleanup(&ctx);
    234         memset(plaintext, 0, ciphertext_len);
    235         free(plaintext);
    236         return NULL;
    237     }
    238 
    239     aes256_gcm_cleanup(&ctx);
    240     *output_len = ciphertext_len;
    241     return plaintext;
    242 }
    243 
    244 /**
    245  * Encrypt data with ChaCha20-Poly1305
    246  * Returns serialized format: [Nonce(12)][Tag(16)][Ciphertext(var)]
    247  *
    248  * @param key 256-bit (32 byte) encryption key
    249  * @param plaintext Plaintext data
    250  * @param plaintext_len Plaintext length
    251  * @param output_len Output parameter for encrypted data length
    252  * @return Encrypted data [Nonce][Tag][Ciphertext] (caller must free) or NULL on failure
    253  */
    254 static inline void* easycrypto_encrypt_chacha(
    255     const uint8_t *key, const uint8_t *plaintext, size_t plaintext_len, size_t *output_len) {
    256 
    257     if (!key || !plaintext || !output_len) return NULL;
    258 
    259     /* Calculate output size: 12 (nonce) + 16 (tag) + plaintext_len */
    260     size_t total_len = 12 + 16 + plaintext_len;
    261     uint8_t *output = malloc(total_len);
    262     if (!output) return NULL;
    263 
    264     /* Generate random nonce */
    265     random_bytes(output, 12);
    266 
    267     /* Encrypt */
    268     chacha20_poly1305_context ctx;
    269     if (chacha20_poly1305_init(&ctx, key, output) != 0) {
    270         free(output);
    271         return NULL;
    272     }
    273 
    274     /* output layout: [nonce(12)][tag(16)][ciphertext] */
    275     uint8_t *tag = output + 12;
    276     uint8_t *ciphertext = output + 28;
    277 
    278     if (chacha20_poly1305_encrypt(&ctx, NULL, 0, plaintext, plaintext_len,
    279                                   ciphertext, tag) != 0) {
    280         chacha20_poly1305_cleanup(&ctx);
    281         free(output);
    282         return NULL;
    283     }
    284 
    285     chacha20_poly1305_cleanup(&ctx);
    286     *output_len = total_len;
    287     return output;
    288 }
    289 
    290 /**
    291  * Decrypt data with ChaCha20-Poly1305
    292  * Expects format: [Nonce(12)][Tag(16)][Ciphertext(var)]
    293  *
    294  * @param key 256-bit (32 byte) encryption key
    295  * @param encrypted Encrypted data [Nonce][Tag][Ciphertext]
    296  * @param encrypted_len Length of encrypted data
    297  * @param output_len Output parameter for plaintext length
    298  * @return Decrypted data (caller must free) or NULL on failure
    299  */
    300 static inline void* easycrypto_decrypt_chacha(
    301     const uint8_t *key, const void *encrypted, size_t encrypted_len, size_t *output_len) {
    302 
    303     if (!key || !encrypted || !output_len || encrypted_len < 28) return NULL;
    304 
    305     const uint8_t *data = (const uint8_t*)encrypted;
    306 
    307     /* Parse input: [nonce(12)][tag(16)][ciphertext] */
    308     const uint8_t *nonce = data;
    309     const uint8_t *tag = data + 12;
    310     const uint8_t *ciphertext = data + 28;
    311     size_t ciphertext_len = encrypted_len - 28;
    312 
    313     /* Allocate output */
    314     uint8_t *plaintext = malloc(ciphertext_len);
    315     if (!plaintext) return NULL;
    316 
    317     /* Decrypt */
    318     chacha20_poly1305_context ctx;
    319     if (chacha20_poly1305_init(&ctx, key, nonce) != 0) {
    320         free(plaintext);
    321         return NULL;
    322     }
    323 
    324     if (chacha20_poly1305_decrypt(&ctx, NULL, 0,
    325                                   ciphertext, ciphertext_len,
    326                                   tag, plaintext) != 0) {
    327         chacha20_poly1305_cleanup(&ctx);
    328         memset(plaintext, 0, ciphertext_len);
    329         free(plaintext);
    330         return NULL;
    331     }
    332 
    333     chacha20_poly1305_cleanup(&ctx);
    334     *output_len = ciphertext_len;
    335     return plaintext;
    336 }
    337 
    338 /**
    339  * Encrypt data with XChaCha20-Poly1305
    340  * Returns serialized format: [Nonce(24)][Tag(16)][Ciphertext(var)]
    341  *
    342  * @param key 256-bit (32 byte) encryption key
    343  * @param plaintext Plaintext data
    344  * @param plaintext_len Plaintext length
    345  * @param output_len Output parameter for encrypted data length
    346  * @return Encrypted data [Nonce][Tag][Ciphertext] (caller must free) or NULL on failure
    347  *
    348  * Note: XChaCha20 uses 192-bit (24 byte) nonces - can be randomly generated safely
    349  */
    350 static inline void* easycrypto_encrypt_xchacha(
    351     const uint8_t *key, const uint8_t *plaintext, size_t plaintext_len, size_t *output_len) {
    352 
    353     if (!key || !plaintext || !output_len) return NULL;
    354 
    355     /* Calculate output size: 24 (nonce) + 16 (tag) + plaintext_len */
    356     size_t total_len = 24 + 16 + plaintext_len;
    357     uint8_t *output = malloc(total_len);
    358     if (!output) return NULL;
    359 
    360     /* Generate random nonce (safe with 192 bits) */
    361     random_bytes(output, 24);
    362 
    363     /* Encrypt */
    364     xchacha20_poly1305_context ctx;
    365     if (xchacha20_poly1305_init(&ctx, key, output) != 0) {
    366         free(output);
    367         return NULL;
    368     }
    369 
    370     /* output layout: [nonce(24)][tag(16)][ciphertext] */
    371     uint8_t *tag = output + 24;
    372     uint8_t *ciphertext = output + 40;
    373 
    374     if (xchacha20_poly1305_encrypt(&ctx, NULL, 0, plaintext, plaintext_len,
    375                                    ciphertext, tag) != 0) {
    376         xchacha20_poly1305_cleanup(&ctx);
    377         free(output);
    378         return NULL;
    379     }
    380 
    381     xchacha20_poly1305_cleanup(&ctx);
    382     *output_len = total_len;
    383     return output;
    384 }
    385 
    386 /**
    387  * Decrypt data with XChaCha20-Poly1305
    388  * Expects format: [Nonce(24)][Tag(16)][Ciphertext(var)]
    389  *
    390  * @param key 256-bit (32 byte) encryption key
    391  * @param encrypted Encrypted data [Nonce][Tag][Ciphertext]
    392  * @param encrypted_len Length of encrypted data
    393  * @param output_len Output parameter for plaintext length
    394  * @return Decrypted data (caller must free) or NULL on failure
    395  */
    396 static inline void* easycrypto_decrypt_xchacha(
    397     const uint8_t *key, const void *encrypted, size_t encrypted_len, size_t *output_len) {
    398 
    399     if (!key || !encrypted || !output_len || encrypted_len < 40) return NULL;
    400 
    401     const uint8_t *data = (const uint8_t*)encrypted;
    402 
    403     /* Parse input: [nonce(24)][tag(16)][ciphertext] */
    404     const uint8_t *nonce = data;
    405     const uint8_t *tag = data + 24;
    406     const uint8_t *ciphertext = data + 40;
    407     size_t ciphertext_len = encrypted_len - 40;
    408 
    409     /* Allocate output */
    410     uint8_t *plaintext = malloc(ciphertext_len);
    411     if (!plaintext) return NULL;
    412 
    413     /* Decrypt */
    414     xchacha20_poly1305_context ctx;
    415     if (xchacha20_poly1305_init(&ctx, key, nonce) != 0) {
    416         free(plaintext);
    417         return NULL;
    418     }
    419 
    420     if (xchacha20_poly1305_decrypt(&ctx, NULL, 0,
    421                                    ciphertext, ciphertext_len,
    422                                    tag, plaintext) != 0) {
    423         xchacha20_poly1305_cleanup(&ctx);
    424         memset(plaintext, 0, ciphertext_len);
    425         free(plaintext);
    426         return NULL;
    427     }
    428 
    429     xchacha20_poly1305_cleanup(&ctx);
    430     *output_len = ciphertext_len;
    431     return plaintext;
    432 }
    433 
    434 /**
    435  * Encrypt data with Twofish-256-GCM
    436  * Returns serialized format: [Nonce(12)][Tag(16)][Ciphertext(var)]
    437  *
    438  * @param key 256-bit (32 byte) encryption key
    439  * @param plaintext Plaintext data
    440  * @param plaintext_len Plaintext length
    441  * @param output_len Output parameter for encrypted data length
    442  * @return Encrypted data [Nonce][Tag][Ciphertext] (caller must free) or NULL on failure
    443  */
    444 static inline void* easycrypto_encrypt_twofish(
    445     const uint8_t *key, const uint8_t *plaintext, size_t plaintext_len, size_t *output_len) {
    446 
    447     if (!key || !plaintext || !output_len) return NULL;
    448 
    449     /* Calculate output size: 12 (nonce) + 16 (tag) + plaintext_len */
    450     size_t total_len = 12 + 16 + plaintext_len;
    451     uint8_t *output = malloc(total_len);
    452     if (!output) return NULL;
    453 
    454     /* Generate random nonce */
    455     random_bytes(output, 12);
    456 
    457     /* Encrypt */
    458     twofish256_gcm_context ctx;
    459     if (twofish256_gcm_init(&ctx, key) != 0) {
    460         free(output);
    461         return NULL;
    462     }
    463 
    464     /* output layout: [nonce(12)][tag(16)][ciphertext] */
    465     uint8_t *tag = output + 12;
    466     uint8_t *ciphertext = output + 28;
    467 
    468     if (twofish256_gcm_encrypt(&ctx, output, 12, NULL, 0,
    469                               plaintext, plaintext_len,
    470                               ciphertext, tag, 16) != 0) {
    471         twofish256_gcm_cleanup(&ctx);
    472         free(output);
    473         return NULL;
    474     }
    475 
    476     twofish256_gcm_cleanup(&ctx);
    477     *output_len = total_len;
    478     return output;
    479 }
    480 
    481 /**
    482  * Decrypt data with Twofish-256-GCM
    483  * Expects format: [Nonce(12)][Tag(16)][Ciphertext(var)]
    484  *
    485  * @param key 256-bit (32 byte) encryption key
    486  * @param encrypted Encrypted data [Nonce][Tag][Ciphertext]
    487  * @param encrypted_len Length of encrypted data
    488  * @param output_len Output parameter for plaintext length
    489  * @return Decrypted data (caller must free) or NULL on failure
    490  */
    491 static inline void* easycrypto_decrypt_twofish(
    492     const uint8_t *key, const void *encrypted, size_t encrypted_len, size_t *output_len) {
    493 
    494     if (!key || !encrypted || !output_len || encrypted_len < 28) return NULL;
    495 
    496     const uint8_t *data = (const uint8_t*)encrypted;
    497 
    498     /* Parse input: [nonce(12)][tag(16)][ciphertext] */
    499     const uint8_t *nonce = data;
    500     const uint8_t *tag = data + 12;
    501     const uint8_t *ciphertext = data + 28;
    502     size_t ciphertext_len = encrypted_len - 28;
    503 
    504     /* Allocate output */
    505     uint8_t *plaintext = malloc(ciphertext_len);
    506     if (!plaintext) return NULL;
    507 
    508     /* Decrypt */
    509     twofish256_gcm_context ctx;
    510     if (twofish256_gcm_init(&ctx, key) != 0) {
    511         free(plaintext);
    512         return NULL;
    513     }
    514 
    515     if (twofish256_gcm_decrypt(&ctx, nonce, 12, NULL, 0,
    516                               ciphertext, ciphertext_len,
    517                               tag, 16, plaintext) != 0) {
    518         twofish256_gcm_cleanup(&ctx);
    519         memset(plaintext, 0, ciphertext_len);
    520         free(plaintext);
    521         return NULL;
    522     }
    523 
    524     twofish256_gcm_cleanup(&ctx);
    525     *output_len = ciphertext_len;
    526     return plaintext;
    527 }
    528 
    529 /**
    530  * Encrypt data with Salsa20-Poly1305
    531  * Returns serialized format: [Nonce(8)][Tag(16)][Ciphertext(var)]
    532  * Note: Salsa20 uses 8-byte nonce (different from ChaCha20's 12 bytes)
    533  *
    534  * @param key 256-bit (32 byte) encryption key
    535  * @param plaintext Plaintext data
    536  * @param plaintext_len Plaintext length
    537  * @param output_len Output parameter for encrypted data length
    538  * @return Encrypted data [Nonce][Tag][Ciphertext] (caller must free) or NULL on failure
    539  */
    540 static inline void* easycrypto_encrypt_salsa(
    541     const uint8_t *key, const uint8_t *plaintext, size_t plaintext_len, size_t *output_len) {
    542 
    543     if (!key || !plaintext || !output_len) return NULL;
    544 
    545     /* Calculate output size: 8 (nonce) + 16 (tag) + plaintext_len */
    546     size_t total_len = 8 + 16 + plaintext_len;
    547     uint8_t *output = malloc(total_len);
    548     if (!output) return NULL;
    549 
    550     /* Generate random nonce (8 bytes for Salsa20) */
    551     random_bytes(output, 8);
    552 
    553     /* Encrypt */
    554     salsa20_poly1305_context ctx;
    555     if (salsa20_poly1305_init(&ctx, key, output) != 0) {
    556         free(output);
    557         return NULL;
    558     }
    559 
    560     /* output layout: [nonce(8)][tag(16)][ciphertext] */
    561     uint8_t *tag = output + 8;
    562     uint8_t *ciphertext = output + 24;
    563 
    564     if (salsa20_poly1305_encrypt(&ctx, NULL, 0, plaintext, plaintext_len,
    565                                  ciphertext, tag) != 0) {
    566         salsa20_poly1305_cleanup(&ctx);
    567         free(output);
    568         return NULL;
    569     }
    570 
    571     salsa20_poly1305_cleanup(&ctx);
    572     *output_len = total_len;
    573     return output;
    574 }
    575 
    576 /**
    577  * Decrypt data with Salsa20-Poly1305
    578  * Expects format: [Nonce(8)][Tag(16)][Ciphertext(var)]
    579  *
    580  * @param key 256-bit (32 byte) encryption key
    581  * @param encrypted Encrypted data [Nonce][Tag][Ciphertext]
    582  * @param encrypted_len Length of encrypted data
    583  * @param output_len Output parameter for plaintext length
    584  * @return Decrypted data (caller must free) or NULL on failure
    585  */
    586 static inline void* easycrypto_decrypt_salsa(
    587     const uint8_t *key, const void *encrypted, size_t encrypted_len, size_t *output_len) {
    588 
    589     if (!key || !encrypted || !output_len || encrypted_len < 24) return NULL;
    590 
    591     const uint8_t *data = (const uint8_t*)encrypted;
    592 
    593     /* Parse input: [nonce(8)][tag(16)][ciphertext] */
    594     const uint8_t *nonce = data;
    595     const uint8_t *tag = data + 8;
    596     const uint8_t *ciphertext = data + 24;
    597     size_t ciphertext_len = encrypted_len - 24;
    598 
    599     /* Allocate output */
    600     uint8_t *plaintext = malloc(ciphertext_len);
    601     if (!plaintext) return NULL;
    602 
    603     /* Decrypt */
    604     salsa20_poly1305_context ctx;
    605     if (salsa20_poly1305_init(&ctx, key, nonce) != 0) {
    606         free(plaintext);
    607         return NULL;
    608     }
    609 
    610     if (salsa20_poly1305_decrypt(&ctx, NULL, 0,
    611                                  ciphertext, ciphertext_len,
    612                                  tag, plaintext) != 0) {
    613         salsa20_poly1305_cleanup(&ctx);
    614         memset(plaintext, 0, ciphertext_len);
    615         free(plaintext);
    616         return NULL;
    617     }
    618 
    619     salsa20_poly1305_cleanup(&ctx);
    620     *output_len = ciphertext_len;
    621     return plaintext;
    622 }
    623 
    624 /**
    625  * Encrypt data with Serpent-256-GCM
    626  * Returns serialized format: [Nonce(12)][Tag(16)][Ciphertext(var)]
    627  *
    628  * @param key 256-bit (32 byte) encryption key
    629  * @param plaintext Plaintext data
    630  * @param plaintext_len Plaintext length
    631  * @param output_len Output parameter for encrypted data length
    632  * @return Encrypted data [Nonce][Tag][Ciphertext] (caller must free) or NULL on failure
    633  */
    634 static inline void* easycrypto_encrypt_serpent(
    635     const uint8_t *key, const uint8_t *plaintext, size_t plaintext_len, size_t *output_len) {
    636 
    637     if (!key || !plaintext || !output_len) return NULL;
    638 
    639     /* Calculate output size: 12 (nonce) + 16 (tag) + plaintext_len */
    640     size_t total_len = 12 + 16 + plaintext_len;
    641     uint8_t *output = malloc(total_len);
    642     if (!output) return NULL;
    643 
    644     /* Generate random nonce */
    645     random_bytes(output, 12);
    646 
    647     /* Encrypt */
    648     serpent_gcm_context ctx;
    649     if (serpent_gcm_init(&ctx, key) != 0) {
    650         free(output);
    651         return NULL;
    652     }
    653 
    654     /* output layout: [nonce(12)][tag(16)][ciphertext] */
    655     uint8_t *tag = output + 12;
    656     uint8_t *ciphertext = output + 28;
    657 
    658     if (serpent_gcm_encrypt(&ctx, output, 12, NULL, 0,
    659                            plaintext, plaintext_len,
    660                            ciphertext, tag, 16) != 0) {
    661         serpent_gcm_cleanup(&ctx);
    662         free(output);
    663         return NULL;
    664     }
    665 
    666     serpent_gcm_cleanup(&ctx);
    667     *output_len = total_len;
    668     return output;
    669 }
    670 
    671 /**
    672  * Decrypt data with Serpent-256-GCM
    673  * Expects format: [Nonce(12)][Tag(16)][Ciphertext(var)]
    674  *
    675  * @param key 256-bit (32 byte) encryption key
    676  * @param encrypted Encrypted data [Nonce][Tag][Ciphertext]
    677  * @param encrypted_len Length of encrypted data
    678  * @param output_len Output parameter for plaintext length
    679  * @return Decrypted data (caller must free) or NULL on failure
    680  */
    681 static inline void* easycrypto_decrypt_serpent(
    682     const uint8_t *key, const void *encrypted, size_t encrypted_len, size_t *output_len) {
    683 
    684     if (!key || !encrypted || !output_len || encrypted_len < 28) return NULL;
    685 
    686     const uint8_t *data = (const uint8_t*)encrypted;
    687 
    688     /* Parse input: [nonce(12)][tag(16)][ciphertext] */
    689     const uint8_t *nonce = data;
    690     const uint8_t *tag = data + 12;
    691     const uint8_t *ciphertext = data + 28;
    692     size_t ciphertext_len = encrypted_len - 28;
    693 
    694     /* Allocate output */
    695     uint8_t *plaintext = malloc(ciphertext_len);
    696     if (!plaintext) return NULL;
    697 
    698     /* Decrypt */
    699     serpent_gcm_context ctx;
    700     if (serpent_gcm_init(&ctx, key) != 0) {
    701         free(plaintext);
    702         return NULL;
    703     }
    704 
    705     if (serpent_gcm_decrypt(&ctx, nonce, 12, NULL, 0,
    706                            ciphertext, ciphertext_len,
    707                            tag, 16, plaintext) != 0) {
    708         serpent_gcm_cleanup(&ctx);
    709         memset(plaintext, 0, ciphertext_len);
    710         free(plaintext);
    711         return NULL;
    712     }
    713 
    714     serpent_gcm_cleanup(&ctx);
    715     *output_len = ciphertext_len;
    716     return plaintext;
    717 }
    718 
    719 /**
    720  * Free encrypted data structure
    721  */
    722 static inline void encrypted_data_free(encrypted_data *data) {
    723     if (!data) return;
    724     if (data->nonce) free(data->nonce);
    725     if (data->ciphertext) {
    726         memset(data->ciphertext, 0, data->ciphertext_len);
    727         free(data->ciphertext);
    728     }
    729     if (data->tag) free(data->tag);
    730     free(data);
    731 }
    732 
    733 /**
    734  * Serialize encrypted data to a flat byte array for storage
    735  * Format: [Nonce (12 bytes)][Tag (16 bytes)][Ciphertext (variable)]
    736  *
    737  * Note: Algorithm selection must be tracked externally (not stored in data)
    738  *
    739  * @param data Encrypted data structure
    740  * @param output_len Output parameter for serialized data length
    741  * @return Serialized data (caller must free) or NULL on error
    742  */
    743 static inline uint8_t* encrypted_data_serialize(const encrypted_data *data, size_t *output_len) {
    744     if (!data || !output_len) return NULL;
    745 
    746     /* Calculate total size: nonce + tag + ciphertext */
    747     size_t total_len = data->nonce_len + data->tag_len + data->ciphertext_len;
    748 
    749     uint8_t *buffer = malloc(total_len);
    750     if (!buffer) return NULL;
    751 
    752     size_t offset = 0;
    753 
    754     /* Write nonce */
    755     memcpy(buffer + offset, data->nonce, data->nonce_len);
    756     offset += data->nonce_len;
    757 
    758     /* Write tag */
    759     memcpy(buffer + offset, data->tag, data->tag_len);
    760     offset += data->tag_len;
    761 
    762     /* Write ciphertext */
    763     memcpy(buffer + offset, data->ciphertext, data->ciphertext_len);
    764 
    765     *output_len = total_len;
    766     return buffer;
    767 }
    768 
    769 /**
    770  * Deserialize encrypted data from a flat byte array
    771  * Format: [Nonce (12 bytes)][Tag (16 bytes)][Ciphertext (variable)]
    772  *
    773  * @param buffer Serialized data (minimum 28 bytes: 12 nonce + 16 tag + 0 ciphertext)
    774  * @param buffer_len Length of serialized data
    775  * @param algorithm Algorithm used (must be provided externally)
    776  * @return Encrypted data structure (caller must free with encrypted_data_free) or NULL on error
    777  */
    778 static inline encrypted_data* encrypted_data_deserialize(const uint8_t *buffer, size_t buffer_len, int algorithm) {
    779     /* Fixed sizes for GCM mode */
    780     const size_t nonce_len = 12;
    781     const size_t tag_len = 16;
    782 
    783     /* Minimum size check */
    784     if (!buffer || buffer_len < (nonce_len + tag_len)) {
    785         return NULL;
    786     }
    787 
    788     encrypted_data *data = malloc(sizeof(encrypted_data));
    789     if (!data) return NULL;
    790 
    791     memset(data, 0, sizeof(encrypted_data));
    792 
    793     /* Set algorithm */
    794     data->algorithm = algorithm;
    795 
    796     /* Calculate ciphertext length */
    797     data->ciphertext_len = buffer_len - nonce_len - tag_len;
    798 
    799     size_t offset = 0;
    800 
    801     /* Read nonce */
    802     data->nonce_len = nonce_len;
    803     data->nonce = malloc(nonce_len);
    804     if (!data->nonce) {
    805         free(data);
    806         return NULL;
    807     }
    808     memcpy(data->nonce, buffer + offset, nonce_len);
    809     offset += nonce_len;
    810 
    811     /* Read tag */
    812     data->tag_len = tag_len;
    813     data->tag = malloc(tag_len);
    814     if (!data->tag) {
    815         free(data->nonce);
    816         free(data);
    817         return NULL;
    818     }
    819     memcpy(data->tag, buffer + offset, tag_len);
    820     offset += tag_len;
    821 
    822     /* Read ciphertext */
    823     data->ciphertext = malloc(data->ciphertext_len);
    824     if (!data->ciphertext) {
    825         free(data->nonce);
    826         free(data->tag);
    827         free(data);
    828         return NULL;
    829     }
    830     memcpy(data->ciphertext, buffer + offset, data->ciphertext_len);
    831 
    832     return data;
    833 }
    834 
    835 /* Convenience macros */
    836 #define ENCRYPT(key, plaintext, plaintext_len, output_len) \
    837     easycrypto_encrypt_aes(key, plaintext, plaintext_len, output_len)
    838 #define DECRYPT(key, encrypted, encrypted_len, output_len) \
    839     easycrypto_decrypt_aes(key, encrypted, encrypted_len, output_len)
    840 
    841 #define ENCRYPT_AES(key, plaintext, plaintext_len, output_len) \
    842     easycrypto_encrypt_aes(key, plaintext, plaintext_len, output_len)
    843 #define DECRYPT_AES(key, encrypted, encrypted_len, output_len) \
    844     easycrypto_decrypt_aes(key, encrypted, encrypted_len, output_len)
    845 
    846 #define ENCRYPT_AES128(key, plaintext, plaintext_len, output_len) \
    847     easycrypto_encrypt_aes128(key, plaintext, plaintext_len, output_len)
    848 #define DECRYPT_AES128(key, encrypted, encrypted_len, output_len) \
    849     easycrypto_decrypt_aes128(key, encrypted, encrypted_len, output_len)
    850 
    851 #define ENCRYPT_CHACHA(key, plaintext, plaintext_len, output_len) \
    852     easycrypto_encrypt_chacha(key, plaintext, plaintext_len, output_len)
    853 #define DECRYPT_CHACHA(key, encrypted, encrypted_len, output_len) \
    854     easycrypto_decrypt_chacha(key, encrypted, encrypted_len, output_len)
    855 
    856 #define ENCRYPT_XCHACHA(key, plaintext, plaintext_len, output_len) \
    857     easycrypto_encrypt_xchacha(key, plaintext, plaintext_len, output_len)
    858 #define DECRYPT_XCHACHA(key, encrypted, encrypted_len, output_len) \
    859     easycrypto_decrypt_xchacha(key, encrypted, encrypted_len, output_len)
    860 
    861 #define ENCRYPT_SERPENT(key, plaintext, plaintext_len, output_len) \
    862     easycrypto_encrypt_serpent(key, plaintext, plaintext_len, output_len)
    863 #define DECRYPT_SERPENT(key, encrypted, encrypted_len, output_len) \
    864     easycrypto_decrypt_serpent(key, encrypted, encrypted_len, output_len)
    865 
    866 #define ENCRYPT_TWOFISH(key, plaintext, plaintext_len, output_len) \
    867     easycrypto_encrypt_twofish(key, plaintext, plaintext_len, output_len)
    868 #define DECRYPT_TWOFISH(key, encrypted, encrypted_len, output_len) \
    869     easycrypto_decrypt_twofish(key, encrypted, encrypted_len, output_len)
    870 
    871 /* Salsa20-Poly1305 AEAD macros */
    872 #define ENCRYPT_SALSA(key, plaintext, plaintext_len, output_len) \
    873     easycrypto_encrypt_salsa(key, plaintext, plaintext_len, output_len)
    874 #define DECRYPT_SALSA(key, encrypted, encrypted_len, output_len) \
    875     easycrypto_decrypt_salsa(key, encrypted, encrypted_len, output_len)
    876 
    877 #ifdef __cplusplus
    878 }
    879 #endif
    880 
    881 #endif /* EASYCRYPTO_H */