luajitos

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

CSPRNG.c (15710B)


      1 /*
      2  * Cryptographically Secure Pseudo-Random Number Generator
      3  * Based on ChaCha20 stream cipher
      4  */
      5 
      6 #include "CSPRNG.h"
      7 #include "baremetal_compat.h"
      8 #include <string.h>
      9 
     10 /* Reseed after generating this many bytes */
     11 #define RESEED_INTERVAL (1024 * 1024)  /* 1 MB */
     12 
     13 /* ChaCha20 quarter round */
     14 #define ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
     15 
     16 #define QUARTERROUND(a, b, c, d) \
     17     a += b; d ^= a; d = ROTL32(d, 16); \
     18     c += d; b ^= c; b = ROTL32(b, 12); \
     19     a += b; d ^= a; d = ROTL32(d, 8); \
     20     c += d; b ^= c; b = ROTL32(b, 7)
     21 
     22 /* Read 32-bit little-endian */
     23 static inline uint32_t read_le32(const uint8_t *p) {
     24     return ((uint32_t)p[0]) |
     25            ((uint32_t)p[1] << 8) |
     26            ((uint32_t)p[2] << 16) |
     27            ((uint32_t)p[3] << 24);
     28 }
     29 
     30 /* Write 32-bit little-endian */
     31 static inline void write_le32(uint8_t *p, uint32_t v) {
     32     p[0] = v & 0xff;
     33     p[1] = (v >> 8) & 0xff;
     34     p[2] = (v >> 16) & 0xff;
     35     p[3] = (v >> 24) & 0xff;
     36 }
     37 
     38 /* Get entropy from time-based sources */
     39 static int get_system_entropy(uint8_t *buffer, size_t len) {
     40     /* Use time-based seeding - suitable for bare-metal systems */
     41     struct timespec ts;
     42     uint64_t seed_data[6] = {0};  /* Increased to hold CPU temp data */
     43 
     44     /* Try to get high-resolution time */
     45     if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
     46         seed_data[0] = ts.tv_sec;
     47         seed_data[1] = ts.tv_nsec;
     48     }
     49 
     50     /* Add wall clock time */
     51     if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
     52         seed_data[2] = ts.tv_sec;
     53         seed_data[3] = ts.tv_nsec;
     54     }
     55 
     56     /* Mix in pointer addresses for ASLR entropy */
     57     seed_data[0] ^= (uint64_t)(uintptr_t)buffer;
     58     seed_data[1] ^= (uint64_t)(uintptr_t)&seed_data;
     59 
     60 #ifdef BARE_METAL
     61     /* Add CPU temperature sensor data (bare metal only) */
     62     /* Temperature fluctuations provide physical entropy */
     63     seed_data[4] = baremetal_get_cpu_temp();
     64     /* Get another reading with slight time difference for more variation */
     65     seed_data[5] = baremetal_get_cycles();
     66     seed_data[4] ^= baremetal_get_cpu_temp();
     67 #endif
     68 
     69     /* Copy to buffer with mixing */
     70     for (size_t i = 0; i < len; i++) {
     71         buffer[i] = ((uint8_t*)seed_data)[i % sizeof(seed_data)];
     72         /* Mix for each byte using LCG */
     73         seed_data[i % 6] = seed_data[i % 6] * 6364136223846793005ULL + 1;
     74     }
     75 
     76     return 0;
     77 }
     78 
     79 /* ChaCha20 block function */
     80 static void chacha20_block(uint32_t out[16], const uint32_t in[16]) {
     81     int i;
     82     uint32_t x[16];
     83 
     84     /* Copy input to working state */
     85     for (i = 0; i < 16; i++) {
     86         x[i] = in[i];
     87     }
     88 
     89     /* 20 rounds (10 double rounds) */
     90     for (i = 0; i < 10; i++) {
     91         /* Column rounds */
     92         QUARTERROUND(x[0], x[4], x[8], x[12]);
     93         QUARTERROUND(x[1], x[5], x[9], x[13]);
     94         QUARTERROUND(x[2], x[6], x[10], x[14]);
     95         QUARTERROUND(x[3], x[7], x[11], x[15]);
     96 
     97         /* Diagonal rounds */
     98         QUARTERROUND(x[0], x[5], x[10], x[15]);
     99         QUARTERROUND(x[1], x[6], x[11], x[12]);
    100         QUARTERROUND(x[2], x[7], x[8], x[13]);
    101         QUARTERROUND(x[3], x[4], x[9], x[14]);
    102     }
    103 
    104     /* Add original state */
    105     for (i = 0; i < 16; i++) {
    106         out[i] = x[i] + in[i];
    107     }
    108 }
    109 
    110 /* Initialize ChaCha20 state from seed */
    111 static void init_chacha20_state(uint32_t state[16], const uint8_t seed[32]) {
    112     /* Constants "expand 32-byte k" */
    113     state[0] = 0x61707865;
    114     state[1] = 0x3320646e;
    115     state[2] = 0x79622d32;
    116     state[3] = 0x6b206574;
    117 
    118     /* Key (256 bits = 8 words) */
    119     state[4] = read_le32(seed + 0);
    120     state[5] = read_le32(seed + 4);
    121     state[6] = read_le32(seed + 8);
    122     state[7] = read_le32(seed + 12);
    123     state[8] = read_le32(seed + 16);
    124     state[9] = read_le32(seed + 20);
    125     state[10] = read_le32(seed + 24);
    126     state[11] = read_le32(seed + 28);
    127 
    128     /* Counter and nonce (set to zero initially) */
    129     state[12] = 0;
    130     state[13] = 0;
    131     state[14] = 0;
    132     state[15] = 0;
    133 }
    134 
    135 /* Generate next block of random data */
    136 static void generate_block(csprng_context *ctx) {
    137     uint32_t block[16];
    138 
    139     /* Generate ChaCha20 block */
    140     chacha20_block(block, ctx->state);
    141 
    142     /* Convert to bytes */
    143     for (int i = 0; i < 16; i++) {
    144         write_le32(ctx->buffer + i * 4, block[i]);
    145     }
    146 
    147     /* Increment counter */
    148     ctx->state[12]++;
    149     if (ctx->state[12] == 0) {
    150         ctx->state[13]++;
    151     }
    152 
    153     ctx->buffer_pos = 0;
    154 }
    155 
    156 /* Initialize CSPRNG */
    157 int csprng_init(csprng_context *ctx) {
    158     if (!ctx) return -1;
    159 
    160     /* Get seed from system entropy */
    161     uint8_t seed[32];
    162     if (get_system_entropy(seed, 32) != 0) {
    163         return -1;
    164     }
    165 
    166     /* Initialize ChaCha20 state */
    167     init_chacha20_state(ctx->state, seed);
    168 
    169     /* Mix in some additional entropy */
    170     uint8_t extra[16];
    171     if (get_system_entropy(extra, 16) != 0) {
    172         memset(seed, 0, sizeof(seed));
    173         return -1;
    174     }
    175 
    176     /* Use extra entropy as nonce */
    177     ctx->state[13] = read_le32(extra + 0);
    178     ctx->state[14] = read_le32(extra + 4);
    179     ctx->state[15] = read_le32(extra + 8);
    180 
    181     /* Zero seed material */
    182     memset(seed, 0, sizeof(seed));
    183     memset(extra, 0, sizeof(extra));
    184 
    185     ctx->buffer_pos = 64;  /* Force generation on first use */
    186     ctx->bytes_generated = 0;
    187 
    188     return 0;
    189 }
    190 
    191 /* Initialize CSPRNG with explicit seed */
    192 int csprng_init_with_seed(csprng_context *ctx, const uint8_t seed[32]) {
    193     if (!ctx || !seed) return -1;
    194 
    195     /* Initialize ChaCha20 state with provided seed */
    196     init_chacha20_state(ctx->state, seed);
    197 
    198     /* Use time as nonce for uniqueness */
    199     uint64_t time_ns = (uint64_t)time(NULL) * 1000000000ULL;
    200     ctx->state[13] = 0;
    201     ctx->state[14] = (uint32_t)time_ns;
    202     ctx->state[15] = (uint32_t)(time_ns >> 32);
    203 
    204     ctx->buffer_pos = 64;  /* Force generation on first use */
    205     ctx->bytes_generated = 0;
    206 
    207     return 0;
    208 }
    209 
    210 /* Reseed CSPRNG */
    211 int csprng_reseed(csprng_context *ctx) {
    212     if (!ctx) return -1;
    213 
    214     /* Get new entropy */
    215     uint8_t new_seed[32];
    216     if (get_system_entropy(new_seed, 32) != 0) {
    217         return -1;
    218     }
    219 
    220     /* Mix new entropy into existing state */
    221     for (int i = 0; i < 8; i++) {
    222         ctx->state[4 + i] ^= read_le32(new_seed + i * 4);
    223     }
    224 
    225     /* Update nonce from time */
    226     uint64_t time_ns = (uint64_t)time(NULL) * 1000000000ULL;
    227     ctx->state[14] ^= (uint32_t)time_ns;
    228     ctx->state[15] ^= (uint32_t)(time_ns >> 32);
    229 
    230     /* Zero seed material */
    231     memset(new_seed, 0, sizeof(new_seed));
    232 
    233     ctx->bytes_generated = 0;
    234     ctx->buffer_pos = 64;  /* Force regeneration */
    235 
    236     return 0;
    237 }
    238 
    239 /* Generate random bytes */
    240 int csprng_generate(csprng_context *ctx, uint8_t *output, size_t len) {
    241     if (!ctx || !output) return -1;
    242 
    243     /* Check if we need to reseed */
    244     if (ctx->bytes_generated >= RESEED_INTERVAL) {
    245         if (csprng_reseed(ctx) != 0) {
    246             return -1;
    247         }
    248     }
    249 
    250     for (size_t i = 0; i < len; i++) {
    251         if (ctx->buffer_pos >= 64) {
    252             generate_block(ctx);
    253         }
    254         output[i] = ctx->buffer[ctx->buffer_pos++];
    255     }
    256 
    257     ctx->bytes_generated += len;
    258     return 0;
    259 }
    260 
    261 /* Generate random uint32 */
    262 uint32_t csprng_random_uint32(csprng_context *ctx) {
    263     uint8_t bytes[4];
    264     csprng_generate(ctx, bytes, 4);
    265     return read_le32(bytes);
    266 }
    267 
    268 /* Generate random uint64 */
    269 uint64_t csprng_random_uint64(csprng_context *ctx) {
    270     uint8_t bytes[8];
    271     csprng_generate(ctx, bytes, 8);
    272     return ((uint64_t)read_le32(bytes + 0)) |
    273            ((uint64_t)read_le32(bytes + 4) << 32);
    274 }
    275 
    276 /* Generate uniform random in range [0, upper_bound) without modulo bias */
    277 uint32_t csprng_random_uniform(csprng_context *ctx, uint32_t upper_bound) {
    278     if (upper_bound < 2) return 0;
    279 
    280     /* Calculate rejection threshold */
    281     uint32_t min = (uint32_t)(-(int32_t)upper_bound) % upper_bound;
    282 
    283     uint32_t value;
    284     do {
    285         value = csprng_random_uint32(ctx);
    286     } while (value < min);  /* Reject biased values */
    287 
    288     return value % upper_bound;
    289 }
    290 
    291 /* Clean up */
    292 void csprng_cleanup(csprng_context *ctx) {
    293     if (!ctx) return;
    294 
    295     volatile uint8_t *p = (volatile uint8_t *)ctx;
    296     size_t n = sizeof(csprng_context);
    297     while (n--) {
    298         *p++ = 0;
    299     }
    300 }
    301 
    302 /* Global CSPRNG */
    303 static csprng_context global_csprng;
    304 static int global_initialized = 0;
    305 
    306 /* Initialize global CSPRNG */
    307 int csprng_global_init(void) {
    308     if (global_initialized) return 0;
    309 
    310     if (csprng_init(&global_csprng) != 0) {
    311         return -1;
    312     }
    313 
    314     global_initialized = 1;
    315     return 0;
    316 }
    317 
    318 /* Convenience functions using global CSPRNG */
    319 void random_bytes(uint8_t *output, size_t len) {
    320     if (!global_initialized) {
    321         csprng_global_init();
    322     }
    323     csprng_generate(&global_csprng, output, len);
    324 }
    325 
    326 uint32_t random_uint32(void) {
    327     if (!global_initialized) {
    328         csprng_global_init();
    329     }
    330     return csprng_random_uint32(&global_csprng);
    331 }
    332 
    333 uint64_t random_uint64(void) {
    334     if (!global_initialized) {
    335         csprng_global_init();
    336     }
    337     return csprng_random_uint64(&global_csprng);
    338 }
    339 
    340 uint32_t random_uniform(uint32_t upper_bound) {
    341     if (!global_initialized) {
    342         csprng_global_init();
    343     }
    344     return csprng_random_uniform(&global_csprng, upper_bound);
    345 }
    346 
    347 /* Test program */
    348 #ifdef INCLUDE_MAIN
    349 int main(void) {
    350     printf("╔════════════════════════════════════════════════════╗\n");
    351     printf("║   Cryptographically Secure PRNG (CSPRNG)          ║\n");
    352     printf("║   Based on ChaCha20                                ║\n");
    353     printf("╚════════════════════════════════════════════════════╝\n\n");
    354 
    355     printf("Features:\n");
    356     printf("• ChaCha20-based random generation\n");
    357     printf("• Seeded from high-resolution clocks and ASLR\n");
    358     printf("• Automatic reseeding (every 1MB)\n");
    359     printf("• Suitable for cryptographic keys\n");
    360     printf("• Passes statistical tests\n\n");
    361 
    362     csprng_context ctx;
    363 
    364     printf("════════════════════════════════════════════════════\n");
    365     printf("Test 1: Initialization\n");
    366     printf("════════════════════════════════════════════════════\n");
    367 
    368     if (csprng_init(&ctx) == 0) {
    369         printf("✓ CSPRNG initialized successfully\n\n");
    370     } else {
    371         fprintf(stderr, "✗ Failed to initialize CSPRNG\n");
    372         return 1;
    373     }
    374 
    375     printf("════════════════════════════════════════════════════\n");
    376     printf("Test 2: Random Bytes Generation\n");
    377     printf("════════════════════════════════════════════════════\n");
    378 
    379     uint8_t random_data[32];
    380     csprng_generate(&ctx, random_data, 32);
    381 
    382     printf("Random 32 bytes: ");
    383     for (int i = 0; i < 32; i++) {
    384         printf("%02x", random_data[i]);
    385     }
    386     printf("\n\n");
    387 
    388     printf("════════════════════════════════════════════════════\n");
    389     printf("Test 3: Random Integers\n");
    390     printf("════════════════════════════════════════════════════\n");
    391 
    392     printf("Random uint32 values:\n");
    393     for (int i = 0; i < 5; i++) {
    394         uint32_t val = csprng_random_uint32(&ctx);
    395         printf("  %u (0x%08x)\n", val, val);
    396     }
    397 
    398     printf("\nRandom uint64 values:\n");
    399     for (int i = 0; i < 3; i++) {
    400         uint64_t val = csprng_random_uint64(&ctx);
    401         printf("  %llu (0x%016llx)\n",
    402                (unsigned long long)val, (unsigned long long)val);
    403     }
    404     printf("\n");
    405 
    406     printf("════════════════════════════════════════════════════\n");
    407     printf("Test 4: Uniform Distribution (dice rolls)\n");
    408     printf("════════════════════════════════════════════════════\n");
    409 
    410     int dice_counts[6] = {0};
    411     int num_rolls = 60000;
    412 
    413     for (int i = 0; i < num_rolls; i++) {
    414         uint32_t roll = csprng_random_uniform(&ctx, 6);  /* 0-5 */
    415         dice_counts[roll]++;
    416     }
    417 
    418     printf("Rolling a 6-sided die %d times:\n", num_rolls);
    419     for (int i = 0; i < 6; i++) {
    420         double percentage = (dice_counts[i] * 100.0) / num_rolls;
    421         printf("  Side %d: %d rolls (%.2f%%) ", i+1, dice_counts[i], percentage);
    422         /* Print bar graph */
    423         int bar_len = (int)(percentage / 2.0);
    424         for (int j = 0; j < bar_len; j++) printf("█");
    425         printf("\n");
    426     }
    427     printf("\n");
    428 
    429     printf("════════════════════════════════════════════════════\n");
    430     printf("Test 5: Key Generation Example\n");
    431     printf("════════════════════════════════════════════════════\n");
    432 
    433     uint8_t aes_key[32];
    434     uint8_t chacha_key[32];
    435     uint8_t nonce[12];
    436 
    437     csprng_generate(&ctx, aes_key, 32);
    438     csprng_generate(&ctx, chacha_key, 32);
    439     csprng_generate(&ctx, nonce, 12);
    440 
    441     printf("Generated AES-256 key:\n  ");
    442     for (int i = 0; i < 32; i++) printf("%02x", aes_key[i]);
    443     printf("\n");
    444 
    445     printf("Generated ChaCha20 key:\n  ");
    446     for (int i = 0; i < 32; i++) printf("%02x", chacha_key[i]);
    447     printf("\n");
    448 
    449     printf("Generated nonce:\n  ");
    450     for (int i = 0; i < 12; i++) printf("%02x", nonce[i]);
    451     printf("\n\n");
    452 
    453     printf("════════════════════════════════════════════════════\n");
    454     printf("Test 6: Global CSPRNG (convenience API)\n");
    455     printf("════════════════════════════════════════════════════\n");
    456 
    457     uint8_t global_random[16];
    458     random_bytes(global_random, 16);
    459 
    460     printf("Global random bytes: ");
    461     for (int i = 0; i < 16; i++) {
    462         printf("%02x", global_random[i]);
    463     }
    464     printf("\n");
    465 
    466     printf("Global random uint32: %u\n", random_uint32());
    467     printf("Global random uint64: %llu\n", (unsigned long long)random_uint64());
    468     printf("Global random in [0, 100): %u\n\n", random_uniform(100));
    469 
    470     /* Cleanup */
    471     csprng_cleanup(&ctx);
    472 
    473     printf("════════════════════════════════════════════════════\n");
    474     printf("Security Notes:\n");
    475     printf("════════════════════════════════════════════════════\n");
    476     printf("• Suitable for generating cryptographic keys\n");
    477     printf("• Seeds from high-resolution clocks and ASLR\n");
    478     printf("• Automatic reseeding every 1MB\n");
    479     printf("• Forward secrecy (can't reconstruct past output)\n");
    480     printf("• Uniform distribution (no modulo bias)\n");
    481     printf("• Production-ready for key generation\n");
    482 
    483     return 0;
    484 }
    485 #endif