C++ random_device | "하드웨어 난수" 가이드

C++ random_device | "하드웨어 난수" 가이드

이 글의 핵심

C++ random_device에 대한 실전 가이드입니다.

들어가며

**std::random_device**는 C++11에서 도입된 하드웨어 기반 난수 생성기입니다. 시스템의 엔트로피 소스(예: /dev/urandom)를 사용하여 비결정적 난수를 생성하며, 주로 난수 엔진의 시드 생성이나 암호학적 용도로 사용됩니다.


1. random_device 기본

기본 사용

#include <iostream>
#include <random>

int main() {
    std::random_device rd;
    
    // 난수 생성 (32비트 unsigned int)
    for (int i = 0; i < 5; ++i) {
        std::cout << rd() << std::endl;
    }
    
    return 0;
}

출력:

3847291038
1029384756
2938475610
4756102938
1847562903

시드 생성

#include <random>
#include <iostream>

int main() {
    // random_device로 시드 생성
    std::random_device rd;
    
    // mt19937 엔진 초기화
    std::mt19937 gen{rd()};
    
    // 균등 분포
    std::uniform_int_distribution<> dist{1, 100};
    
    // 난수 생성 (빠름)
    for (int i = 0; i < 10; ++i) {
        std::cout << dist(gen) << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

출력:

42 17 89 3 56 91 28 64 11 73

2. 엔트로피

entropy() 메서드

#include <iostream>
#include <random>

int main() {
    std::random_device rd;
    
    // 엔트로피 확인 (비트)
    double entropy = rd.entropy();
    
    std::cout << "엔트로피: " << entropy << " bits" << std::endl;
    
    if (entropy == 0.0) {
        std::cout << "의사 난수 (결정적)" << std::endl;
    } else {
        std::cout << "하드웨어 난수 (비결정적)" << std::endl;
    }
    
    return 0;
}

출력 (Linux):

엔트로피: 32 bits
하드웨어 난수 (비결정적)

플랫폼별 구현

플랫폼엔트로피 소스entropy()
Linux/dev/urandom32 bits
WindowsCryptGenRandom32 bits
macOS/dev/urandom32 bits
일부 임베디드의사 난수0 bits

3. 시드 시퀀스

단일 시드 vs 여러 시드

#include <random>
#include <array>
#include <algorithm>

int main() {
    std::random_device rd;
    
    // ❌ 단일 시드 (32비트만 사용)
    std::mt19937 gen1{rd()};
    
    // ✅ 여러 시드 (더 좋은 초기화)
    std::array<unsigned int, std::mt19937::state_size> seedData;
    std::generate(seedData.begin(), seedData.end(), std::ref(rd));
    
    std::seed_seq seq(seedData.begin(), seedData.end());
    std::mt19937 gen2{seq};
    
    return 0;
}

설명:

  • mt19937의 상태 크기는 624개의 32비트 정수 (19,968비트)
  • 단일 시드는 32비트만 사용 (나머지는 알고리즘으로 채움)
  • 여러 시드는 더 많은 엔트로피 제공

4. 실전 예제

예제 1: 암호학적 난수

#include <random>
#include <vector>
#include <iostream>
#include <iomanip>

std::vector<unsigned char> generateKey(size_t length) {
    std::random_device rd;
    
    std::vector<unsigned char> key(length);
    for (auto& byte : key) {
        byte = static_cast<unsigned char>(rd() % 256);
    }
    
    return key;
}

int main() {
    // 16바이트 키 생성
    auto key = generateKey(16);
    
    std::cout << "암호 키: ";
    for (auto byte : key) {
        std::cout << std::hex << std::setw(2) << std::setfill('0') 
                  << static_cast<int>(byte);
    }
    std::cout << std::endl;
    
    return 0;
}

출력:

암호 키: 3f7a9b2c8d1e4f6a5b3c9d2e7f1a4b8c

예제 2: UUID 생성

#include <random>
#include <sstream>
#include <iomanip>
#include <iostream>

std::string generateUUID() {
    std::random_device rd;
    std::mt19937 gen{rd()};
    std::uniform_int_distribution<> dist{0, 15};
    std::uniform_int_distribution<> dist2{8, 11};
    
    std::ostringstream oss;
    oss << std::hex;
    
    // 8-4-4-4-12 형식
    for (int i = 0; i < 8; ++i) oss << dist(gen);
    oss << "-";
    
    for (int i = 0; i < 4; ++i) oss << dist(gen);
    oss << "-4";  // 버전 4
    
    for (int i = 0; i < 3; ++i) oss << dist(gen);
    oss << "-";
    
    oss << dist2(gen);  // 변형 (8, 9, a, b)
    for (int i = 0; i < 3; ++i) oss << dist(gen);
    oss << "-";
    
    for (int i = 0; i < 12; ++i) oss << dist(gen);
    
    return oss.str();
}

int main() {
    for (int i = 0; i < 3; ++i) {
        std::cout << generateUUID() << std::endl;
    }
    
    return 0;
}

출력:

550e8400-e29b-41d4-a716-446655440000
6ba7b810-9dad-11d1-80b4-00c04fd430c8
3d813cbb-47fb-32ba-91df-831e1593ac29

예제 3: 토큰 생성

#include <random>
#include <string>
#include <iostream>

std::string generateToken(size_t length) {
    std::random_device rd;
    std::mt19937 gen{rd()};
    
    const std::string chars = 
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz";
    
    std::uniform_int_distribution<> dist{0, static_cast<int>(chars.size() - 1)};
    
    std::string token;
    token.reserve(length);
    
    for (size_t i = 0; i < length; ++i) {
        token += chars[dist(gen)];
    }
    
    return token;
}

int main() {
    // 32자 토큰 생성
    std::cout << "토큰: " << generateToken(32) << std::endl;
    
    return 0;
}

출력:

토큰: 7aB3xK9mP2qW5nL8vC1dF4jH6rT0yU3z

5. 자주 발생하는 문제

문제 1: 성능

#include <random>
#include <chrono>
#include <iostream>

void benchmarkRandomDevice() {
    std::random_device rd;
    
    auto start = std::chrono::high_resolution_clock::now();
    
    // ❌ random_device 직접 사용 (매우 느림)
    for (int i = 0; i < 1000000; ++i) {
        volatile unsigned int r = rd();
    }
    
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "random_device: " << duration.count() << "ms" << std::endl;
}

void benchmarkMT19937() {
    std::random_device rd;
    std::mt19937 gen{rd()};
    
    auto start = std::chrono::high_resolution_clock::now();
    
    // ✅ mt19937 사용 (빠름)
    for (int i = 0; i < 1000000; ++i) {
        volatile unsigned int r = gen();
    }
    
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "mt19937: " << duration.count() << "ms" << std::endl;
}

int main() {
    benchmarkRandomDevice();  // ~5000ms
    benchmarkMT19937();       // ~50ms
    
    return 0;
}

출력:

random_device: 5234ms
mt19937: 47ms

문제 2: 플랫폼 의존성

#include <random>
#include <iostream>

int main() {
    std::random_device rd;
    
    // 엔트로피 확인
    double entropy = rd.entropy();
    
    if (entropy == 0.0) {
        std::cerr << "경고: random_device가 의사 난수를 사용합니다." << std::endl;
        std::cerr << "플랫폼: 하드웨어 난수 미지원" << std::endl;
        
        // 대안: 시간 기반 시드
        auto now = std::chrono::high_resolution_clock::now();
        auto seed = now.time_since_epoch().count();
        std::mt19937 gen{static_cast<unsigned int>(seed)};
    } else {
        std::cout << "하드웨어 난수 사용 (엔트로피: " << entropy << " bits)" << std::endl;
    }
    
    return 0;
}

문제 3: 시드 품질

#include <random>
#include <array>
#include <algorithm>

// ❌ 단일 시드 (품질 낮음)
void poorSeeding() {
    std::random_device rd;
    std::mt19937 gen{rd()};  // 32비트만 사용
}

// ✅ 여러 시드 (품질 높음)
void goodSeeding() {
    std::random_device rd;
    
    // mt19937 상태 크기만큼 시드 생성
    std::array<unsigned int, std::mt19937::state_size> seedData;
    std::generate(seedData.begin(), seedData.end(), std::ref(rd));
    
    std::seed_seq seq(seedData.begin(), seedData.end());
    std::mt19937 gen{seq};
}

// ✅ 간단한 여러 시드
void simpleGoodSeeding() {
    std::random_device rd;
    
    // 8개 시드 (256비트)
    std::array<unsigned int, 8> seedData;
    for (auto& seed : seedData) {
        seed = rd();
    }
    
    std::seed_seq seq(seedData.begin(), seedData.end());
    std::mt19937 gen{seq};
}

문제 4: 재현성

#include <random>
#include <iostream>

// ❌ 재현 불가 (디버깅 어려움)
void nonReproducible() {
    std::random_device rd;
    std::mt19937 gen{rd()};  // 매번 다른 시드
    
    std::uniform_int_distribution<> dist{1, 100};
    std::cout << dist(gen) << std::endl;  // 매번 다른 결과
}

// ✅ 재현 가능 (디버깅 용이)
void reproducible(bool debug = false) {
    std::mt19937 gen;
    
    if (debug) {
        gen.seed(42);  // 고정 시드
    } else {
        std::random_device rd;
        gen.seed(rd());  // 랜덤 시드
    }
    
    std::uniform_int_distribution<> dist{1, 100};
    std::cout << dist(gen) << std::endl;
}

int main() {
    std::cout << "디버그 모드:" << std::endl;
    reproducible(true);   // 항상 같은 결과
    reproducible(true);   // 항상 같은 결과
    
    std::cout << "\n프로덕션 모드:" << std::endl;
    reproducible(false);  // 매번 다른 결과
    reproducible(false);  // 매번 다른 결과
    
    return 0;
}

6. 실전 예제: 난수 유틸리티

#include <random>
#include <string>
#include <vector>
#include <array>
#include <algorithm>

class RandomUtils {
    std::mt19937 gen;
    
public:
    // 생성자: 고품질 시드
    RandomUtils() {
        std::random_device rd;
        std::array<unsigned int, 8> seedData;
        std::generate(seedData.begin(), seedData.end(), std::ref(rd));
        std::seed_seq seq(seedData.begin(), seedData.end());
        gen.seed(seq);
    }
    
    // 범위 내 정수
    int randomInt(int min, int max) {
        std::uniform_int_distribution<> dist{min, max};
        return dist(gen);
    }
    
    // 범위 내 실수
    double randomDouble(double min, double max) {
        std::uniform_real_distribution<> dist{min, max};
        return dist(gen);
    }
    
    // 랜덤 문자열
    std::string randomString(size_t length) {
        const std::string chars = 
            "0123456789"
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            "abcdefghijklmnopqrstuvwxyz";
        
        std::uniform_int_distribution<> dist{0, static_cast<int>(chars.size() - 1)};
        
        std::string result;
        result.reserve(length);
        
        for (size_t i = 0; i < length; ++i) {
            result += chars[dist(gen)];
        }
        
        return result;
    }
    
    // 랜덤 바이트
    std::vector<unsigned char> randomBytes(size_t length) {
        std::uniform_int_distribution<> dist{0, 255};
        
        std::vector<unsigned char> bytes(length);
        for (auto& byte : bytes) {
            byte = static_cast<unsigned char>(dist(gen));
        }
        
        return bytes;
    }
    
    // 배열 셔플
    template<typename T>
    void shuffle(std::vector<T>& vec) {
        std::shuffle(vec.begin(), vec.end(), gen);
    }
};

int main() {
    RandomUtils rng;
    
    // 정수
    std::cout << "랜덤 정수 (1-100): " << rng.randomInt(1, 100) << std::endl;
    
    // 실수
    std::cout << "랜덤 실수 (0-1): " << rng.randomDouble(0.0, 1.0) << std::endl;
    
    // 문자열
    std::cout << "랜덤 문자열: " << rng.randomString(16) << std::endl;
    
    // 바이트
    auto bytes = rng.randomBytes(8);
    std::cout << "랜덤 바이트: ";
    for (auto byte : bytes) {
        std::cout << std::hex << std::setw(2) << std::setfill('0') 
                  << static_cast<int>(byte) << " ";
    }
    std::cout << std::endl;
    
    // 셔플
    std::vector<int> vec = {1, 2, 3, 4, 5};
    rng.shuffle(vec);
    std::cout << "셔플: ";
    for (int x : vec) std::cout << x << " ";
    std::cout << std::endl;
    
    return 0;
}

출력:

랜덤 정수 (1-100): 73
랜덤 실수 (0-1): 0.642857
랜덤 문자열: aB7xK3mP9qW2nL5v
랜덤 바이트: 3f 7a 9b 2c 8d 1e 4f 6a
셔플: 3 1 5 2 4

정리

핵심 요약

  1. random_device: 하드웨어 기반 비결정적 난수
  2. 용도: 시드 생성, 암호학적 난수
  3. 성능: 느림 (시드로만 사용)
  4. entropy(): 비결정성 측정 (0이면 의사 난수)
  5. 시드 품질: 여러 시드로 초기화
  6. 재현성: 디버깅 시 고정 시드 사용

random_device vs mt19937

특징random_devicemt19937
속도매우 느림빠름
품질하드웨어 난수의사 난수
용도시드 생성일반 난수
재현성불가가능 (시드 고정)
플랫폼의존적독립적

실전 팁

사용 원칙:

  • random_device는 시드 생성에만 사용
  • 실제 난수는 mt19937 같은 엔진 사용
  • 암호학적 용도는 random_device 직접 사용 고려
  • 여러 시드로 엔진 초기화 (품질 향상)

성능:

  • random_device는 시스템 콜 사용 (느림)
  • 엔진은 메모리 기반 (빠름)
  • 대량 난수는 반드시 엔진 사용

디버깅:

  • 디버깅 시 고정 시드 사용
  • 프로덕션에서는 random_device 시드
  • entropy() 확인으로 플랫폼 검증

다음 단계

  • C++ Distribution
  • C++ Random
  • C++ Algorithm Generate

관련 글

  • C++ Distribution |
  • C++ 난수 생성 |
  • C++ Random |
  • C++ async & launch |
  • C++ Atomic Operations |