Linux 하드웨어 난수 생성기 (hwrng & QRNG)

hwrng 및 QRNG 경로를 커널 엔트로피 품질과 암호학적 안전성 관점에서 심층 분석합니다. hwrng 프레임워크와 드라이버 등록 모델, 하드웨어 난수 소스 신뢰도 평가, 엔트로피 풀 혼합과 credit 정책, 커널 CSPRNG(ChaCha20) 초기화·재시드 동작, 부팅 초기 entropy starvation 대응, 가상화/임베디드 환경의 난수 공급 이슈, rngd와 userspace 연계, 품질 검증과 장애 진단 절차까지 안전한 난수 인프라 구축에 필요한 핵심을 다룹니다.

전제 조건
일상 비유: 난수 생성기는 거대한 동전 던지기 기계

컴퓨터는 본질적으로 결정론적 기계라 "진정한 우연"을 만들지 못합니다. 하드웨어 난수 생성기(hwrng)는 열잡음, 방사선 감쇠, 양자 효과 등 물리 세계의 예측 불가한 현상을 측정해 진짜 우연한 비트를 공급합니다. 커널은 이 원시 엔트로피를 ChaCha20 암호화 알고리즘으로 정제해 빠르고 안전한 난수 스트림(/dev/urandom)을 만듭니다.

핵심 요약

  • TRNG vs CSPRNG: 하드웨어 TRNG는 물리적 엔트로피 원천, CSPRNG는 그것을 씨앗 삼아 대량 난수 생성
  • /dev/random vs /dev/urandom: 현대 커널(5.18+)에서 두 인터페이스는 동일하게 동작 (CSPRNG 기반)
  • getrandom() 시스템 콜: 커널 초기화 완료 전에는 블록, 이후 절대 블록하지 않음
  • hwrng 드라이버: struct hwrng 하나만 구현하면 커널 엔트로피 풀에 자동 기여
  • quality 파라미터: 하드웨어 품질을 0~1024로 표현 (1024 = 비트당 1비트 엔트로피)
  • QRNG 물리 원리 3종: 빔 분리기(편광), 진공 요동(ΔEΔt≥ℏ/2), 광자 도착 시간(TOAD)은 모두 양자 역학적 비결정성 활용
  • NIST SP 800-90B: min-entropy 기반 통계 검증으로 hwrng quality 파라미터를 인증, FIPS 140-3 요구사항과 연동
  • PCIe vs USB QRNG: PCIe는 MSI 인터럽트+MMIO 직접 읽기로 더 높은 처리율 달성, USB는 Bulk 전송으로 단순 구현

단계별 이해

  1. 엔트로피 풀 개념 파악엔트로피 풀 메커니즘
  2. 커널 CSPRNG 내부 구조 이해커널 CSPRNG 내부
  3. hwrng API 학습hwrng 드라이버 개발
  4. QRNG 물리 원리 심화QRNG 물리적 원리 심화
  5. 실제 QRNG 드라이버 분석QRNG 드라이버 구현
  6. NIST 800-90B 인증 이해NIST SP 800-90B 인증
  7. 암호 키 생성 플로우암호 키 생성 완전 플로우
  8. 테스트/검증 방법 숙지테스트 및 검증

hwrng 서브시스템 개요

Linux 커널의 hwrng(Hardware Random Number Generator) 서브시스템은 다양한 하드웨어 난수 생성기를 통일된 인터페이스로 추상화합니다. Intel RDRAND, ARM TRNG, TPM 2.0, USB QRNG 등 모든 하드웨어 RNG는 동일한 struct hwrng API를 통해 커널 엔트로피 풀에 기여합니다.

난수 생성기 분류

유형전체 명칭특징예시
TRNG True Random Number Generator 물리적 현상 기반, 느리지만 진정한 무작위성 열잡음, 방사선, 광자 분리
QRNG Quantum Random Number Generator 양자 역학 기반, 원리적으로 예측 불가 ID Quantique, QuintessenceLabs
PRNG Pseudo Random Number Generator 결정론적, 빠름, 씨앗(seed)에 의존 rand(), Mersenne Twister
CSPRNG Cryptographically Secure PRNG 역추적 불가, 예측 저항성, 암호화 안전 Linux ChaCha20-CRNG, /dev/urandom
DRBG Deterministic Random Bit Generator NIST SP 800-90A 표준, 재현 가능 CTR-DRBG(AES), Hash-DRBG

QRNG 방식별 비교

방식물리 현상처리율대표 제품양자 원리
빔 분리기 광자 편광 불확정성 1~10 Mbps ID Quantique Quantis 하이젠베르크 불확정성 원리
진공 요동 진공 상태 전자기장 ΔE 100+ Mbps QuintessenceLabs qStream ΔEΔt ≥ ℏ/2 (에너지-시간 불확정성)
광자 도착 시간 (TOAD) 광자 방출 시각 양자 요동 10~100 Mbps Comscope, Toshiba QKD 파동-입자 이중성
방사성 붕괴 핵 붕괴 타이밍 낮음 (kbps) HotBits, Geiger counter 양자 터널링
위상 잡음 레이저 위상 자연 선 폭 1~10 Gbps 연구 단계 (대규모) 자연 선 폭 불확정성

커널 내 hwrng 위치 — 아키텍처 다이어그램

Intel RDRAND / RDSEED ARM TRNG / BCM2835 USB QRNG TPM 2.0 RNG hwrng 서브시스템 /drivers/char/hw_random/ hwrng_register() hwrng_unregister() rng-tools 인터페이스 /dev/hwrng IRQ 타이밍 지터 (interrupt jitter) 네트워크/블록 I/O (disk/net 이벤트) CPU 성능 카운터 (jiffies, TSC 지터) 엔트로피 풀 input_pool (Blake2b) 256비트 해시 상태 엔트로피 추정값 add_hwgenerator_randomness() add_interrupt_randomness() credit_entropy_bits() ChaCha20-CRNG crng[NR_CPUS] per-CPU 상태 (64B key) crng_reseed() 주기적 갱신 5분마다 or 요청 시 /dev/urandom /dev/random getrandom() syscall get_random_bytes() get_random_u32()

QRNG 물리적 원리 심화

QRNG 제품 서베이: 국내외 20+ QRNG 업체의 제품별 상세 스펙, 양자 방식 비교, 인증 현황, 구매 경로를 종합 분석한 QRNG 제품 서베이 — 양자 난수 생성기 업체 및 기술 분석 페이지를 참고하세요.

QRNG는 양자 역학의 고유한 비결정성(inherent randomness)을 활용합니다. 고전 물리계와 달리, 양자 시스템의 측정 결과는 원리적으로 예측 불가능하며 이 특성이 진정한 엔트로피 원천이 됩니다. 대표적인 세 가지 방식을 심층 분석합니다.

방식 1: 광자 빔 분리기 (Beam Splitter, BS50:50)

단일 광자를 50:50 빔 분리기(반투명 거울)에 통과시키면, 광자는 양자 중첩 상태로 투과와 반사 두 경로를 동시에 진행합니다. 검출기가 측정하는 순간 파동함수가 붕괴되어 0 또는 1이 결정됩니다. 이 결정은 하이젠베르크 불확정성 원리에 의해 원천적으로 예측 불가능합니다.

단일 광자 소스 → [50:50 빔 분리기] → 검출기 A (반사) → 0비트
                                   ↘ 검출기 B (투과) → 1비트

양자 중첩: |광자⟩ = (1/√2)|반사⟩ + (1/√2)|투과⟩
측정 붕괴: P(0) = |1/√2|² = 0.5,  P(1) = |1/√2|² = 0.5
min-entropy: H_min = -log₂(0.5) = 1.0 비트/비트  ← 이론적 최대

방식 2: 진공 요동 (Vacuum Fluctuation)

진공 상태는 빈 공간이 아닙니다. 하이젠베르크 에너지-시간 불확정성 원리(ΔEΔt ≥ ℏ/2)에 의해 진공에서도 전자기장이 끊임없이 요동칩니다. 이 진공 요동을 ADC(아날로그-디지털 변환기)로 측정하면 양자 무작위 비트를 추출할 수 있습니다. 처리율이 높아(100+ Mbps) 상용 QRNG에 많이 사용됩니다.

/* 진공 요동 QRNG 원리 — ADC 기반 비트 추출 */
/* 호모다인 검출(Homodyne Detection): 진공 상태 X 직교 성분 측정 */
/* 1. 레이저 로컬 오실레이터와 진공 입력 포트 간섭 */
/* 2. 밸런스드 포토다이오드로 차동 광전류 측정 */
/* 3. ADC 샘플링 → 가우시안 분포 데이터 획득 */
/* 4. 추출기(Extractor)로 균등 분포 비트 변환 */

struct vacuum_qrng_sample {
    int16_t adc_raw;      /* 진공 요동 ADC 값: 가우시안 분포 */
    uint8_t lsb_bits;    /* LSB 추출: 가우시안 → 균등 분포 근사 */
};

/* 토플리츠 해시 추출기 (Toeplitz Hash Extractor) */
static int vacuum_extract_bits(const int16_t *adc_samples, size_t n,
                                uint8_t *output, size_t out_bytes)
{
    /* 토플리츠 행렬 곱으로 입력 편향 제거 */
    /* min-entropy 추정에 따라 압축 비율 결정 */
    /* NIST SP 800-90B 3.1.3절의 추출기 요구사항 준수 */
    return toeplitz_hash(adc_samples, n, output, out_bytes);
}

방식 3: 광자 도착 시간 (TOAD — Time Of Arrival Detection)

레이저 다이오드가 방출하는 광자들의 도착 시각은 양자 역학적 파동-입자 이중성으로 인해 원천적으로 불확정적입니다. 각 광자 도착 이벤트의 타임스탬프에서 LSB(최하위 비트)만 추출하면 빠르고 품질 높은 양자 난수를 얻을 수 있습니다.

/* TOAD 방식 hwrng read 콜백 예제 */
/* 1. SPAD(Single Photon Avalanche Diode)로 광자 도착 시각 측정 */
/* 2. TDC(Time-to-Digital Converter)로 ps 정밀도 타임스탬프 획득 */
/* 3. 연속 타임스탬프 간 차이(Δt) 계산 */
/* 4. Δt의 LSB 비트 추출 → 균등 분포 난수 */
static int toad_read(struct hwrng *rng, void *data, size_t max, bool wait)
{
    struct toad_dev *dev = container_of(rng, struct toad_dev, hwrng);
    uint64_t t1, t2, delta;
    size_t bytes = 0;
    uint8_t *out = data;

    while (bytes < max) {
        t1 = readq(dev->tdc_base + TDC_TIMESTAMP_REG);  /* 광자 n 도착 시각 */
        t2 = readq(dev->tdc_base + TDC_TIMESTAMP_REG);  /* 광자 n+1 도착 시각 */
        delta = t2 - t1;                                    /* 광자 간격 Δt */
        /* LSB 8비트 추출 — 가장 빠르게 변하는 비트 */
        *out++ = (uint8_t)(delta & 0xFF);
        bytes++;
    }
    return bytes;
}
QRNG 물리적 원리 3종 비교 ① 빔 분리기 (BS50:50) 단일 광자 소스 50:50 빔 분리기 검출기A → 0 검출기B→1 P(0) = P(1) = 0.5 H_min = 1.0 비트/비트 원리: 하이젠베르크 불확정성 처리율: 1~10 Mbps 대표: ID Quantique Quantis quality=1024 적합 ② 진공 요동 (Vacuum) 진공 상태 입력 포트 호모다인 검출기 (Balanced Photodetector) ADC (가우시안 샘플링) 토플리츠 추출기 원리: ΔEΔt ≥ ℏ/2 처리율: 100+ Mbps 대표: QuintessenceLabs qStream quality=900~1024 적합 ③ 광자 도착 시간 (TOAD) 레이저 다이오드 펄스 SPAD 단일 광자 검출기 TDC 타임스탬프 (ps) Δt LSB 8비트 추출 원리: 파동-입자 이중성 처리율: 10~100 Mbps 대표: Toshiba QKD, Comscope quality=800~1024 적합 공통 결론: 세 방식 모두 hwrng quality=1024로 등록 가능 NIST SP 800-90B 인증 후 quality 값 확정 → add_hwgenerator_randomness()로 커널 input_pool에 기여 엔트로피 기여량 = bytes_read × quality × 8 / 1024 (비트)

엔트로피 풀 메커니즘

커널 엔트로피 풀은 drivers/char/random.c에 구현된 핵심 난수 인프라입니다. Linux 5.18 이후 단일 input_pool 구조로 단순화되었습니다.

/dev/random vs /dev/urandom — 현대 커널의 통합

Linux 5.18+ 변경사항: /dev/random/dev/urandom은 이제 동일한 CSPRNG 백엔드를 사용합니다. /dev/random은 더 이상 엔트로피 고갈로 블록하지 않습니다. 블록킹 동작은 초기화 완료 전에만 발생합니다.
인터페이스블록킹 조건권장 용도커널 구현
/dev/random CRNG 초기화 전 레거시 호환성 random_read()
/dev/urandom 절대 블록 안 함 일반 용도 urandom_read()
getrandom() CRNG 초기화 전 (GRND_NONBLOCK 아닐 때) 권장 — 파일 디스크립터 불필요 sys_getrandom()
get_random_bytes() 없음 (커널 내부용) 커널 드라이버 lib/random.c

