가장 큰 결함 std::random_device은 CSPRNG를 사용할 수없는 경우 결정 론적 폴 백이 허용된다는 것입니다. 이것은 std::random_device생성 된 바이트가 결정적 일 수 있으므로를 사용하여 PRNG를 시드하지 않는 좋은 이유 입니다. 안타깝게도 이것이 언제 발생하는지 알아 내거나 저품질 난수 대신 실패를 요청하는 API를 제공하지 않습니다.
즉, 완전히 이식 가능한 솔루션 은 없지만 적절한 최소한의 접근 방식이 있습니다. CSPRNG ( sysrandom아래에 정의 됨) 주위에 최소 래퍼를 사용 하여 PRNG를 시드 할 수 있습니다.
윈도우
CryptGenRandomCSPRNG를 사용할 수 있습니다 . 예를 들어 다음 코드를 사용할 수 있습니다.
bool acquire_context(HCRYPTPROV *ctx)
{
if (!CryptAcquireContext(ctx, nullptr, nullptr, PROV_RSA_FULL, 0)) {
return CryptAcquireContext(ctx, nullptr, nullptr, PROV_RSA_FULL, CRYPT_NEWKEYSET);
}
return true;
}
size_t sysrandom(void* dst, size_t dstlen)
{
HCRYPTPROV ctx;
if (!acquire_context(&ctx)) {
throw std::runtime_error("Unable to initialize Win32 crypt library.");
}
BYTE* buffer = reinterpret_cast<BYTE*>(dst);
if(!CryptGenRandom(ctx, dstlen, buffer)) {
throw std::runtime_error("Unable to generate random bytes.");
}
if (!CryptReleaseContext(ctx, 0)) {
throw std::runtime_error("Unable to release Win32 crypt library.");
}
return dstlen;
}
유닉스 유사
많은 유닉스 계열 시스템에서 가능하면 / dev / urandom을 사용해야 합니다 (POSIX 호환 시스템에 존재한다고 보장 할 수는 없지만).
size_t sysrandom(void* dst, size_t dstlen)
{
char* buffer = reinterpret_cast<char*>(dst);
std::ifstream stream("/dev/urandom", std::ios_base::binary | std::ios_base::in);
stream.read(buffer, dstlen);
return dstlen;
}
다른
CSPRNG를 사용할 수없는 경우 std::random_device. 그러나 다양한 컴파일러 (특히 MinGW)가 PRNG 로 구현하기 때문에 가능하면 이것을 피할 것입니다 (사실 매번 동일한 시퀀스를 생성하여 사람에게 적절하게 무작위가 아니라는 것을 경고합니다).
파종
이제 최소한의 오버 헤드로 조각을 얻었으므로 원하는 비트의 임의 엔트로피를 생성하여 PRNG를 시드 할 수 있습니다. 이 예에서는 (분명히 불충분 한) 32 비트를 사용하여 PRNG를 시드하고이 값을 늘려야합니다 (CSPRNG에 따라 다름).
std::uint_least32_t seed;
sysrandom(&seed, sizeof(seed));
std::mt19937 gen(seed);
부스트 비교
소스 코드를 간략히 살펴본 후 boost :: random_device (진정한 CSPRNG)에 대한 유사점을 볼 수 있습니다 . Boost는 MS_DEF_PROVWindows에서 사용 하며 PROV_RSA_FULL. 빠진 유일한 것은 암호화 컨텍스트를 확인하는 것입니다 CRYPT_VERIFYCONTEXT. * Nix에서 Boost는 /dev/urandom. IE,이 솔루션은 이식 가능하고 잘 테스트되었으며 사용하기 쉽습니다.
Linux 전문화
보안을 위해 간결함을 희생하려는 경우 getrandomLinux 3.17 이상 및 최신 Solaris에서 탁월한 선택입니다. 커널이 부팅 후 아직 CSPRNG를 초기화하지 않은 경우 차단된다는 점을 제외하면 getrandom과 동일하게 작동 /dev/urandom합니다. 다음 스 니펫은 Linux getrandom를 사용할 수 있는지 여부를 감지 하고 그렇지 않은 경우 /dev/urandom.
#if defined(__linux__) || defined(linux) || defined(__linux)
# // Check the kernel version. `getrandom` is only Linux 3.17 and above.
# include <linux/version.h>
# if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)
# define HAVE_GETRANDOM
# endif
#endif
// also requires glibc 2.25 for the libc wrapper
#if defined(HAVE_GETRANDOM)
# include <sys/syscall.h>
# include <linux/random.h>
size_t sysrandom(void* dst, size_t dstlen)
{
int bytes = syscall(SYS_getrandom, dst, dstlen, 0);
if (bytes != dstlen) {
throw std::runtime_error("Unable to read N bytes from CSPRNG.");
}
return dstlen;
}
#elif defined(_WIN32)
// Windows sysrandom here.
#else
// POSIX sysrandom here.
#endif
OpenBSD
마지막 경고가 하나 있습니다. 최신 OpenBSD에는 /dev/urandom. 대신 getentropy 를 사용해야 합니다.
#if defined(__OpenBSD__)
# define HAVE_GETENTROPY
#endif
#if defined(HAVE_GETENTROPY)
# include <unistd.h>
size_t sysrandom(void* dst, size_t dstlen)
{
int bytes = getentropy(dst, dstlen);
if (bytes != dstlen) {
throw std::runtime_error("Unable to read N bytes from CSPRNG.");
}
return dstlen;
}
#endif
다른 생각들
암호 학적으로 안전한 임의 바이트가 필요한 경우 fstream을 POSIX의 버퍼링되지 않은 열기 / 읽기 / 닫기로 대체해야합니다. 모두 있기 때문이다 basic_filebuf및 FILE표준 할당을 통해 할당 (따라서 메모리에서 닦여 생략)한다 내부 버퍼를 포함한다.
다음으로 변경 sysrandom하면 쉽게 수행 할 수 있습니다 .
size_t sysrandom(void* dst, size_t dstlen)
{
int fd = open("/dev/urandom", O_RDONLY);
if (fd == -1) {
throw std::runtime_error("Unable to open /dev/urandom.");
}
if (read(fd, dst, dstlen) != dstlen) {
close(fd);
throw std::runtime_error("Unable to read N bytes from CSPRNG.");
}
close(fd);
return dstlen;
}
감사
FILE버퍼링 된 읽기 사용 을 지적한 Ben Voigt에게 특별히 감사드립니다 . 따라서 사용해서는 안됩니다.
나는 또한 언급 한 Peter Cordes getrandom와 OpenBSD의 /dev/urandom.