3 August 2026

A (hopefully not horribly unsafe) CSPRNG based on 3-round Feistel networks, as seen in the 4th iteration kcrypt.

#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define Fi(n,a...) for (int i = 0; i < n; i++) { a; }
typedef uint8_t gf;  typedef struct { gf k1[32], k2[64]; } bk;
static gf LOG[256], EXP[510];
static void gentab(void) {
  int b = 1;  Fi(255, LOG[EXP[i] = EXP[i + 255] = b] = i,
                      b = b * 2 ^ b / 128 * 285)
}
static gf mul2(gf x) { return x * 2 ^ x / 128 * 285; }
static gf inv(gf x) { return x ? EXP[255 - LOG[x]] : 0; }
static void shuffle(gf p[32], gf key[64], int round) {
  int j, t;  Fi(32, p[i] = i)
  for (int i = 32; --i;)
    t = p[i], p[i] = p[j = key[i + round * 21 & 63] % -~i], p[j] = t;
}
static void diffuse(gf a[32]) {
  static const int offset[] = { 1, 3, 7, 13 };  gf b[32];
  for (int q = 0; q < 4; q++) {
    Fi(32, b[i] = a[i] ^ mul2(a[i + offset[q] & 31]))
    memcpy(a, b, 32);
  }
}
static void block(gf out[64], gf in[64], bk * key) {
  gf rk[16][32], s[32], next[32], p[32];  memcpy(s, key->k1, 32);
  for (int q = 0; q < 16; q++) {
    shuffle(p, key->k2, q);
    Fi(32, next[i] = (gf) (inv(s[p[i]] ^ mul2(s[p[i + 1 & 31]])
      ^ key->k2[i + q * 17 & 63] ^ (gf) ((q + 1) * 0x9d + i))
      + key->k2[i + 32 + q * 29 & 63]))
    diffuse(next);  memcpy(rk[q], next, 32);  memcpy(s, next, 32);
  }
  memcpy(out, in, 64);
  for (int q = 0; q < 16; q++) {
    gf r[32];  memcpy(r, out + 32, 32);  shuffle(p, key->k2, q);
    Fi(32, out[32 + i] = (gf) (rk[q][i] + inv(r[p[i]]
      ^ mul2(r[p[i + 1 & 31]]) ^ key->k2[i + 32 + q * 11 & 63])))
    diffuse(out + 32);  Fi(32, out[32 + i] ^= out[i])
    memcpy(out, r, 32);
  }
}
int main(void) {
  gentab();  struct { bk key; gf nonce[16]; } seed;
  int random = open("/dev/urandom", O_RDONLY);
  if (random < 0) goto err;
  gf * p = (gf *) &seed;
  for (size_t left = sizeof seed; left;) {
    ssize_t n = read(random, p, left);
    if (n > 0) p += n, left -= (size_t)n;
    else if (n < 0 && errno == EINTR) continue;
    else goto err;
  }
  if (close(random) < 0)
    { err: perror("/dev/urandom");  return 1; }
  gf in[64] = {0}, out[64];  memcpy(in, seed.nonce, 16);  in[63] = 3;
  for (uint64_t counter = 0;; counter++) {
    Fi(8, in[16 + i] = counter >> i * 8)
    block(p = out, in, &seed.key);
    for (size_t left = 64; left;) {
      ssize_t n = write(STDOUT_FILENO, p, left);
      if (n > 0) p += n, left -= (size_t)n;
      else if (n < 0 && errno == EINTR) continue;
      else {
        if (errno == EPIPE) return 0;
        perror("stdout");  return 1;
      }
    }
    if (counter == UINT64_MAX)
      { fputs("krng: counter exhausted\n", stderr);  return 1; }
  }
}
< back to journal