엔트로피 소스와 수집 경로

/* drivers/char/random.c — 엔트로피 수집 함수들 */

/* 1. 인터럽트 지터 — 가장 풍부한 소프트웨어 엔트로피 소스 */
void add_interrupt_randomness(int irq)
{
    struct fast_pool *fast_pool = this_cpu_ptr(&irq_randomness);
    struct pt_regs *regs = get_irq_regs();
    unsigned long now = jiffies;
    cycles_t cycles = random_get_entropy();  /* TSC or 아키텍처별 타이머 */

    fast_mix(fast_pool->pool, irq, now ^ cycles,
             regs ? instruction_pointer(regs) : _RET_IP_);

    /* 64회 인터럽트마다 input_pool에 기여 */
    if (++fast_pool->count < 64 && !time_after(now, fast_pool->last + HZ))
        return;
    add_interrupt_bench(cycles);
    mix_interrupt_randomness(fast_pool);
}

/* 2. hwrng 기여 — hw_random 코어가 자동으로 호출 */
void add_hwgenerator_randomness(const void *buf, size_t len, size_t entropy)
{
    _mix_pool_bytes(buf, len);          /* input_pool에 혼합 */
    credit_init_bits(entropy);          /* 엔트로피 추정값 증가 */
    wake_up_interruptible(&random_wait); /* 대기 중인 getrandom() 깨움 */
}

/* 3. 시스템 이벤트 기여 */
void add_device_randomness(const void *buf, size_t len)  /* MAC 주소, 시리얼 번호 등 */
void add_input_randomness(unsigned int type, unsigned int code, unsigned int val)  /* 키보드/마우스 */
void add_disk_randomness(struct gendisk *disk)  /* 디스크 I/O 완료 타이밍 */
코드 설명
  • cycles_t cycles random_get_entropy()는 x86에서 TSC(Time Stamp Counter), ARM에서 CNTVCT를 읽습니다. 인터럽트 도착 시각의 나노초 단위 지터가 실제 엔트로피가 됩니다.
  • fast_pool per-CPU 고속 풀입니다. 모든 인터럽트마다 input_pool에 직접 쓰면 락 경합이 심해지므로, 64회마다 한 번씩 배치 처리합니다.
  • credit_init_bits(entropy) 엔트로피 추정값을 비트 단위로 누적합니다. CRNG 초기화에는 256비트 이상이 필요합니다. hwrng의 quality 값이 이 계산에 사용됩니다.

getrandom() 시스템 콜 흐름

/* 사용자 공간에서 사용 */
#include <sys/random.h>

unsigned char key[32];
ssize_t ret = getrandom(key, sizeof(key), 0);  /* 0 = 블록킹 모드 */
if (ret < 0) {
    perror("getrandom");  /* EINTR, EFAULT 가능 */
    return -1;
}

/* GRND_NONBLOCK: CRNG 초기화 전에도 블록하지 않음 (EAGAIN 반환) */
ret = getrandom(key, sizeof(key), GRND_NONBLOCK);

/* GRND_RANDOM: /dev/random 동작 에뮬레이션 (레거시, 권장 안 함) */
ret = getrandom(key, sizeof(key), GRND_RANDOM);

/* 커널 내부: get_random_bytes() 계열 */
get_random_bytes(buf, nbytes);              /* 범용 */
u32 r = get_random_u32();                   /* 32비트 */
u64 r = get_random_u64();                   /* 64비트 */
u32 r = get_random_u32_below(100);         /* 0~99, 편향 없음 */
u32 r = get_random_u32_inclusive(1, 6);   /* 주사위 1~6 */

엔트로피 예산 계산 — quality 파라미터의 역할

hwrng 코어는 hwrng_fillfn() 커널 스레드를 통해 지속적으로 rng->read()를 호출하고, 읽은 바이트 수와 quality 값을 곱해 엔트로피 기여량을 계산합니다.

/* drivers/char/hw_random/core.c — hwrng_fillfn() 핵심 로직 */
static int hwrng_fillfn(void *unused)
{
    long rc;

    while (!kthread_should_stop()) {
        struct hwrng *rng;
        mutex_lock(&rng_mutex);
        rng = get_current_rng_nolock();
        mutex_unlock(&rng_mutex);

        if (!rng) { msleep(1000); continue; }

        /* read() 호출 — 최대 RNGD_BUFF_SIZE(4096)바이트 */
        rc = rng->read(rng, rng_buffer, rng_buffer_size, true);
        if (rc <= 0) { msleep(500); continue; }

        /* 엔트로피 기여량 계산:
         *   entropy_bits = bytes_read × quality × 8 / 1024
         *   예) 4096바이트 × quality=1024 × 8 / 1024 = 32768비트
         *   예) 4096바이트 × quality=512  × 8 / 1024 = 16384비트
         *   예) 4096바이트 × quality=0    × 8 / 1024 = 0비트 (기여 없음) */
        add_hwgenerator_randomness(rng_buffer, rc,
                                    (rc * rng->quality * 8) >> 10);
    }
    hwrng_put(rng);
    return 0;
}
quality 값의미4096바이트 읽기 시 기여 엔트로피CRNG 초기화(256비트) 위한 읽기 횟수
1024이론적 최대 (검증 QRNG)32,768 비트1회 미만
51250% 효율 (Intel RDRAND)16,384 비트1회 미만
25625% 효율 (열잡음 TRNG)8,192 비트1회 미만
12812.5% 효율 (FPGA 지터)4,096 비트1회 미만
0엔트로피 기여 없음0 비트∞ (초기화 불가)

커널 CSPRNG 내부

Linux 커널은 ChaCha20 기반 CRNG(Cryptographically secure Random Number Generator)를 사용합니다. 커밋 1e7f583903b(v5.17)부터 per-CPU ChaCha20 CRNG로 완전히 전환되었습니다.

ChaCha20-CRNG 상태 구조

/* include/linux/random.h, drivers/char/random.c */

/* per-CPU CRNG 상태 — 64바이트 (ChaCha20 키 크기) */
struct crng {
    u8 key[CHACHA20_KEY_SIZE];  /* 32바이트 키 */
    unsigned long generation;   /* 리시드 세대 번호 */
    local_lock_t lock;           /* per-CPU 경량 락 */
};

/* 전역 초기화 완료 플래그 */
static bool crng_ready = false;  /* 256비트 엔트로피 수집 후 true */
static u64 base_crng_generation; /* 전역 세대 카운터 */

/* CRNG 초기화 — 부팅 시 엔트로피 수집 */
static void crng_initialize_primary(void)
{
    extract_entropy(base_crng.key, sizeof(base_crng.key));
    crng_ready = true;
    pr_notice("random: crng init done\n");
}

crng_reseed() — 주기적 갱신 메커니즘

/* 5분마다 또는 충분한 엔트로피 누적 시 호출 */
static void crng_reseed(struct crng *crng)
{
    u8 key[CHACHA20_KEY_SIZE];

    /* input_pool에서 32바이트 추출 (Blake2b 해시 압축) */
    extract_entropy(key, sizeof(key));

    /* 기존 키와 새 키를 XOR하여 forward secrecy 보장 */
    memcpy(&crng->key, key, sizeof(key));
    WRITE_ONCE(crng->generation, base_crng_generation);
    memzero_explicit(key, sizeof(key));  /* 스택 잔류 키 소거 */
}

/* 난수 생성 — ChaCha20 스트림으로 64바이트씩 출력 */
static void crng_fast_key_erasure(u8 *key, struct chacha20_ctx *chacha_state,
                                   u8 *random_data, size_t random_data_len)
{
    /* ChaCha20 블록으로 난수 스트림 생성 */
    chacha20_block(chacha_state, random_data);

    /* Fast Key Erasure: 출력의 앞 32바이트를 새 키로 사용 */
    /* → 이전 출력으로 키를 역추적 불가 (forward secrecy) */
    memcpy(key, random_data, CHACHA20_KEY_SIZE);
    memzero_explicit(random_data, CHACHA20_KEY_SIZE);  /* 키 부분 소거 */
}
코드 설명
  • extract_entropy() input_pool의 Blake2b 해시 상태를 압축해 32바이트 키를 추출합니다. 이 과정은 풀 상태를 변경(absorb)하므로 같은 키가 두 번 출력되지 않습니다.
  • Fast Key Erasure Daniel Bernstein이 설계한 기법입니다. ChaCha20 블록 출력의 첫 32바이트를 즉시 새 키로 교체합니다. 메모리가 탈취되어도 이전 출력을 재현할 수 없습니다.
  • memzero_explicit() 컴파일러 최적화로 소거 코드가 제거되지 않도록 강제합니다. 일반 memset()은 최적화 옵션에 따라 제거될 수 있습니다.

input_pool — Blake2b 압축 함수

/* input_pool: Blake2b 해시 상태 기반 엔트로피 수집 */
struct blake2s_state {
    u32 h[8];   /* 256비트 해시 상태 */
    u32 t[2];   /* 처리된 바이트 카운터 */
    u32 f[2];   /* 최종화 플래그 */
    u8  buf[BLAKE2S_BLOCK_SIZE];  /* 입력 버퍼 (64바이트) */
    size_t buflen;
    u8  outlen;
};

/* 엔트로피 혼합 — Blake2s 압축으로 되돌릴 수 없음 */
static void _mix_pool_bytes(const void *buf, size_t len)
{
    blake2s_update(&input_pool, buf, len);  /* 단방향 해시 업데이트 */
}

/* 엔트로피 추출 — 풀 상태를 변경하며 키 생성 */
static void extract_entropy(void *buf, size_t nbytes)
{
    struct blake2s_state recovery = input_pool;  /* 풀 스냅샷 */
    blake2s_final(&recovery, buf);              /* 최종 해시 출력 */
    blake2s_update(&input_pool, buf, nbytes);   /* 출력을 풀에 재혼합 */
}

hwrng 드라이버 개발

hwrng 서브시스템의 드라이버 API는 단순합니다. struct hwrng 구조체를 초기화하고 hwrng_register()를 호출하면 됩니다.

struct hwrng — 핵심 API

/* include/linux/hw_random.h */

struct hwrng {
    const char   *name;     /* 드라이버 이름 (/sys/class/misc/hw_random/rng_current) */
    int          (*init)(   struct hwrng *rng);           /* 선택사항: 초기화 */
    void         (*cleanup)(struct hwrng *rng);           /* 선택사항: 정리 */
    int          (*data_present)(struct hwrng *rng, int wait);  /* 데이터 준비 여부 */
    int          (*data_read)(  struct hwrng *rng, u32 *data);   /* 구형 API, 4바이트 */
    int          (*read)(       struct hwrng *rng, void *data,   /* 권장 API */
                                size_t max, bool wait);
    unsigned long priv;       /* 드라이버 사용 개인 데이터 (구형) */
    unsigned short quality;   /* 엔트로피 품질: 0(최저)~1024(최고) */
                               /* 1024 = 비트당 1.0비트 엔트로피 (물리적 최대) */
    /* 내부 사용 — 드라이버가 초기화 금지 */
    struct list_head list;
    struct kref     ref;
    struct completion cleanup_done;
    struct completion dying;
};

quality 파라미터 가이드

quality 값엔트로피 품질해당 하드웨어
10241.0 비트/비트 (이론적 최대)검증된 QRNG, 방사선 기반
512~10230.5~1.0 비트/비트Intel RDRAND, ARM TRNG, TPM RNG
256~5110.25~0.5 비트/비트열잡음 기반 아날로그 TRNG
128~255보통 품질FPGA 링 오실레이터
0~127낮은 품질지터 기반, 환경 의존적
0엔트로피 기여 없음테스트용 또는 PRNG

최소 hwrng 드라이버 골격

#include <linux/module.h>
#include <linux/hw_random.h>
#include <linux/platform_device.h>

struct myrng_priv {
    void __iomem *base;  /* MMIO 기주소 */
    struct clk  *clk;
};

/* 필수: 난수 읽기 콜백 */
static int myrng_read(struct hwrng *rng, void *data, size_t max, bool wait)
{
    struct myrng_priv *priv = (struct myrng_priv *)dev_get_drvdata(rng->dev);
    size_t read = 0;

    /* HW가 준비될 때까지 폴링 (실제는 인터럽트 권장) */
    while (read + 4 <= max) {
        /* 레지스터에서 FIFO 가득 찼는지 확인 */
        if (!(readl(priv->base + MYRNG_STATUS_REG) & MYRNG_READY_BIT)) {
            if (!wait)
                break;
            cpu_relax();
            continue;
        }
        /* 32비트 난수 읽기 */
        *(u32 *)((u8 *)data + read) = readl(priv->base + MYRNG_DATA_REG);
        read += 4;
    }
    return read;  /* 실제 읽은 바이트 수 반환 */
}

