Tein aikani kuluksi vielä käsittääkseni NIST SP 800-90A standardin täyttävän pseudosatunnaislukugeneraattorin joka käyttää RDSEED:llä tuotettua 256 bitin siementä ja saman kokoista suolaa. Aika hyvää jälkeä tämäkin tekee. Vaatii OpenSSL 3.0+ toimiakseen. Se aina 1G välein siementää itsensä uudestaan joka on huomattavasti tiheämmin mitä standardi vaatisi. Pitää ajaa tälle vaativampia testejä kunhan tässä ehtii ja jaksaa.
Koodi: Valitse kaikki
#include <openssl/evp.h>
#include <openssl/kdf.h>
#include <immintrin.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#define BLOCK_SIZE 16 // AES-lohkon koko tavuina
#define CHUNK_BLOCKS 1024 // Lohkojen määrä per kirjoituserä (16 KB)
#define RESEED_INTERVAL (64 * 1024) // Uudelleensiemennysväli: 64 * 16 MB = 1 GB
#define SEED_SIZE 32 // 256-bittinen siemen
#define SALT_SIZE 32 // 256-bittinen suola
#define RDSEED_TIMEOUT_NS 10000000000 // 10 s aikaraja RDSEED:lle per 64 bittiä
#define LOG_FILE "aes_ctr_drbg.log" // Lokitiedoston nimi
static volatile sig_atomic_t keep_running = 1;
static EVP_CIPHER_CTX *global_ctx = NULL;
static FILE *log_file = NULL;
// Lokitusfunktio virheille (kirjoittaa lokiin ja stderr:iin)
void log_message(const char *message) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
char timestamp[32];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", localtime(&ts.tv_sec));
fprintf(log_file, "[%s.%03ld] %s\n", timestamp, ts.tv_nsec / 1000000, message);
fprintf(stderr, "[%s.%03ld] %s\n", timestamp, ts.tv_nsec / 1000000, message);
fflush(log_file);
}
// Lokitusfunktio tapahtumille (kirjoittaa vain lokiin)
void log_event(const char *message) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
char timestamp[32];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", localtime(&ts.tv_sec));
fprintf(log_file, "[%s.%03ld] %s\n", timestamp, ts.tv_nsec / 1000000, message);
fflush(log_file);
}
// Signaalinkäsittelijä Ctrl+C:lle
void handle_sigint(int sig) {
(void)sig;
keep_running = 0;
}
// Generoi 64-bittinen satunnaisluku RDSEED:llä
int get_random_64(unsigned long long *val) {
struct timespec start, now;
clock_gettime(CLOCK_MONOTONIC, &start);
// Yritä RDSEED:ä aikarajan puitteissa
while (1) {
if (_rdseed64_step(val) == 1) {
return 1;
}
clock_gettime(CLOCK_MONOTONIC, &now);
long elapsed_ns = (now.tv_sec - start.tv_sec) * 1000000000L + (now.tv_nsec - start.tv_nsec);
if (elapsed_ns > RDSEED_TIMEOUT_NS) {
log_message("RDSEED timeout exceeded");
return 0;
}
nanosleep(&(struct timespec){.tv_nsec = 1000000}, NULL); // 1 ms tauko
}
}
// Generoi 256-bittinen siemen ja 256-bittinen suola RDSEED:llä
int generate_seed_and_salt(uint8_t *seed, uint8_t *salt) {
// Generoi 256 bittiä siemenelle
for (int i = 0; i < SEED_SIZE / sizeof(unsigned long long); i++) {
unsigned long long val;
if (!get_random_64(&val)) {
log_message("Failed to generate seed");
return 0;
}
memcpy(seed + i * sizeof(unsigned long long), &val, sizeof(unsigned long long));
}
// Generoi 256 bittiä suolalle
for (int i = 0; i < SALT_SIZE / sizeof(unsigned long long); i++) {
unsigned long long val;
if (!get_random_64(&val)) {
log_message("Failed to generate salt");
return 0;
}
memcpy(salt + i * sizeof(unsigned long long), &val, sizeof(unsigned long long));
}
return 1;
}
// Aseta uusi siemen ja alusta laskuri
int reseed(EVP_CIPHER_CTX *ctx, uint8_t *iv) {
uint8_t seed[SEED_SIZE];
uint8_t salt[SALT_SIZE];
uint8_t key[SEED_SIZE];
// Generoi siemen ja suola
if (!generate_seed_and_salt(seed, salt)) {
return 0;
}
// Käytä HKDF:ää avaimen muodostamiseen
EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL);
if (!kdf) {
log_message("EVP_KDF_fetch failed");
return 0;
}
EVP_KDF_CTX *kdf_ctx = EVP_KDF_CTX_new(kdf);
EVP_KDF_free(kdf);
if (!kdf_ctx) {
log_message("EVP_KDF_CTX_new failed");
return 0;
}
OSSL_PARAM params = {
OSSL_PARAM_construct_utf8_string("digest", "SHA256", 0),
OSSL_PARAM_construct_octet_string("salt", salt, SALT_SIZE),
OSSL_PARAM_construct_octet_string("key", seed, SEED_SIZE),
OSSL_PARAM_construct_end()
};
if (EVP_KDF_derive(kdf_ctx, key, SEED_SIZE, params) != 1) {
log_message("EVP_KDF_derive failed");
EVP_KDF_CTX_free(kdf_ctx);
return 0;
}
EVP_KDF_CTX_free(kdf_ctx);
// Nollaa laskuri (big-endian, OpenSSL hoitaa kasvattamisen)
memset(iv, 0, BLOCK_SIZE);
// Alusta EVP-konteksti
if (EVP_CIPHER_CTX_reset(ctx) != 1) {
log_message("EVP_CIPHER_CTX_reset failed");
return 0;
}
if (EVP_EncryptInit_ex2(ctx, EVP_aes_256_ctr(), key, iv, NULL) != 1) {
log_message("EVP_EncryptInit_ex2 failed");
return 0;
}
return 1;
}
int main() {
// Ohita SIGPIPE-signaali
signal(SIGPIPE, SIG_IGN);
// Avaa lokitiedosto
log_file = fopen(LOG_FILE, "a");
if (!log_file) {
fprintf(stderr, "Failed to open log file " LOG_FILE "\n");
return 1;
}
log_event("Program started");
// Asenna SIGINT-käsittelijä
signal(SIGINT, handle_sigint);
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
log_message("EVP_CIPHER_CTX_new failed");
fclose(log_file);
return 1;
}
global_ctx = ctx;
uint8_t iv[BLOCK_SIZE] = {0}; // 128-bittinen IV (laskuri)
uint8_t buffer[CHUNK_BLOCKS * BLOCK_SIZE]; // Puskuri: 1024 * 16 = 16 KB
uint8_t zero_block[BLOCK_SIZE] = {0}; // Nollapuskuri syötteeksi
long chunk_counter = 0;
// Alusta DRBG ensimmäisellä siemenellä
if (!reseed(ctx, iv)) {
EVP_CIPHER_CTX_free(ctx);
fclose(log_file);
return 1;
}
// Generoi satunnaisdataa, kunnes keskeytetään
while (keep_running) {
// Generoi yksi 16 KB:n erä
for (int i = 0; i < CHUNK_BLOCKS && keep_running; i++) {
int outlen;
if (EVP_EncryptUpdate(ctx, &buffer[i * BLOCK_SIZE], &outlen, zero_block, BLOCK_SIZE) != 1) {
log_message("EVP_EncryptUpdate failed");
EVP_CIPHER_CTX_free(ctx);
fclose(log_file);
return 1;
}
if (outlen != BLOCK_SIZE) {
log_message("Unexpected output length");
EVP_CIPHER_CTX_free(ctx);
fclose(log_file);
return 1;
}
}
// Kirjoita erä stdout:iin
if (keep_running) {
if (fwrite(buffer, 1, sizeof(buffer), stdout) != sizeof(buffer)) {
if (errno == EPIPE) {
log_event("Output pipe closed, shutting down");
keep_running = 0;
} else {
log_message("fwrite failed");
EVP_CIPHER_CTX_free(ctx);
fclose(log_file);
return 1;
}
} else {
fflush(stdout); // Varmista kirjoitus putkeen
chunk_counter++;
// Uudelleensiemennä joka 1 GB:n jälkeen
if (chunk_counter % RESEED_INTERVAL == 0) {
if (!reseed(ctx, iv)) {
EVP_CIPHER_CTX_free(ctx);
fclose(log_file);
return 1;
}
char msg[64];
long gb_generated = (chunk_counter * CHUNK_BLOCKS * BLOCK_SIZE) / (1024LL * 1024 * 1024);
snprintf(msg, sizeof(msg), "Reseeded after %ld GB", gb_generated);
log_event(msg);
}
}
}
}
// Lokita SIGINT, jos ohjelma keskeytettiin
if (!keep_running) {
log_event("Received SIGINT, shutting down");
}
EVP_CIPHER_CTX_free(ctx);
log_event("Program terminated");
fclose(log_file);
global_ctx = NULL;
return 0;
}