static int myrng_probe(struct platform_device *pdev)
{
    struct myrng_priv *priv;
    struct hwrng *rng;
    struct resource *res;
    int ret;

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    priv->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);

    rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL);
    if (!rng)
        return -ENOMEM;

    rng->name    = "myrng";
    rng->read    = myrng_read;
    rng->quality = 900;   /* 하드웨어 스펙에 따라 조정 */

    platform_set_drvdata(pdev, priv);
    ret = devm_hwrng_register(&pdev->dev, rng);  /* devm 버전: 해제 자동화 */
    if (ret)
        return dev_err_probe(&pdev->dev, ret, "hwrng_register 실패\n");

    dev_info(&pdev->dev, "myrng 등록 완료 (quality=%u)\n", rng->quality);
    return 0;
}

static const struct of_device_id myrng_of_match[] = {
    { .compatible = "myvendor,myrng" },
    {}
};
MODULE_DEVICE_TABLE(of, myrng_of_match);

static struct platform_driver myrng_driver = {
    .probe  = myrng_probe,
    .driver = {
        .name           = "myrng",
        .of_match_table = myrng_of_match,
    },
};
module_platform_driver(myrng_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example hwrng driver");
코드 설명
  • myrng_read 반환값 실제 읽은 바이트 수를 반환해야 합니다. 0 이상이면 성공, 음수면 오류입니다. hw_random 코어는 이 값으로 엔트로피 기여량을 계산합니다.
  • devm_hwrng_register() 디바이스 관리 버전으로, 드라이버 언로드 시 자동으로 hwrng_unregister()를 호출합니다. 수동 hwrng_unregister()와 달리 remove() 콜백에 별도 코드가 필요 없습니다.
  • quality = 900 88% 효율의 엔트로피 품질을 의미합니다. 하드웨어 데이터시트의 min-entropy 스펙(예: 0.875 비트/비트)을 ×1024로 환산합니다.

QRNG 드라이버 구현

상용 QRNG 제품 비교: ID Quantique, EYL, Quantum Dice, Quside 등 상용 QRNG 장치의 폼팩터(USB/PCIe/칩), 속도, 인증 현황은 QRNG 제품 서베이 종합 비교표를 참고하세요.

QRNG(Quantum Random Number Generator)는 양자 역학적 현상(광자 방향, 진공 요동 등)을 이용해 원리적으로 예측 불가능한 비트를 생성합니다. 대부분의 상용 QRNG는 USB 또는 PCIe 인터페이스로 연결되어 hwrng 드라이버로 구현됩니다.

USB QRNG 드라이버 완전 예제

아래 코드는 USB HID 기반 QRNG 장치 (예: ID Quantique Quantis USB)의 드라이버 구조를 보여줍니다.

#include <linux/module.h>
#include <linux/usb.h>
#include <linux/hw_random.h>
#include <linux/slab.h>
#include <linux/completion.h>

#define QRNG_VENDOR_ID       0x1d50   /* 가상의 벤더 ID */
#define QRNG_PRODUCT_ID      0x6021
#define QRNG_BULK_IN_EP      0x81    /* Bulk IN 엔드포인트 */
#define QRNG_BUFFER_SIZE     4096   /* 한 번에 읽는 양자 난수 버퍼 크기 */
#define QRNG_QUALITY         1024   /* 양자 소스 — 이론적 최대 엔트로피 */

struct qrng_dev {
    struct usb_device  *udev;
    struct usb_interface *interface;
    struct hwrng        hwrng;

    /* 비동기 Bulk 전송용 */
    struct urb         *urb;
    u8                  *buf;        /* DMA 가능 버퍼 */
    size_t              buf_len;     /* 사용 가능한 바이트 수 */
    size_t              buf_off;     /* 현재 읽기 오프셋 */
    struct mutex        lock;
    struct completion   urb_done;
    bool                disconnecting;
};

/* URB 완료 콜백 — 인터럽트 컨텍스트에서 호출됨 */
static void qrng_urb_complete(struct urb *urb)
{
    struct qrng_dev *qdev = urb->context;

    if (urb->status == 0) {
        qdev->buf_len = urb->actual_length;
        qdev->buf_off = 0;
    } else if (urb->status == -ENOENT || urb->status == -ECONNRESET ||
               urb->status == -ESHUTDOWN) {
        qdev->buf_len = 0;  /* 연결 해제 시 정상 종료 */
    }
    complete(&qdev->urb_done);
}

/* hwrng read 콜백 — 슬립 가능 컨텍스트 */
static int qrng_read(struct hwrng *rng, void *data, size_t max, bool wait)
{
    struct qrng_dev *qdev = container_of(rng, struct qrng_dev, hwrng);
    size_t available, to_copy;
    int ret;

    mutex_lock(&qdev->lock);

    /* 버퍼에 데이터가 없으면 USB 전송 시작 */
    if (qdev->buf_off >= qdev->buf_len) {
        if (!wait) {
            mutex_unlock(&qdev->lock);
            return 0;
        }

        reinit_completion(&qdev->urb_done);
        usb_fill_bulk_urb(qdev->urb, qdev->udev,
                          usb_rcvbulkpipe(qdev->udev, QRNG_BULK_IN_EP),
                          qdev->buf, QRNG_BUFFER_SIZE,
                          qrng_urb_complete, qdev);
        qdev->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

        ret = usb_submit_urb(qdev->urb, GFP_KERNEL);
        if (ret) {
            mutex_unlock(&qdev->lock);
            return ret;
        }

        mutex_unlock(&qdev->lock);
        wait_for_completion(&qdev->urb_done);  /* USB 전송 완료 대기 */
        mutex_lock(&qdev->lock);

        if (qdev->buf_len == 0) {
            mutex_unlock(&qdev->lock);
            return -EIO;
        }
    }

    /* 버퍼에서 요청량만큼 복사 */
    available = qdev->buf_len - qdev->buf_off;
    to_copy   = min(available, max);
    memcpy(data, qdev->buf + qdev->buf_off, to_copy);
    qdev->buf_off += to_copy;

    mutex_unlock(&qdev->lock);
    return to_copy;
}

static int qrng_probe(struct usb_interface *interface,
                       const struct usb_device_id *id)
{
    struct qrng_dev *qdev;
    int ret;

    qdev = kzalloc(sizeof(*qdev), GFP_KERNEL);
    if (!qdev)
        return -ENOMEM;

    qdev->udev      = interface_to_usbdev(interface);
    qdev->interface = interface;
    mutex_init(&qdev->lock);
    init_completion(&qdev->urb_done);

    /* DMA 가능 버퍼 할당 */
    qdev->buf = usb_alloc_coherent(qdev->udev, QRNG_BUFFER_SIZE,
                                    GFP_KERNEL, &qdev->urb->transfer_dma);
    if (!qdev->buf) {
        ret = -ENOMEM;
        goto err_free;
    }

    qdev->urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!qdev->urb) {
        ret = -ENOMEM;
        goto err_free_buf;
    }

    /* hwrng 등록 */
    qdev->hwrng.name    = "qrng-quantum";
    qdev->hwrng.read    = qrng_read;
    qdev->hwrng.quality = QRNG_QUALITY;   /* 1024 — 양자 소스 */

    usb_set_intfdata(interface, qdev);
    ret = hwrng_register(&qdev->hwrng);
    if (ret) {
        dev_err(&interface->dev, "hwrng 등록 실패: %d\n", ret);
        goto err_free_urb;
    }

    dev_info(&interface->dev, "QRNG 연결됨 (quality=1024)\n");
    return 0;

err_free_urb:
    usb_free_urb(qdev->urb);
err_free_buf:
    usb_free_coherent(qdev->udev, QRNG_BUFFER_SIZE, qdev->buf,
                       qdev->urb->transfer_dma);
err_free:
    kfree(qdev);
    return ret;
}

static void qrng_disconnect(struct usb_interface *interface)
{
    struct qrng_dev *qdev = usb_get_intfdata(interface);

    qdev->disconnecting = true;
    hwrng_unregister(&qdev->hwrng);       /* read() 완료 후 반환 보장 */
    usb_kill_urb(qdev->urb);              /* 진행 중인 URB 취소 */
    usb_free_urb(qdev->urb);
    usb_free_coherent(qdev->udev, QRNG_BUFFER_SIZE, qdev->buf,
                       qdev->urb->transfer_dma);
    kfree(qdev);
    dev_info(&interface->dev, "QRNG 분리됨\n");
}

static const struct usb_device_id qrng_id_table[] = {
    { USB_DEVICE(QRNG_VENDOR_ID, QRNG_PRODUCT_ID) },
    {}
};
MODULE_DEVICE_TABLE(usb, qrng_id_table);

static struct usb_driver qrng_driver = {
    .name       = "qrng",
    .probe      = qrng_probe,
    .disconnect = qrng_disconnect,
    .id_table   = qrng_id_table,
};
module_usb_driver(qrng_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("USB QRNG hwrng driver");
MODULE_AUTHOR("Example Author");
코드 설명
  • QRNG_QUALITY = 1024 양자 소스는 원리적으로 예측 불가능하므로 최대 품질 1024를 사용합니다. 실제 하드웨어는 제조사 인증서(NIST SP 800-90B)를 확인한 후 설정하세요.
  • usb_fill_bulk_urb() USB Bulk 전송 요청을 초기화합니다. QRNG는 대량 바이트를 전송하므로 Bulk 엔드포인트가 적합합니다. 인터럽트 엔드포인트는 소량(64B 이하)에만 적합합니다.
  • hwrng_unregister() 순서 hwrng_unregister()는 진행 중인 read() 콜백이 완료될 때까지 블록합니다. 그 후 usb_kill_urb()를 호출해야 URB 사용 후 해제 버그를 피할 수 있습니다.
  • container_of() struct hwrngstruct qrng_dev에 내장되어 있으므로, container_of()로 부모 구조체 포인터를 얻습니다. 별도 할당보다 메모리 효율적입니다.

알려진 상용 QRNG 드라이버 위치

벤더/제품커널 소스 위치인터페이스quality
BCM2835 (라즈베리파이)drivers/char/hw_random/bcm2835-rng.cMMIO200
Intel RDRANDarch/x86/kernel/cpu/rdrand.cCPU 명령어1024
ARM TRNG (v8.5+)drivers/char/hw_random/arm-smccc-trng.cSMC 콜1024
TPM 2.0 RNGdrivers/char/tpm/tpm-chip.cTPM 커맨드질문에 따라
VirtIO RNG (KVM 게스트)drivers/char/hw_random/virtio-rng.cvirtqueue1024
EXYNOS (삼성)drivers/char/hw_random/exynos-trng.cMMIO + 인터럽트없음(기본)

PCIe QRNG 드라이버

PCIe 기반 QRNG는 USB보다 높은 처리율과 낮은 지연을 제공합니다. MMIO(Memory-Mapped I/O) BAR를 통해 직접 하드웨어 레지스터에 접근하고, MSI(Message Signaled Interrupt) 인터럽트로 데이터 준비를 통지받습니다.

#include <linux/module.h>
#include <linux/pci.h>
#include <linux/hw_random.h>
#include <linux/interrupt.h>
#include <linux/wait.h>

#define PCIE_QRNG_VENDOR    0x1234
#define PCIE_QRNG_DEVICE    0xABCD
#define QRNG_REG_STATUS     0x00   /* 상태 레지스터 */
#define QRNG_REG_DATA       0x04   /* 난수 데이터 레지스터 (32비트) */
#define QRNG_REG_FIFO_COUNT 0x08   /* FIFO 내 사용 가능 워드 수 */
#define QRNG_STATUS_READY   BIT(0) /* FIFO에 데이터 있음 */
#define QRNG_QUALITY        1024

struct pcie_qrng_dev {
    void __iomem        *mmio_base;   /* BAR0 MMIO 기주소 */
    struct hwrng         hwrng;        /* hwrng 서브시스템 핸들 */
    struct pci_dev      *pdev;
    wait_queue_head_t    wait_q;       /* 인터럽트 대기 큐 */
    atomic_t             data_ready;   /* MSI 수신 플래그 */
};

/* MSI 인터럽트 핸들러 — 하드웨어가 FIFO에 데이터 채웠을 때 호출 */
static irqreturn_t pcie_qrng_irq(int irq, void *dev_id)
{
    struct pcie_qrng_dev *qdev = dev_id;
    u32 status = readl(qdev->mmio_base + QRNG_REG_STATUS);

    if (!(status & QRNG_STATUS_READY))
        return IRQ_NONE;

    atomic_set(&qdev->data_ready, 1);
    wake_up_interruptible(&qdev->wait_q);  /* read()에서 대기 중인 스레드 깨움 */
    return IRQ_HANDLED;
}

/* hwrng read 콜백 — MMIO BAR 직접 읽기 + MSI 인터럽트 대기 */
static int pcie_qrng_read(struct hwrng *rng, void *data, size_t max, bool wait)
{
    struct pcie_qrng_dev *qdev = container_of(rng, struct pcie_qrng_dev, hwrng);
    size_t bytes = 0;
    u32 *out = data;
    int ret;

    while (bytes + sizeof(u32) <= max) {
        /* FIFO에 데이터가 없으면 MSI 인터럽트 대기 */
        if (!atomic_read(&qdev->data_ready)) {
            if (!wait)
                break;
            ret = wait_event_interruptible(qdev->wait_q,
                          atomic_read(&qdev->data_ready));
            if (ret)
                return bytes ? (int)bytes : -EINTR;
        }
        /* FIFO에서 32비트 읽기 */
        *out++ = readl(qdev->mmio_base + QRNG_REG_DATA);
        bytes += sizeof(u32);
        if (readl(qdev->mmio_base + QRNG_REG_FIFO_COUNT) == 0)
            atomic_set(&qdev->data_ready, 0);
    }
    return (int)bytes;
}

static int pcie_qrng_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    struct pcie_qrng_dev *qdev;
    int ret;

    qdev = devm_kzalloc(&pdev->dev, sizeof(*qdev), GFP_KERNEL);
    if (!qdev)
        return -ENOMEM;

    init_waitqueue_head(&qdev->wait_q);
    atomic_set(&qdev->data_ready, 0);
    qdev->pdev = pdev;

    /* PCIe 활성화 + DMA 마스크 설정 */
    ret = pcim_enable_device(pdev);
    if (ret)
        return dev_err_probe(&pdev->dev, ret, "PCIe 활성화 실패\n");

    ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
    if (ret)
        return dev_err_probe(&pdev->dev, ret, "DMA 마스크 설정 실패\n");

    /* BAR0 MMIO 매핑 (devm: 언로드 시 자동 해제) */
    qdev->mmio_base = pcim_iomap(pdev, 0, 0);
    if (IS_ERR(qdev->mmio_base))
        return PTR_ERR(qdev->mmio_base);

    /* MSI 인터럽트 1개 할당 */
    ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
    if (ret < 0)
        return dev_err_probe(&pdev->dev, ret, "MSI 할당 실패\n");

    ret = devm_request_irq(&pdev->dev, pci_irq_vector(pdev, 0),
                            pcie_qrng_irq, 0, "pcie-qrng", qdev);
    if (ret)
        return dev_err_probe(&pdev->dev, ret, "IRQ 등록 실패\n");

    /* hwrng 등록 */
    qdev->hwrng.name    = "pcie-qrng";
    qdev->hwrng.read    = pcie_qrng_read;
    qdev->hwrng.quality = QRNG_QUALITY;

    pci_set_drvdata(pdev, qdev);
    ret = devm_hwrng_register(&pdev->dev, &qdev->hwrng);
    if (ret)
        return dev_err_probe(&pdev->dev, ret, "hwrng 등록 실패\n");

    dev_info(&pdev->dev, "PCIe QRNG 등록 완료 (quality=%u)\n", QRNG_QUALITY);
    return 0;
}

static const struct pci_device_id pcie_qrng_ids[] = {
    { PCI_DEVICE(PCIE_QRNG_VENDOR, PCIE_QRNG_DEVICE) },
    {}
};
MODULE_DEVICE_TABLE(pci, pcie_qrng_ids);

static struct pci_driver pcie_qrng_driver = {
    .name     = "pcie-qrng",
    .id_table = pcie_qrng_ids,
    .probe    = pcie_qrng_probe,
};
module_pci_driver(pcie_qrng_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PCIe QRNG hwrng driver with MSI interrupt");
코드 설명
  • pcim_enable_device() devm 기반 PCIe 초기화 함수입니다. 드라이버 언로드 시 자동으로 pci_disable_device()를 호출합니다. pci_enable_device() + pci_set_master()를 자동 포함합니다.
  • pcim_iomap(pdev, 0, 0) BAR0을 전체 크기(0)로 MMIO 매핑합니다. devm 기반이라 언로드 시 자동 해제됩니다. 두 번째 인수 0은 BAR 인덱스, 세 번째 인수 0은 전체 크기를 의미합니다.
  • pci_alloc_irq_vectors(MSI) 레거시 INTx 대신 MSI를 요청합니다. MSI는 공유 인터럽트 선 없이 메시지 방식으로 동작해 지연이 낮고 공유 IRQ 문제가 없습니다.
  • wait_event_interruptible() 신호(SIGINT 등)에 의해 인터럽트될 수 있는 대기입니다. hwrng 코어 스레드가 이 콜백을 호출하므로 -EINTR 처리가 필요합니다.

NIST SP 800-90B 인증

NIST SP 800-90B는 하드웨어 엔트로피 소스(TRNG/QRNG)의 통계적 품질을 검증하는 미국 표준입니다. 커널 hwrng의 quality 파라미터는 이 표준의 min-entropy 측정값에서 직접 유도됩니다. FIPS 140-3 인증을 위한 필수 요건이기도 합니다.

min-entropy와 quality 파라미터 연관성

/* NIST SP 800-90B — min-entropy H_min 계산 및 quality 변환 */

/* 1단계: 가장 자주 나타나는 심볼의 확률 p_max 측정 */
/*        테스트 데이터 1,000,000비트에서 각 바이트값 출현 빈도 집계 */
double p_max = max_count / total_samples;  /* 이상적 QRNG: p_max ≈ 1/256 */

/* 2단계: min-entropy 계산 (비트 단위) */
double H_min = -log2(p_max);              /* 이상적: -log2(1/256) = 8.0 비트/바이트 */

/* 3단계: 비트당 min-entropy */
double H_min_per_bit = H_min / 8.0;       /* 이상적: 1.0 비트/비트 */

/* 4단계: hwrng quality 값으로 변환 (0~1024 스케일) */
unsigned short quality = (unsigned short)(H_min_per_bit * 1024);
/* 예시:
 *   H_min_per_bit = 1.000 → quality = 1024  (검증된 QRNG, 빔 분리기)
 *   H_min_per_bit = 0.990 → quality = 1014  (실용적 QRNG, 진공 요동)
 *   H_min_per_bit = 0.875 → quality =  896  (Intel RDRAND 실측치)
 *   H_min_per_bit = 0.500 → quality =  512  (FPGA 링 오실레이터)
 *   H_min_per_bit = 0.000 → quality =    0  (PRNG, 엔트로피 없음) */

NIST SP 800-90B 검증 6단계

단계검증 항목도구/방법판정 기준
1. 데이터 수집 1,000,000비트 원시 샘플 수집 dd if=/dev/hwrng bs=1M count=1 데이터 손실 없음
2. IID 검증 독립-동일 분포(IID) 여부 확인 ea_iid (NIST 공식 도구) IID 가설 기각 불가 시 IID 경로
3. min-entropy 추정 10가지 추정기로 최소값 결정 ea_non_iid H_min > 목표값 (예: 0.9 비트/비트)
4. 압축 검증 Markov/LZ 압축 테스트 내장 압축기 테스트 예측 가능 패턴 없음
5. 적응형 비율 검증 하드웨어 고장 감지 임계값 설정 Repetition Count, Adaptive Proportion 오경보율 α < 2⁻²⁰
6. 보고서 제출 독립 실험실 검증 및 CAVP 등록 NIST CAVP (Cryptographic Algorithm Validation Program) 인증서 발급

FIPS 140-3 요구사항 연관

FIPS 140-3 요구사항hwrng 구현관련 커널 코드
연속 RNG 테스트 (CRNGT) 연속 동일 출력 감지 hwrng_fillfn() 내 반복 감지
시작 업 테스트 (SURT) 초기화 시 통계 검증 hwrng_register() 호출 시
min-entropy ≥ 목표값 quality 파라미터 설정 struct hwrng.quality
조건부 예외 테스트 오류 시 드라이버 비활성화 hwrng_unregister()

암호 키 생성 완전 플로우

QRNG에서 생성된 엔트로피가 최종 암호 키로 변환되는 전체 흐름을 추적합니다. 커널 내부에서 여러 계층의 처리를 거쳐 안전한 키가 생성됩니다.

QRNG 하드웨어 빔분리기 / 진공요동 / TOAD 기타 엔트로피 소스 IRQ 지터 / 디스크 I/O / 키보드 hwrng 서브시스템 hwrng_fillfn() 커널 스레드 input_pool Blake2b 256비트 해시 상태 add_hwgenerator_randomness() ChaCha20-CRNG per-CPU 32바이트 키 상태 crng_reseed() 5분 주기 갱신 extract_entropy() AES 세션 키 (128/256비트) TLS 핸드셰이크 논스 ECDH 개인 키 (256비트) 디스크 암호화 마스터 키 SSH 호스트 키 쌍 IPSec SA 키 get_random_bytes() 키 소거 (Key Zeroization) — 사용 후 필수 memzero_explicit(key, sizeof(key)) — 컴파일러 최적화 무력화, 스택/힙 잔류 키 소거 kfree_sensitive(ptr) — 해제 전 자동 제로화, 슬랩 캐시 재사용 방지 Forward Secrecy — Fast Key Erasure 보장 crng_fast_key_erasure(): ChaCha20 출력 앞 32바이트를 즉시 새 키로 교체 → 메모리 탈취되어도 이전 난수 출력 역추적 불가 (Perfect Forward Secrecy)

암호 키 생성 3가지 방식

/* 방식 1: get_random_bytes() — 권장 (CRNG 경유, 항상 안전) */
static int generate_aes_key(u8 *key, size_t keylen)
{
    get_random_bytes(key, keylen);  /* CRNG 초기화 후 항상 블록하지 않음 */
    /* keylen = 16(AES-128), 24(AES-192), 32(AES-256) */
    return 0;
}

/* 사용 후 반드시 소거 */
memzero_explicit(key, keylen);   /* 스택 키: 컴파일러 최적화 무력화 */
kfree_sensitive(heap_key);       /* 힙 키: 해제 전 자동 제로화 */

/* 방식 2: /dev/hwrng 직접 읽기 — 사용자 공간 (원시 엔트로피, CRNG 미경유) */
#include <fcntl.h>
#include <unistd.h>

static int read_hwrng_key(unsigned char *key, size_t len)
{
    int fd = open("/dev/hwrng", O_RDONLY);
    if (fd < 0) return -1;
    ssize_t n = read(fd, key, len);
    close(fd);
    return (n == (ssize_t)len) ? 0 : -1;
    /* 주의: 추출기(extractor) 미적용 시 편향 가능, NIST 권장은 getrandom() */
}

/* 방식 3: rng->read() — 커널 드라이버 내부 직접 접근 */
static int kernel_rng_keygen(struct hwrng *rng, u8 *key, size_t len)
{
    int ret = rng->read(rng, key, len, true);
    if (ret != (int)len) return -EIO;
    /* 주의: 원시 엔트로피 — 반드시 KDF(HKDF/PBKDF2)로 처리 후 사용 */
    return 0;
}

테스트 및 검증

hwrng 드라이버를 로드한 후 다음 방법으로 동작을 검증합니다.

sysfs 인터페이스

# 현재 활성 hwrng 확인
cat /sys/class/misc/hw_random/rng_current
# 출력: myrng (또는 qrng-quantum)

# 사용 가능한 hwrng 목록
cat /sys/class/misc/hw_random/rng_available
# 출력: myrng virtio_rng intel-rng

# 활성 hwrng 변경 (우선순위: quality 높은 것이 자동 선택)
echo "myrng" > /sys/class/misc/hw_random/rng_current

# hwrng에서 직접 읽기 (원시 바이트)
dd if=/dev/hwrng bs=1 count=32 | xxd

rng-tools를 사용한 엔트로피 공급

# rng-tools 설치 (Ubuntu/Debian)
apt-get install rng-tools5

# rngd 데몬 실행 — /dev/hwrng → /dev/random 엔트로피 공급
rngd -r /dev/hwrng -o /dev/random -f

# 엔트로피 풀 현재 크기 확인 (레거시, 5.18+에서는 의미 감소)
cat /proc/sys/kernel/random/entropy_avail

# CRNG 초기화 완료 여부 확인 (dmesg)
dmesg | grep -E "crng|random"
# 정상: "random: crng init done"
# 경고: "random: get_random_u32 called from ... with crng_init=0" → 초기화 전 사용

rngtest — NIST SP 800-22 통계 검증

# NIST 통계 테스트 실행 (20000비트 × 200 블록)
cat /dev/hwrng | rngtest -c 200

# 예상 출력 (정상 QRNG):
# rngtest: bits received from input: 4000032
# rngtest: FIPS 140-2 success: 196
# rngtest: FIPS 140-2 failures: 4
# rngtest: bits discarded due to failures: 80032
# → 200블록 중 ~4개 실패는 통계적으로 정상 (5% 이내)

# 실패율이 너무 높으면 (10% 초과) 드라이버 또는 하드웨어 문제

# ENT (entropy estimation 도구)
dd if=/dev/hwrng bs=1M count=1 | ent
# Entropy: 7.9999 bits per byte → 이상적 QRNG
# Chi-square: 초과 확률 > 1%이면 양호

커널 내부 엔트로피 디버그

# CRNG 초기화 타임스탬프 확인
dmesg --ctime | grep "crng init"

# hwrng 스레드 동작 확인 (hw_random 코어가 kthread로 지속 수집)
ps aux | grep hwrng
# 출력: ... [hwrng] (커널 스레드)

# /proc/sys/kernel/random/ 파라미터
ls /proc/sys/kernel/random/
# boot_id        — 부팅 고유 UUID (변경 불가)
# uuid           — 읽을 때마다 새 UUID 생성
# entropy_avail  — 현재 엔트로피 추정값 (비트)
# poolsize       — 입력 풀 크기 (항상 256)
# urandom_min_reseed_secs — 재시드 최소 간격

# 엔트로피 소모 시뮬레이션 (테스트 전용)
dd if=/dev/urandom of=/dev/null bs=1M count=100 &
watch -n 0.5 'cat /proc/sys/kernel/random/entropy_avail'

엔트로피 기여량 실시간 모니터링

# hwrng 스레드 동작 및 엔트로피 기여량 실시간 모니터링
# (1초 간격으로 entropy_avail 변화 추적)
watch -n 1 'echo "=== $(date +%T) ===" ;
  echo "활성 hwrng: $(cat /sys/class/misc/hw_random/rng_current)";
  echo "엔트로피 추정: $(cat /proc/sys/kernel/random/entropy_avail) bits";
  echo "CRNG 재시드: $(cat /proc/sys/kernel/random/urandom_min_reseed_secs)s 간격"'

# hwrng 커널 스레드 CPU 사용률 확인
ps -eo pid,comm,pcpu | grep hwrng

# 1MB 샘플 수집 속도 측정 (처리율 벤치마크)
time dd if=/dev/hwrng of=/dev/null bs=4k count=256 2>&1
# 출력 예: 1048576 bytes copied, 0.08 s, 12.6 MB/s  ← PCIe QRNG
#          1048576 bytes copied, 1.05 s, 998 kB/s    ← USB QRNG

NIST SP 800-90B 오픈소스 검증 도구

# NIST SP 800-90B 공식 검증 도구 설치 (Python 3 기반)
git clone https://github.com/usnistgov/SP800-90B_EntropyAssessment.git
cd SP800-90B_EntropyAssessment
pip3 install -r requirements.txt

# 1,000,000바이트 원시 엔트로피 수집
dd if=/dev/hwrng bs=1M count=1 of=qrng_sample.bin

# IID 추정 (독립-동일 분포 가정)
python3 ./src/ea_iid.py qrng_sample.bin 8
# 출력: min-entropy estimate = 7.999 bits per symbol → quality=1023 적합

# Non-IID 추정 (보수적 추정, FIPS 140-3 요건)
python3 ./src/ea_non_iid.py qrng_sample.bin 8
# 10가지 추정기 중 최솟값이 실제 min-entropy
# 출력 예: min-entropy = 7.998 → H_min_per_bit = 0.9998 → quality=1023

# 재시작 테스트 (Restart Test) — 하드웨어 초기 조건 의존성 검증
python3 ./src/ea_restart.py qrng_restart.bin 8 1000
# 1000회 재시작 × 1000샘플 = 1,000,000바이트 필요

dieharder 통계 배터리 전체 실행

# dieharder 설치
apt-get install dieharder   # Ubuntu/Debian
dnf install dieharder       # Fedora/RHEL

# 전체 배터리 실행 (114개 통계 검정, 수 시간 소요)
cat /dev/hwrng | dieharder -a -g 200
# -a: 모든 테스트, -g 200: stdin 입력
# 결과: PASSED/WEAK/FAILED — WEAK는 5% 이내 정상

# 특정 테스트만 실행 (빠른 검증)
cat /dev/hwrng | dieharder -d 0 -g 200    # Birthday Spacings
cat /dev/hwrng | dieharder -d 15 -g 200   # RGB Bit Distribution
cat /dev/hwrng | dieharder -d 100 -g 200  # STS Monobit

# ENT 도구 — 빠른 엔트로피 추정
dd if=/dev/hwrng bs=1M count=1 | ent
# Entropy: 7.9999 bits per byte  → 이상적 QRNG
# Chi-square: 적합 확률 50%      → 정상 분포
# Arithmetic mean: 127.49        → 균등 분포 (이론: 127.5)
# Serial correlation: 0.000001  → 독립성 확인

드라이버 로드/언로드 검증 체크리스트

단계확인 명령정상 기대값
드라이버 로드 insmod myrng.ko && dmesg | tail -5 "myrng 등록 완료" 메시지
hwrng 인식 cat /sys/class/misc/hw_random/rng_available 목록에 "myrng" 포함
난수 읽기 dd if=/dev/hwrng bs=16 count=1 | xxd 16바이트 난수 출력
엔트로피 기여 watch cat /proc/sys/kernel/random/entropy_avail 값이 증가
NIST 테스트 cat /dev/hwrng | rngtest -c 100 실패율 5% 이하
드라이버 언로드 rmmod myrng && dmesg | tail -3 정상 언로드 메시지, 크래시 없음

엔트로피 풀 구조 심층 분석

Linux 커널의 엔트로피 풀은 drivers/char/random.c에 구현된 핵심 난수 인프라의 심장부입니다. 커널 5.18 이전에는 input_poolblocking_pool 두 개의 풀이 존재했지만, 현대 커널에서는 Blake2s 기반의 단일 input_pool로 통합되었습니다. 이 섹션에서는 풀의 내부 구조, 믹싱 알고리즘, 엔트로피 추정 메커니즘을 심층적으로 분석합니다.

풀 구조의 진화: LFSR에서 Blake2s로

역사적 배경: Linux 4.x까지 엔트로피 풀은 LFSR(Linear Feedback Shift Register) 기반 믹싱 함수를 사용했습니다. 커밋 c1ea38a5e7c(v5.17)에서 Blake2s 해시 함수로 완전 교체되었으며, 이로써 풀 크기가 4096비트(512바이트)에서 256비트(32바이트)로 축소되었지만 암호학적 강도는 크게 향상되었습니다.
항목레거시 (5.17 이전)현대 (5.18+)
풀 구조input_pool + blocking_pool (2개)input_pool 단일
믹싱 함수LFSR (비트 연산 기반)Blake2s (암호학적 해시)
풀 크기4096비트 (512바이트)256비트 (32바이트 해시 상태)
추출 함수SHA-1 기반 extractBlake2s final + re-absorb
엔트로피 카운터정수 비트 카운터 (정밀)init_bits 256비트 목표
/dev/random 블록킹엔트로피 고갈 시 블록CRNG 초기화 전에만 블록
forward secrecy미보장 (SHA-1 출력 재사용)Fast Key Erasure 보장

믹싱 알고리즘 상세

/* drivers/char/random.c — 현대 커널 엔트로피 풀 구조 */

/* 전역 엔트로피 풀 — Blake2s 해시 상태 */
static struct {
    struct blake2s_state hash;      /* 256비트 내부 상태 */
    spinlock_t lock;                /* 동시 접근 보호 */
    unsigned int init_bits;         /* 누적 엔트로피 비트 수 */
} input_pool = {
    .hash.h = { BLAKE2S_IV0 ^ (0x01010000 | 32),
                BLAKE2S_IV1, BLAKE2S_IV2, BLAKE2S_IV3,
                BLAKE2S_IV4, BLAKE2S_IV5, BLAKE2S_IV6, BLAKE2S_IV7 },
    .hash.outlen = 32,
    .lock = __SPIN_LOCK_UNLOCKED(input_pool.lock),
};

/* 믹싱 함수 — 엔트로피를 풀에 혼합 (비가역적) */
static void _mix_pool_bytes(const void *buf, size_t len)
{
    unsigned long flags;

    spin_lock_irqsave(&input_pool.lock, flags);
    blake2s_update(&input_pool.hash, buf, len);
    spin_unlock_irqrestore(&input_pool.lock, flags);
}

/* 엔트로피 추출 — 풀 상태를 변경하며 32바이트 키 생성 */
static void extract_entropy(void *buf, size_t len)
{
    unsigned long flags;
    u8 hash[BLAKE2S_HASH_SIZE];
    struct blake2s_state state;

    spin_lock_irqsave(&input_pool.lock, flags);

    /* 1. 현재 풀 상태의 스냅샷 생성 */
    state = input_pool.hash;

    /* 2. 스냅샷에서 최종 해시 계산 (256비트) */
    blake2s_final(&state, hash);

    /* 3. 출력 해시를 풀에 재혼합 → backtrack resistance */
    blake2s_update(&input_pool.hash, hash, sizeof(hash));

    spin_unlock_irqrestore(&input_pool.lock, flags);

    memcpy(buf, hash, len);
    memzero_explicit(hash, sizeof(hash));
    memzero_explicit(&state, sizeof(state));
}

/* 엔트로피 크레딧 — init_bits 누적 (256비트 목표) */
static void credit_init_bits(size_t bits)
{
    unsigned int new_bits, orig;
    unsigned long flags;

    if (!bits)
        return;

    spin_lock_irqsave(&input_pool.lock, flags);
    orig = input_pool.init_bits;
    new_bits = min_t(unsigned int, POOL_READY_BITS,
                     orig + bits);  /* 최대 256비트까지만 */
    WRITE_ONCE(input_pool.init_bits, new_bits);
    spin_unlock_irqrestore(&input_pool.lock, flags);

    /* 256비트 도달 시 CRNG 초기화 트리거 */
    if (orig < POOL_READY_BITS && new_bits >= POOL_READY_BITS)
        crng_reseed();
}
엔트로피 풀 내부 구조 및 믹싱 흐름 hwrng 드라이버 IRQ 타이밍 지터 디스크/네트워크 I/O 키보드/마우스 이벤트 add_hwgenerator_randomness() add_interrupt_randomness() add_disk_randomness() add_input_randomness() input_pool (Blake2s) h[0..7]: 256비트 해시 상태 t[0..1]: 바이트 카운터 buf[64]: 입력 버퍼 init_bits: 0 → 256 (CRNG) _mix_pool_bytes() extract_entropy() Blake2s final + re-absorb 재혼합 (backtrack resistance) ChaCha20-CRNG (per-CPU) 32바이트 키 + Fast Key Erasure crng_reseed() /dev/urandom getrandom() hwrng (하드웨어) 소프트웨어 엔트로피 핵심 풀 상태 재혼합 (비가역)

엔트로피 추정과 크레딧 정책

커널은 각 소스별로 다른 엔트로피 크레딧 정책을 적용합니다. hwrng의 경우 quality 파라미터가 직접적으로 크레딧 계산에 사용됩니다.

엔트로피 소스크레딧 정책기여 조건최대 기여량
hwrng bytes × quality × 8 / 1024 hwrng_fillfn() 스레드 주기적 호출 quality 의존 (최대 무제한)
인터럽트 지터 fast_pool 64회마다 1비트 TSC/사이클 카운터 차이 초당 수십 비트
디스크 I/O 타이밍 델타 기반 CONFIG_RANDOM_TRUST_BOOTLOADER I/O 빈도에 비례
입력 이벤트 이벤트 타이밍 + 코드 키보드/마우스 존재 시 서버에서는 거의 0
CPU RDRAND CONFIG_RANDOM_TRUST_CPU=y 시 256비트 부팅 초기 즉시 256비트 (일회성)

RDSEED vs RDRAND 비교 분석

Intel x86 프로세서는 두 가지 하드웨어 난수 명령어를 제공합니다: RDRAND(Ivy Bridge, 2012~)와 RDSEED(Broadwell, 2014~). 두 명령어는 같은 하드웨어 엔트로피 소스(열잡음 기반 디지털 난수 생성기)를 공유하지만, 출력 방식과 암호학적 보증이 근본적으로 다릅니다.

RDRAND 내부 동작

/* arch/x86/include/asm/archrandom.h — RDRAND 인라인 어셈블리 */

static inline bool rdrand_long(unsigned long *v)
{
    bool ok;
    unsigned int retry = RDRAND_RETRY_LOOPS;  /* 기본 10회 */

    do {
        asm volatile(RDRAND_LONG
                     "\n\t"
                     CC_SET(c)      /* CF=1이면 유효한 난수 */
                     : CC_OUT(c)(ok), "=a"(*v));
        if (ok)
            return true;
    } while (--retry);
    return false;  /* DRNG 내부 고갈 — 매우 드뭄 */
}

/* RDSEED — 순수 하드웨어 엔트로피 (DRBG 미경유) */
static inline bool rdseed_long(unsigned long *v)
{
    bool ok;
    asm volatile(RDSEED_LONG
                 "\n\t"
                 CC_SET(c)      /* CF=1이면 유효한 시드 */
                 : CC_OUT(c)(ok), "=a"(*v));
    return ok;  /* 실패 확률이 RDRAND보다 높음 */
}

/* 커널 엔트로피 수집에서의 사용 — random.c */
static void try_to_generate_entropy(void)
{
    unsigned long v;

    /* RDSEED 우선 시도 (순수 엔트로피) */
    if (rdseed_long(&v)) {
        _mix_pool_bytes(&v, sizeof(v));
        credit_init_bits(sizeof(v) * 8);  /* 64비트 크레딧 */
        return;
    }

    /* RDSEED 실패 시 RDRAND 폴백 (DRBG 경유) */
    if (rdrand_long(&v)) {
        _mix_pool_bytes(&v, sizeof(v));
        /* CONFIG_RANDOM_TRUST_CPU=y 설정 시에만 크레딧 부여 */
    }
}
Intel RDRAND vs RDSEED 아키텍처 비교 열잡음 디지털 RNG (ES, Entropy Source) 실리콘 트랜지스터 열적 요동 → 비결정적 비트 생성 RDSEED 경로 (순수 엔트로피) ES 컨디셔너 (AES-CBC-MAC) RDSEED 출력 (64비트) DRBG 미경유 → 원시 시드 실패 확률 높음 (ES 고갈 시) 용도: DRBG 시드, 커널 풀 시딩 처리율: ~70 MB/s (변동) 엔트로피 크레딧: 64비트/호출 RDRAND 경로 (DRBG 출력) CTR-DRBG (AES-256-CTR) RDRAND 출력 (64비트) DRBG 경유 → 결정론적 확장 실패 확률 매우 낮음 (버퍼링) 용도: 빠른 난수, ASLR, 스택 카나리 처리율: ~500 MB/s (일정) 엔트로피 크레딧: 조건부 직접 출력 시드 공급 커널 권장: RDSEED 우선 → 실패 시 RDRAND 폴백 (try_to_generate_entropy)

RDRAND vs RDSEED 상세 비교표

항목RDRANDRDSEED
도입 시기Ivy Bridge (2012, 3세대)Broadwell (2014, 5세대)
CPUID 플래그CPUID.01H:ECX.RDRAND[30]CPUID.07H:EBX.RDSEED[18]
엔트로피 출처CTR-DRBG (AES-256-CTR) 출력ES 컨디셔너 직접 출력
NIST 준수SP 800-90A (DRBG)SP 800-90B (Entropy Source)
실패 확률매우 낮음 (내부 버퍼링)상대적으로 높음 (ES 직접)
처리율 (Skylake)~500 MB/s~70 MB/s (변동 있음)
예측 저항성재시드 간격 내 예측 가능매 출력마다 독립
커널 사용처보조 엔트로피 믹싱input_pool 초기 시딩
지연(latency)~400 사이클~800 사이클 (변동)
AMD 명칭동일 (Zen+)동일 (Zen 2+)
보안 주의: RDRAND/RDSEED는 CPU 마이크로코드 수준에서 구현되어 독립적 검증이 어렵습니다. 커널의 CONFIG_RANDOM_TRUST_CPU 옵션이 n이면 RDRAND 출력을 엔트로피로 신뢰하지 않고, 믹싱만 수행합니다. nordrand 부트 파라미터로 RDRAND 사용을 완전히 비활성화할 수 있습니다.

FIPS 140-2/140-3 인증과 커널 RNG

FIPS 140(Federal Information Processing Standard 140)은 미국 연방 정부가 요구하는 암호 모듈 보안 표준입니다. Linux 커널은 CONFIG_CRYPTO_FIPS 옵션과 fips=1 부트 파라미터를 통해 FIPS 모드를 지원하며, 이 모드에서는 RNG 관련 추가 요구사항이 활성화됩니다.

FIPS 모드 커널 설정

/* crypto/fips.c — FIPS 모드 전역 플래그 */
int fips_enabled;
EXPORT_SYMBOL_GPL(fips_enabled);

/* 부팅 파라미터: fips=1 → FIPS 모드 활성화 */
static int __init fips_enable(char *str)
{
    fips_enabled = simple_strtol(str, NULL, 0);
    printk(KERN_INFO "fips mode is %s\n",
           fips_enabled ? "enabled" : "disabled");
    return 1;
}
__setup("fips=", fips_enable);

/* FIPS 모드에서의 RNG 셀프 테스트 */
/* crypto/testmgr.c — alg_test_drbg() */
static int alg_test_drbg(const struct alg_test_desc *desc,
                          const char *driver, u32 type, u32 mask)
{
    int err = 0;
    int i;
    const struct drbg_testvec *tv;

    /* 알려진 응답 테스트(KAT): 고정 시드 → 고정 출력 비교 */
    for (i = 0; i < desc->suite.drbg.count; i++) {
        tv = &desc->suite.drbg.vecs[i];
        err = drbg_cavs_test(tv, desc->alg_common.cra_driver_name);
        if (err) {
            pr_err("DRBG KAT 실패 #%d: %s\n", i, driver);
            return err;
        }
    }
    return 0;
}
FIPS 140 인증 프로세스와 커널 RNG FIPS 140-2 (레거시, 2001~2026) 셀프 테스트: 전원 투입 시 Known Answer Test 연속 RNG 테스트: 연속 동일 출력 감지 통계 테스트: Monobit, Poker, Runs, Long Runs DRBG: SP 800-90A (CTR-DRBG, Hash-DRBG) 엔트로피 소스: SP 800-90B 필수 아님 보안 레벨: 1~4 (Level 1 = 소프트웨어만) 폐지 예정: 2026년 9월 이후 신규 인증 불가 FIPS 140-3 (현행, 2019~) 셀프 테스트: 사전 운용 + 조건부 테스트 CRNGT: 연속 난수 생성 테스트 강화 건강 테스트: Repetition Count + Adaptive Proportion DRBG: SP 800-90A Rev.1 (예측 저항 필수) 엔트로피 소스: SP 800-90B 필수 보안 레벨: 1~4 (ISO 19790 정렬) Linux: CONFIG_CRYPTO_FIPS + fips=1 부팅 전환 커널 부팅 시 FIPS 셀프 테스트 순서 (fips=1) 1. 무결성 검증 HMAC-SHA256 커널 이미지 검증 2. 암호 알고리즘 KAT AES, SHA, HMAC Known Answer Test 3. DRBG 셀프 테스트 CTR-DRBG KAT alg_test_drbg() 4. RNG 연속 테스트 CRNGT 활성화 연속 동일 출력 감지 모든 테스트 통과 → 정상 부팅 계속 테스트 실패 → kernel panic() 호출 FIPS 모드에서는 인증되지 않은 암호 알고리즘 사용이 차단됩니다 (crypto_unregister_alg 호출) Red Hat, Ubuntu Pro, SUSE SLE 등 상용 배포판이 FIPS 인증된 커널 패키지를 제공합니다

FIPS DRBG 요구사항과 커널 구현 매핑

FIPS 요구사항SP 800-90A 조항커널 구현코드 위치
시드 소스 품질 Section 8.6.1 hwrng quality >= 256 drivers/char/hw_random/core.c
재시드 주기 Section 9.3.2 5분 또는 2^48 블록 drivers/char/random.c crng_reseed()
예측 저항 Section 11.3 Fast Key Erasure crng_fast_key_erasure()
상태 소거 Section 11.4 memzero_explicit() 모든 키 사용 후 호출
건강 테스트 Section 11.3.3 연속 출력 비교 hwrng_fillfn()

HW RNG 드라이버 구조 심화

커널의 hwrng 서브시스템(drivers/char/hw_random/)은 다양한 하드웨어 RNG를 통일된 프레임워크로 관리합니다. 이 섹션에서는 코어 프레임워크의 내부 동작, 등록/해제 흐름, 주요 드라이버(virtio-rng, TPM RNG, Intel DRNG)의 구현 차이를 분석합니다.

hwrng 코어 프레임워크 내부

/* drivers/char/hw_random/core.c — hwrng_register() 흐름 */

int hwrng_register(struct hwrng *rng)
{
    int err = -EINVAL;
    struct hwrng *tmp;
    bool is_new_current = false;

    /* 필수 콜백 검증: read() 또는 data_read() 중 하나 필수 */
    if (!rng->name || (!rng->data_read && !rng->read))
        return -EINVAL;

    mutex_lock(&rng_mutex);

    /* 이름 중복 검사 */
    list_for_each_entry(tmp, &rng_list, list) {
        if (strcmp(tmp->name, rng->name) == 0) {
            err = -EEXIST;
            goto out_unlock;
        }
    }

    /* 초기화 콜백 호출 (있으면) */
    if (rng->init) {
        err = rng->init(rng);
        if (err)
            goto out_unlock;
    }

    kref_init(&rng->ref);
    init_completion(&rng->cleanup_done);
    init_completion(&rng->dying);

    /* 우선순위: quality가 높은 드라이버가 current_rng */
    if (!current_rng || rng->quality > current_rng->quality) {
        current_rng = rng;
        is_new_current = true;
    }

    list_add_tail(&rng->list, &rng_list);

    /* hwrng_fillfn 스레드 시작 (최초 등록 시) */
    if (is_new_current || !hwrng_fill)
        start_khwrngd();

    mutex_unlock(&rng_mutex);
    return 0;

out_unlock:
    mutex_unlock(&rng_mutex);
    return err;
}
hwrng 코어 프레임워크 구조 intel-rng quality=1024 virtio-rng quality=1024 tpm-rng quality=0~1024 bcm2835-rng quality=200 사용자 QRNG quality=설정값 hwrng_register() 이름 검증 + 초기화 quality 비교 → current rng_list (연결 리스트) intel-rng (current_rng) virtio-rng tpm-rng, bcm2835, ... [hwrng] kthread hwrng_fillfn() current_rng->read() 주기 호출 4096바이트씩 수집 get_current_rng() /dev/hwrng (misc 10,183) 사용자 공간 직접 읽기 rng_dev_read() → rng->read() sysfs 인터페이스 rng_current: 활성 드라이버 rng_available: 등록 목록 rng_quality: quality 값 조회 input_pool add_hwgenerator_randomness() 주요 hwrng 드라이버 비교 드라이버 인터페이스 read() 구현 특이사항 처리율 intel-rng RDRAND/RDSEED CPU 명령어 직접 CONFIG_RANDOM_TRUST_CPU 500 MB/s virtio-rng virtqueue I/O 호스트에서 공급 KVM/QEMU 게스트 필수 호스트 의존 tpm-rng TPM 커맨드 TPM2_GetRandom 느리지만 독립 하드웨어 ~1 MB/s bcm2835 MMIO 레지스터 FIFO 폴링 라즈베리파이 전용 ~10 MB/s exynos-trng MMIO + IRQ 인터럽트 대기 삼성 Exynos SoC ~5 MB/s

virtio-rng 드라이버 상세

가상화 환경에서 게스트 OS의 엔트로피 부족은 심각한 보안 문제를 야기합니다. virtio-rng 드라이버는 호스트의 /dev/urandom에서 생성된 난수를 virtqueue를 통해 게스트에 공급합니다.

/* drivers/char/hw_random/virtio-rng.c — 핵심 구조 */

struct virtrng_info {
    struct hwrng          hwrng;
    struct virtqueue     *vq;        /* virtio 큐 */
    struct completion     have_data;  /* 데이터 수신 완료 */
    unsigned int          data_avail; /* 사용 가능 바이트 */
    unsigned int          data_idx;   /* 현재 읽기 인덱스 */
    u8                   *data;      /* 수신 버퍼 */
    bool                  busy;
    bool                  hwrng_removed;
};

/* virtio 콜백 — 호스트에서 데이터 도착 */
static void random_recv_done(struct virtqueue *vq)
{
    struct virtrng_info *vi = vq->vdev->priv;

    vi->data_avail = virtqueue_get_buf(vq, &vi->data_avail);
    complete(&vi->have_data);
}

/* hwrng read — virtqueue에서 난수 수신 */
static int virtio_read(struct hwrng *hwrng, void *buf,
                        size_t size, bool wait)
{
    struct virtrng_info *vi = (struct virtrng_info *)hwrng->priv;
    int ret;

    if (!vi->busy) {
        vi->busy = true;
        reinit_completion(&vi->have_data);
        register_buffer(vi);  /* virtqueue에 수신 버퍼 등록 */
    }

    if (!wait)
        return 0;

    ret = wait_for_completion_killable(&vi->have_data);
    if (ret < 0)
        return ret;

    vi->busy = false;
    memcpy(buf, vi->data, vi->data_avail);
    return vi->data_avail;
}
QEMU 설정: -device virtio-rng-pci,max-bytes=1024,period=1000으로 가상 RNG를 추가합니다. max-bytesperiod(ms)로 게스트에 공급하는 난수 대역폭을 제한할 수 있습니다. -object rng-random,filename=/dev/urandom,id=rng0으로 호스트 소스를 지정합니다.

getrandom() 시스템 콜 심층 분석

getrandom()(syscall #318, x86-64)은 Linux 3.17에서 도입된 현대적 난수 획득 인터페이스입니다. 파일 디스크립터가 필요 없고, CRNG 초기화 상태를 정확히 반영하며, /dev/urandom의 초기 부팅 시 안전하지 않은 난수 반환 문제를 해결합니다.

커널 내부 실행 경로

/* drivers/char/random.c — getrandom() 시스템 콜 구현 */

SYSCALL_DEFINE3(getrandom, char __user *, ubuf,
                size_t, len, unsigned int, flags)
{
    struct iov_iter iter;
    int ret;

    /* 플래그 유효성 검사 */
    if (flags & ~(GRND_NONBLOCK | GRND_RANDOM | GRND_INSECURE))
        return -EINVAL;

    /* 크기 제한: INT_MAX (사실상 무제한) */
    len = min_t(size_t, len, INT_MAX);

    /* GRND_INSECURE: CRNG 초기화 전에도 즉시 반환 (안전하지 않음) */
    if (flags & GRND_INSECURE)
        goto insecure;

    /* CRNG 초기화 대기 */
    if (!crng_ready()) {
        if (flags & GRND_NONBLOCK)
            return -EAGAIN;  /* 즉시 반환: 아직 준비 안 됨 */

        /* 블록: CRNG 초기화(256비트 엔트로피 수집) 완료까지 대기 */
        ret = wait_for_random_bytes();
        if (ret)
            return ret;  /* -EINTR: 시그널에 의해 인터럽트 */
    }

insecure:
    /* ChaCha20-CRNG에서 난수 생성 → 사용자 공간으로 복사 */
    import_ubuf(ITER_DEST, ubuf, len, &iter);
    ret = get_random_bytes_user(&iter);

    return ret;
}

/* wait_for_random_bytes() — CRNG 초기화 대기 */
int wait_for_random_bytes(void)
{
    while (!crng_ready()) {
        int ret;
        /* jitterentropy 보조 시딩 시도 */
        try_to_generate_entropy();
        ret = wait_event_interruptible_timeout(
                    crng_init_wait, crng_ready(), HZ);
        if (ret)
            return ret > 0 ? 0 : ret;
    }
    return 0;
}
getrandom() 시스템 콜 실행 흐름 getrandom(buf, len, flags) syscall #318 flags 검사 GRND_INSECURE 즉시 반환 (안전하지 않음) CRNG 초기화 무시 crng_ready()? 아니오 GRND_NONBLOCK 검사 NONBLOCK=1 → -EAGAIN 반환 NONBLOCK=0 → 블록 대기 wait_for_random_bytes() ChaCha20-CRNG 난수 생성 get_random_bytes_user() → copy_to_user() len 바이트 반환 flags=0 CRNG 준비 전 블록 권장 기본값 GRND_NONBLOCK 미준비 시 -EAGAIN 비동기 처리용 GRND_RANDOM /dev/random 에뮬레이션 레거시, 권장 안 함 GRND_INSECURE 항상 즉시 반환 보안 용도 부적합

부팅 초기 블로킹 문제와 대응

부팅 초기 엔트로피 기근: 헤드리스 서버, 임베디드 장치, 컨테이너에서는 키보드/마우스 입력이 없고, 디스크 I/O가 적어 CRNG 초기화(256비트)에 수십 초가 걸릴 수 있습니다. 이 기간 동안 getrandom()은 블록됩니다.
환경CRNG 초기화 시간원인해결책
물리 서버 (hwrng 있음) < 1초 hwrng가 즉시 엔트로피 공급 기본 설정으로 충분
물리 서버 (hwrng 없음) 1~5초 인터럽트 지터 + 디스크 I/O jitterentropy-rngd 설치
KVM 가상머신 1~30초 가상화된 타이머 정밀도 감소 virtio-rng 장치 추가
Docker 컨테이너 호스트 의존 호스트 CRNG 공유 호스트의 hwrng 보장
임베디드/IoT 10초~수 분 최소 하드웨어, 입력 없음 SoC 내장 TRNG 드라이버 활성화
# 부팅 초기 블로킹 진단
dmesg | grep -E "crng|random|getrandom"
# "random: crng init done" 메시지 확인 (타임스탬프 = 초기화 소요 시간)

# 커널 명령줄로 엔트로피 소스 신뢰 설정
# random.trust_cpu=1     — RDRAND 출력을 엔트로피로 신뢰
# random.trust_bootloader=1 — 부트로더 제공 시드 신뢰

# systemd-random-seed.service: 이전 부팅의 엔트로피 저장/복원
systemctl status systemd-random-seed.service
# 저장 위치: /var/lib/systemd/random-seed (32바이트)

ChaCha20 CRNG 내부 심층 분석

Linux 커널의 CRNG(Cryptographically secure Random Number Generator)는 Daniel Bernstein의 ChaCha20 스트림 암호를 핵심으로 사용합니다. 이 섹션에서는 per-CPU CRNG 구조, 리시드 메커니즘, NUMA 최적화, Fast Key Erasure 기법을 상세히 분석합니다.

ChaCha20 블록 함수 상세

/* lib/crypto/chacha20-generic.c — ChaCha20 핵심 */

/* ChaCha20 상태: 4×4 = 16개의 32비트 워드 (512비트 = 64바이트) */
/*
 * 상태 레이아웃:
 *   cccccccc  cccccccc  cccccccc  cccccccc   ← 상수 "expand 32-byte k"
 *   kkkkkkkk  kkkkkkkk  kkkkkkkk  kkkkkkkk   ← 키 (256비트, 8워드)
 *   kkkkkkkk  kkkkkkkk  kkkkkkkk  kkkkkkkk
 *   bbbbbbbb  nnnnnnnn  nnnnnnnn  nnnnnnnn   ← 블록 카운터 + 논스
 */

static void chacha20_block_generic(u32 *state, u8 *stream)
{
    u32 x[16];
    int i;

    memcpy(x, state, 64);

    /* 20라운드 (10 double-round) */
    for (i = 0; i < 10; i++) {
        /* 열(column) 라운드 */
        QUARTERROUND(x[0], x[4], x[8],  x[12]);
        QUARTERROUND(x[1], x[5], x[9],  x[13]);
        QUARTERROUND(x[2], x[6], x[10], x[14]);
        QUARTERROUND(x[3], x[7], x[11], x[15]);

        /* 대각선(diagonal) 라운드 */
        QUARTERROUND(x[0], x[5], x[10], x[15]);
        QUARTERROUND(x[1], x[6], x[11], x[12]);
        QUARTERROUND(x[2], x[7], x[8],  x[13]);
        QUARTERROUND(x[3], x[4], x[9],  x[14]);
    }

    /* 최종 더하기: 원본 상태와 XOR → 역연산 방지 */
    for (i = 0; i < 16; i++)
        x[i] += state[i];

    memcpy(stream, x, 64);  /* 64바이트 키스트림 출력 */
    state[12]++;             /* 블록 카운터 증가 */
}

/* Quarter Round 매크로 — ChaCha20의 핵심 연산 */
#define QUARTERROUND(a, b, c, d) \
    a += b; d ^= a; d = rol32(d, 16); \
    c += d; b ^= c; b = rol32(b, 12); \
    a += b; d ^= a; d = rol32(d, 8);  \
    c += d; b ^= c; b = rol32(b, 7)
ChaCha20 CRNG: per-CPU 구조와 리시드 흐름 input_pool (Blake2s) 엔트로피 수집 + 혼합 base_crng (전역) key[32]: 마스터 키 generation: 리시드 세대 extract_entropy() 리시드 조건 5분 경과 (CRNG_RESEED_INTERVAL) 또는 충분한 엔트로피 누적 per-CPU CRNG 인스턴스 (NUMA 최적화) CPU 0 (Node 0) key[32]: per-CPU 키 generation: 세대 동기 local_lock: 경량 락 No lock contention CPU 1 (Node 0) key[32]: per-CPU 키 generation: 세대 동기 local_lock: 경량 락 No lock contention CPU 2 (Node 1) key[32]: per-CPU 키 generation: 세대 동기 local_lock: 경량 락 NUMA-local 접근 CPU N (Node M) key[32]: per-CPU 키 generation: 세대 동기 local_lock: 경량 락 ... 세대 불일치 시 키 갱신 Fast Key Erasure (crng_fast_key_erasure) 앞 32바이트 → 새 키로 교체 뒤 32바이트 → 난수 출력 이전 키를 즉시 덮어쓰므로 메모리 탈취 시에도 과거 출력 역추적 불가 → Perfect Forward Secrecy + Information-theoretic security

NUMA per-node CRNG 최적화

/* drivers/char/random.c — per-CPU CRNG 키 갱신 로직 */

/* 난수 요청 시 per-CPU CRNG 세대 확인 */
static void crng_make_state(u32 chacha_state[CHACHA_STATE_WORDS],
                             u8 *random_data, size_t random_data_len)
{
    unsigned long flags;
    struct crng *crng;

    /* preempt 비활성화 + per-CPU CRNG 접근 */
    local_lock_irqsave(&crngs.lock, flags);
    crng = raw_cpu_ptr(&crngs);

    /* 세대(generation) 비교 → 불일치 시 base_crng에서 새 키 복사 */
    if (unlikely(crng->generation != READ_ONCE(base_crng.generation))) {
        spin_lock(&base_crng.lock);
        memcpy(crng->key, base_crng.key, sizeof(crng->key));
        crng->generation = base_crng.generation;
        spin_unlock(&base_crng.lock);
    }

    /* per-CPU 키로 ChaCha20 상태 초기화 */
    chacha_init_consts(chacha_state);
    memcpy(&chacha_state[4], crng->key, CHACHA20_KEY_SIZE);
    memset(&chacha_state[14], 0, 8);  /* 논스 = 0 */
    ++chacha_state[12];               /* 블록 카운터 증가 */

    /* Fast Key Erasure: 출력의 앞 32바이트 → 새 키 */
    crng_fast_key_erasure(crng->key, chacha_state,
                          random_data, random_data_len);

    local_unlock_irqrestore(&crngs.lock, flags);
}
성능 이점: per-CPU CRNG 구조 덕분에 get_random_bytes()는 다른 CPU와 락 경합 없이 동작합니다. NUMA 시스템에서 원격 노드 메모리 접근도 발생하지 않아 1000만+ 호출/초 처리율을 달성합니다.

엔트로피 소스 완전 가이드

Linux 커널은 다양한 엔트로피 소스를 수집하여 input_pool에 혼합합니다. 각 소스는 서로 다른 품질과 수집 속도를 가지며, 이들의 조합으로 전체 엔트로피 품질을 보장합니다. 이 섹션에서는 각 소스의 동작 원리, 수집 경로, 환경별 가용성을 분석합니다.

인터럽트 타이밍 지터

인터럽트 타이밍은 가장 풍부한 소프트웨어 엔트로피 소스입니다. 하드웨어 인터럽트(IRQ) 도착 시각의 나노초 단위 변동(지터)은 CPU 캐시 상태, 메모리 접근 패턴, 버스 경합 등에 의해 결정되며 외부에서 예측하기 매우 어렵습니다.

/* drivers/char/random.c — 인터럽트 엔트로피 수집 상세 */

/* per-CPU 고속 풀 — 인터럽트 컨텍스트에서 락 없이 동작 */
struct fast_pool {
    union {
        u32 pool32[4];    /* 128비트 풀 (SipHash 스타일) */
        u64 pool64[2];
    };
    unsigned long last;     /* 마지막 기여 시각 (jiffies) */
    u16 count;               /* 인터럽트 카운터 (64회마다 기여) */
    u16 reg_idx;             /* 레지스터 인덱스 */
};

/* fast_mix() — 128비트 풀에 빠르게 혼합 (SipHash 유사) */
static void fast_mix(u32 pool[4], u32 a, u32 b, u32 c)
{
    pool[0] ^= a;
    pool[1] ^= b;
    pool[2] ^= c;
    pool[3] ^= pool[0];

    /* 4라운드 혼합 — 확산(diffusion) 보장 */
    pool[0] = rol32(pool[0], 7)  + pool[3];
    pool[1] = rol32(pool[1], 13) + pool[2];
    pool[2] = rol32(pool[2], 11) + pool[1];
    pool[3] = rol32(pool[3], 17) + pool[0];
}

/* mix_interrupt_randomness() — fast_pool → input_pool 전이 */
static void mix_interrupt_randomness(struct work_struct *work)
{
    struct fast_pool *fast_pool = container_of(work, ...);
    u32 pool[4];

    memcpy(pool, fast_pool->pool32, sizeof(pool));
    memset(fast_pool->pool32, 0, sizeof(fast_pool->pool32));

    _mix_pool_bytes(pool, sizeof(pool));
    credit_init_bits(max(1u, ...) );  /* 최소 1비트 크레딧 */
}

jitterentropy — CPU 지터 엔트로피

/* crypto/jitterentropy.c — 소프트웨어 기반 엔트로피 생성 */
/* CPU 명령어 실행 시간의 미세 변동을 엔트로피로 활용 */
/* 하드웨어 RNG가 없는 환경에서 보조 엔트로피 소스 역할 */

static u64 jent_measure_jitter(struct rand_data *ec)
{
    u64 time, delta;

    /* 메모리 접근 루프: 캐시 라인 경합으로 지터 유발 */
    jent_memaccess(ec, 0);

    /* 고정밀 타임스탬프 측정 */
    time = jent_get_nstime();  /* rdtsc / cntvct / clock_gettime */
    delta = time - ec->prev_time;
    ec->prev_time = time;

    /* 1차 미분: 타임스탬프 간 차이 */
    /* 2차 미분: 차이의 차이 → 진정한 지터 추출 */
    return delta;
}

/* 커널 부팅 시 jitterentropy 자동 활성화 조건 */
/* CONFIG_CRYPTO_JITTERENTROPY=y (대부분 배포판 기본) */
/* CRNG 초기화 대기 중 try_to_generate_entropy()에서 호출 */
엔트로피 소스 분류와 수집 경로 하드웨어 엔트로피 소스 RDRAND/RDSEED (CPU 내장) TPM 2.0 RNG (독립 칩) QRNG (양자 디바이스) SoC 내장 TRNG (ARM 등) 크레딧: quality 파라미터 기반 소프트웨어 엔트로피 소스 인터럽트 타이밍 지터 (TSC) 디스크 I/O 완료 타이밍 키보드/마우스 이벤트 코드+타이밍 jitterentropy (CPU 실행 지터) 크레딧: 이벤트 빈도 + 델타 기반 수집 함수 (API 계층) add_hwgenerator_randomness() add_interrupt_randomness() add_disk_randomness() add_input_randomness() add_device_randomness() add_bootloader_randomness() 공통: _mix_pool_bytes() 호출 + credit_init_bits() 크레딧 부여 (소스별 크레딧 정책 상이) input_pool Blake2s 256비트 상태 init_bits: 0 → 256 256비트 도달 → CRNG 초기화 완료! 환경별 주요 소스 물리 서버: RDRAND + IRQ + 디스크 VM/KVM: virtio-rng + IRQ 임베디드: SoC TRNG + jitter 컨테이너: 호스트 CRNG 공유 IoT/MCU: 전용 TRNG + 외장 QRNG

ftrace/bpftrace 엔트로피 모니터링

hwrng 서브시스템과 엔트로피 풀의 동작을 실시간으로 관찰하는 것은 드라이버 디버깅과 성능 분석에 필수적입니다. 이 섹션에서는 ftrace, bpftrace, /proc/sys/kernel/random/ 인터페이스를 활용한 모니터링 기법을 다룹니다.

엔트로피 모니터링 도구와 관찰 지점 hwrng 드라이버 input_pool 믹싱 ChaCha20 CRNG 사용자 공간 출력 ftrace 관찰 지점 (events/random/) mix_pool_bytes credit_entropy_bits extract_entropy getrandom / urandom_read bpftrace kprobe 관찰 지점 kprobe:add_hwgenerator_randomness kprobe:add_interrupt_randomness 수집 빈도 + 크기 히스토그램 kprobe:extract_entropy kprobe:crng_reseed 추출/리시드 주기 분석 kprobe:get_random_bytes tracepoint:sys_enter_getrandom 호출자 + 크기 분석 /proc/sys/kernel/random/ entropy_avail, poolsize 실시간 상태 조회 모니터링 전략: ftrace(이벤트 추적) + bpftrace(통계 집계) + /proc(실시간 상태) perf stat으로 RDRAND 명령어 실행 횟수, dd로 처리율 벤치마크 병행

ftrace로 난수 생성 추적

# ftrace 설정: random 서브시스템 이벤트 활성화
cd /sys/kernel/tracing

# 사용 가능한 random 이벤트 확인
ls events/random/
# add_device_randomness    credit_entropy_bits
# debit_entropy            extract_entropy
# get_random_bytes         mix_pool_bytes
# push_to_pool             random_read
# urandom_read             getrandom

# 엔트로피 크레딧 추적 활성화
echo 1 > events/random/credit_entropy_bits/enable
echo 1 > events/random/mix_pool_bytes/enable
echo 1 > events/random/extract_entropy/enable

# 트레이스 시작
echo 1 > tracing_on

# 트레이스 확인 (엔트로피 수집/소비 패턴)
cat trace_pipe | head -50
# 출력 예:
#   [hwrng]-123  credit_entropy_bits: bits 256
#   sshd-456     extract_entropy: nbytes 32
#   nginx-789    get_random_bytes: nbytes 16

# 트레이스 종료
echo 0 > tracing_on

bpftrace 엔트로피 모니터링 스크립트

/* bpftrace: 엔트로피 수집 함수 호출 빈도 히스토그램 */
/* 파일명: entropy_monitor.bt */

/* hwrng에서 엔트로피 수집 추적 */
kprobe:add_hwgenerator_randomness
{
    @hw_count = count();
    @hw_bytes = hist(arg1);  /* 수집 바이트 수 분포 */
}

/* 인터럽트 엔트로피 수집 추적 */
kprobe:add_interrupt_randomness
{
    @irq_count = count();
    @irq_num[arg0] = count();  /* IRQ 번호별 빈도 */
}

/* 엔트로피 추출(소비) 추적 */
kprobe:extract_entropy
{
    @extract_count = count();
    @extract_bytes = hist(arg1);  /* 추출 바이트 수 분포 */
}

/* get_random_bytes 호출자 추적 */
kprobe:get_random_bytes
{
    @callers[kstack(3)] = count();  /* 상위 3프레임 콜스택 */
}

/* getrandom 시스콜 추적 */
tracepoint:syscalls:sys_enter_getrandom
{
    @getrandom_size = hist(args->count);
    @getrandom_flags[args->flags] = count();
}

interval:s:10
{
    printf("\n--- %s 엔트로피 통계 ---\n", strftime("%H:%M:%S", nsecs));
    print(@hw_count);
    print(@irq_count);
    print(@extract_count);
}
# bpftrace 스크립트 실행
bpftrace entropy_monitor.bt

# /proc/sys/kernel/random/ 파라미터 종합 분석
echo "=== 커널 난수 시스템 상태 ==="
echo "엔트로피 추정: $(cat /proc/sys/kernel/random/entropy_avail) bits"
echo "풀 크기:       $(cat /proc/sys/kernel/random/poolsize) bits"
echo "부팅 UUID:     $(cat /proc/sys/kernel/random/boot_id)"
echo "재시드 간격:   $(cat /proc/sys/kernel/random/urandom_min_reseed_secs)초"
echo "읽기 깨움:     $(cat /proc/sys/kernel/random/read_wakeup_threshold) bits"
echo "쓰기 깨움:     $(cat /proc/sys/kernel/random/write_wakeup_threshold) bits"

# hwrng 드라이버 상태 종합
echo ""
echo "=== hwrng 드라이버 상태 ==="
echo "활성 RNG:   $(cat /sys/class/misc/hw_random/rng_current)"
echo "가용 RNG:   $(cat /sys/class/misc/hw_random/rng_available)"

# hwrng 처리율 벤치마크
echo ""
echo "=== hwrng 처리율 벤치마크 ==="
dd if=/dev/hwrng of=/dev/null bs=4096 count=1024 2>&1 | tail -1
# 4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.05 s, 83.9 MB/s

# perf stat으로 RDRAND 명령어 실행 횟수 측정
perf stat -e instructions,cycles,cache-misses \
     dd if=/dev/urandom of=/dev/null bs=4096 count=256 2>/dev/null

/proc/sys/kernel/random/ 파라미터 레퍼런스

파라미터읽기/쓰기기본값설명
entropy_avail 읽기 전용 0~256 현재 엔트로피 추정값 (비트). 5.18+에서는 0 또는 256
poolsize 읽기 전용 256 input_pool 크기 (비트). Blake2s 해시 상태 크기
boot_id 읽기 전용 UUID 부팅마다 새로 생성되는 128비트 UUID
uuid 읽기 전용 UUID 읽을 때마다 새 UUID 생성 (UUID v4)
urandom_min_reseed_secs 읽기/쓰기 60 CRNG 재시드 최소 간격 (초). 0이면 항상 재시드
read_wakeup_threshold 읽기/쓰기 64 /dev/random 블로킹 해제 엔트로피 임계값
write_wakeup_threshold 읽기/쓰기 896 엔트로피 부족 시 poll() 이벤트 발생 임계값

RNG 구현 비교: Linux vs BSD vs Windows

운영체제마다 난수 생성 서브시스템의 설계 철학과 구현이 크게 다릅니다. 이 섹션에서는 Linux, FreeBSD, OpenBSD, Windows의 RNG 구현을 비교하고, 각각의 장단점과 성능 특성을 분석합니다.

운영체제별 RNG 구현 비교표

항목Linux (5.18+)FreeBSD (14)OpenBSD (7.x)Windows (11)
핵심 알고리즘 ChaCha20 (CRNG) Fortuna (AES-256-CTR) ChaCha20 (arc4random) AES-256-CTR (BCryptGenRandom)
엔트로피 풀 Blake2s 256비트 32개 풀 (Fortuna) 256비트 (단일) 다중 풀 (비공개)
리시드 주기 5분 또는 수요 기반 10초 ~ 풀 로테이션 매 1.6MB 출력마다 자동 (비공개)
per-CPU 구조 per-CPU ChaCha20 키 단일 전역 Fortuna per-CPU arc4random per-프로세서 (비공개)
사용자 API getrandom() getentropy() getentropy() BCryptGenRandom()
hwrng 프레임워크 struct hwrng (다중 드라이버) random_harvest (단일) 없음 (RDRAND 직접) CNG Provider
forward secrecy Fast Key Erasure Fortuna 풀 교체 매 블록 키 교체 미공개
FIPS 인증 CONFIG_CRYPTO_FIPS 미인증 미인증 FIPS 140-2 Level 1
부팅 시 블로킹 getrandom() 블록 getentropy() 블록 항상 비블록 항상 비블록
엔트로피 고갈 시 CRNG 계속 출력 (안전) Fortuna 계속 출력 ChaCha20 계속 출력 계속 출력

성능 벤치마크 비교

# Linux /dev/urandom 처리율 측정
dd if=/dev/urandom of=/dev/null bs=4096 count=262144 2>&1 | tail -1
# 1073741824 bytes (1.1 GB) copied, 0.95 s, 1.1 GB/s  ← ChaCha20 per-CPU

# getrandom() 레이턴시 측정 (C 프로그램)
# 32바이트 × 100만 회 → 평균 ~150ns/호출 (x86-64, ChaCha20 SIMD)

# /dev/hwrng 처리율 (하드웨어 의존)
dd if=/dev/hwrng of=/dev/null bs=4096 count=256 2>&1 | tail -1
# Intel RDRAND: ~500 MB/s
# virtio-rng:   호스트 의존 (~100 MB/s)
# USB QRNG:     ~1 MB/s
# PCIe QRNG:    ~100 MB/s
# TPM RNG:      ~1 MB/s
인터페이스Linux (ChaCha20)FreeBSD (Fortuna)OpenBSD (arc4random)비고
커널 내부 (32B) ~50ns ~120ns ~60ns per-CPU 최적화 효과
시스콜 (32B) ~150ns ~200ns ~100ns 시스콜 오버헤드 포함
벌크 처리율 ~1.1 GB/s ~400 MB/s ~800 MB/s AVX2/NEON SIMD 활용
다중 스레드 확장성 선형 (per-CPU) 제한적 (전역 락) 선형 (per-CPU) Linux, OpenBSD 우수

설계 철학 비교

Linux: "믹싱 우선" — 모든 가능한 엔트로피 소스를 Blake2s 풀에 혼합합니다. hwrng 프레임워크로 다양한 하드웨어를 지원하되, 소프트웨어 지터만으로도 안전하게 동작합니다. FIPS 모드를 선택적으로 지원합니다.
OpenBSD: "단순성 우선" — 별도의 hwrng 프레임워크 없이 RDRAND과 인터럽트 지터만 사용합니다. arc4random()은 항상 비블록킹이며 libc에서 직접 제공합니다. getentropy()의 최대 크기가 256바이트로 제한됩니다.
FreeBSD: "Fortuna 설계" — Niels Ferguson과 Bruce Schneier의 Fortuna 알고리즘을 충실히 구현합니다. 32개의 분리된 엔트로피 풀과 지수적 리시드 스케줄로 높은 복원력을 제공합니다.
RNG 아키텍처 비교: Linux vs OpenBSD vs FreeBSD Linux (5.18+) hwrng (다중 드라이버) input_pool (Blake2s 256b) ChaCha20-CRNG (per-CPU) getrandom() /dev/urandom 처리율: ~1.1 GB/s SIMD: AVX2, NEON, SSE 확장성: per-CPU 선형 FIPS: CONFIG_CRYPTO_FIPS 부팅 블록: getrandom() 대기 OpenBSD (7.x) RDRAND + IRQ 지터 (직접) (풀 없음) ChaCha20 (per-CPU) getentropy() arc4random() 처리율: ~800 MB/s getentropy() 최대 256B 확장성: per-CPU 선형 FIPS: 미지원 부팅 블록: 절대 블록 안 함 FreeBSD (14) random_harvest (단일 API) Fortuna 32개 풀 AES-256-CTR (전역) getentropy() /dev/random 처리율: ~400 MB/s AES-NI 하드웨어 가속 확장성: 전역 락 제한 FIPS: 미지원 부팅 블록: getentropy() 대기