Làm thế nào để bắt nguồn từ mt19937 PRNG?


112

Tôi dường như thấy nhiều câu trả lời trong đó ai đó đề xuất sử dụng <random>để tạo các số ngẫu nhiên, thường cùng với mã như thế này:

std::random_device rd;  
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 5);
dis(gen);

Thông thường điều này thay thế một số loại "sự ghê tởm xấu xa" chẳng hạn như:

srand(time(NULL));
rand()%6;

Chúng ta có thể chỉ trích cách làm cũ bằng cách lập luận rằng time(NULL)cung cấp entropy thấp, time(NULL)có thể dự đoán được và kết quả cuối cùng là không đồng nhất.

Nhưng tất cả những điều đó đều đúng với phương pháp mới: nó chỉ có một lớp veneer sáng bóng hơn.

  • rd()trả về một đơn unsigned int. Điều này có ít nhất 16 bit và có lẽ là 32. Điều đó không đủ để tạo ra trạng thái 19937 bit của MT.

  • Sử dụng std::mt19937 gen(rd());gen()(gieo hạt với 32 bit và xem xét đầu ra đầu tiên) không cung cấp phân phối đầu ra tốt. 7 và 13 không bao giờ có thể là đầu ra đầu tiên. Hai hạt tạo ra 0. Mười hai hạt tạo ra 1226181350. ( Link )

  • std::random_devicecó thể, và đôi khi được thực hiện như một PRNG đơn giản với một hạt giống cố định. Do đó, nó có thể tạo ra cùng một trình tự trên mỗi lần chạy. ( Liên kết ) Điều này thậm chí còn tồi tệ hơn time(NULL).

Tệ hơn nữa, rất dễ dàng sao chép và dán các đoạn mã nói trên, bất chấp các vấn đề của chúng. Một số giải pháp cho này đòi hỏi phải mua largish thư viện mà có thể không phù hợp với tất cả mọi người.

Về vấn đề này, câu hỏi của tôi là Làm cách nào để có thể gieo mầm mt19937 PRNG trong C ++ một cách ngắn gọn, dễ hiểu và kỹ lưỡng?

Với những vấn đề trên, một câu trả lời hay:

  • Phải gieo đầy đủ mt19937 / mt19937_64.
  • Không thể chỉ dựa vào std::random_devicehoặc time(NULL)như một nguồn entropy.
  • Không nên dựa vào Boost hoặc các quyền tự do khác.
  • Nên vừa với một số dòng nhỏ để nó trông đẹp mắt khi được dán vào một câu trả lời.

Suy nghĩ

  • Suy nghĩ hiện tại của tôi là kết quả đầu ra từ std::random_devicecó thể được trộn (có thể thông qua XOR) với time(NULL), các giá trị bắt nguồn từ ngẫu nhiên hóa không gian địa chỉ và một hằng số được mã hóa cứng (có thể được thiết lập trong quá trình phân phối) để có được một bức ảnh nỗ lực tốt nhất tại entropy.

  • std::random_device::entropy() không đưa ra một dấu hiệu tốt về những gì std::random_devicecó thể hoặc không thể làm.


24
@Fabien: Cái đó có gì di động vậy? Đây là một câu hỏi C ++, không phải một câu hỏi Linux.
Các cuộc đua ánh sáng trong quỹ đạo vào

6
Suy nghĩ cá nhân của tôi là có lẽ giá trị có thể được rút ra từ std::random_device, time(NULL)và địa chỉ chức năng, sau đó XORed với nhau để tạo ra một loại nguồn entropy nỗ lực tốt nhất.
Richard,

5
Sẽ thật tuyệt nếu có một hàm như does_random_device_actently_work () để ít nhất một hàm có thể làm suy giảm một cách duyên dáng, hoặc tạo ra các cảnh báo hoặc lỗi cho người dùng.

4
Giải pháp thích hợp không phải là ngắn, giải pháp ngắn sẽ không phù hợp. Biện pháp của tôi tôi sử dụng trong tôi thư viện seed11 là cơ bản để thực hiện std::random_deviceđúng trên nền tảng mà bạn lập kế hoạch đang chạy chương trình của bạn vào, và cung cấp một hàm mà tạo ra một máy phát điện hạt giống ( seed11::make_seeded<std::mt19937>())
milleniumbug

5
Ngoài ra: dấu đầu dòng thứ hai của bạn không thêm bất cứ điều gì mới. Không có gì ngạc nhiên khi bạn tìm thấy một số giá trị xuất hiện 12 lần. Bạn sẽ mong đợi chỉ có hơn ba giá trị xuất hiện đúng 12 lần , giả sử rằng bạn có 2 ^ 32 mẫu ngẫu nhiên đồng nhất, độc lập .

Câu trả lời:


58

Tôi tranh luận rằng lỗ hổng lớn nhất std::random_devicelà nó được phép dự phòng xác định nếu không có CSPRNG. Đây là lý do chính đáng để không sử dụng PRNG std::random_device, vì các byte được tạo ra có thể mang tính xác định. Rất tiếc, nó không cung cấp một API để tìm hiểu khi nào điều này xảy ra hoặc yêu cầu thất bại thay vì các số ngẫu nhiên chất lượng thấp.

Đó là, không có giải pháp hoàn toàn di động : tuy nhiên, có một cách tiếp cận tối thiểu và phù hợp. Bạn có thể sử dụng một trình bao bọc tối thiểu xung quanh CSPRNG (được định nghĩa như sysrandombên dưới) để tạo PRNG.

các cửa sổ


Bạn có thể dựa vào CryptGenRandomCSPRNG. Ví dụ: bạn có thể sử dụng mã sau:

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;
}

Unix-Like


Trên nhiều hệ thống giống Unix, bạn nên sử dụng / dev / urandom khi có thể (mặc dù điều này không được đảm bảo tồn tại trên các hệ thống tuân thủ 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;
}

Khác


Nếu không có CSPRNG, bạn có thể chọn dựa vào std::random_device. Tuy nhiên, tôi sẽ tránh điều này nếu có thể, vì nhiều trình biên dịch khác nhau (đáng chú ý nhất là MinGW) triển khai nó dưới dạng PRNG (trên thực tế, tạo ra cùng một trình tự mọi lúc để cảnh báo con người rằng nó không phải là ngẫu nhiên).

Gieo hạt


Bây giờ chúng ta đã có các mảnh của mình với chi phí tối thiểu, chúng ta có thể tạo ra các bit entropy ngẫu nhiên mong muốn để gieo PRNG của chúng ta. Ví dụ sử dụng (rõ ràng là không đủ) 32 bit để tạo PRNG và bạn nên tăng giá trị này (phụ thuộc vào CSPRNG của bạn).

std::uint_least32_t seed;    
sysrandom(&seed, sizeof(seed));
std::mt19937 gen(seed);

So sánh để tăng


Chúng ta có thể thấy các điểm tương đồng để boost :: random_device (một CSPRNG thực sự) sau khi xem nhanh mã nguồn . Tăng cường sử dụng MS_DEF_PROVtrên Windows, là loại nhà cung cấp cho PROV_RSA_FULL. Điều duy nhất còn thiếu sẽ là xác minh bối cảnh mật mã, có thể được thực hiện với CRYPT_VERIFYCONTEXT. Trên * Nix, Boost sử dụng /dev/urandom. IE, giải pháp này có tính di động, được thử nghiệm tốt và dễ sử dụng.

Chuyên môn Linux


Nếu bạn sẵn sàng hy sinh tính ngắn gọn cho bảo mật, đây getrandomlà một lựa chọn tuyệt vời trên Linux 3.17 trở lên và trên Solaris gần đây. getrandomhoạt động giống hệt nhau /dev/urandom, ngoại trừ nó chặn nếu hạt nhân chưa khởi tạo CSPRNG sau khi khởi động. Đoạn mã sau sẽ phát hiện xem Linux getrandomcó khả dụng hay không và nếu không có /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


Có một cảnh báo cuối cùng: OpenBSD hiện đại không có /dev/urandom. Bạn nên sử dụng getentropy để thay thế.

#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

Suy nghĩ khác


Nếu bạn cần các byte ngẫu nhiên an toàn bằng mật mã, bạn có thể nên thay thế fstream bằng mở / đọc / đóng không có bộ đệm của POSIX. Điều này là do cả hai basic_filebufFILEđều chứa bộ đệm bên trong, bộ đệm này sẽ được cấp phát qua bộ cấp phát tiêu chuẩn (và do đó không bị xóa khỏi bộ nhớ).

Điều này có thể dễ dàng được thực hiện bằng cách thay đổi sysrandomthành:

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;
}

Cảm ơn


Đặc biệt cảm ơn Ben Voigt vì đã chỉ ra cách FILEsử dụng các lần đọc được đệm và do đó không nên sử dụng.

Tôi cũng muốn cảm ơn Peter Cordes đã đề cập getrandom, và sự thiếu sót của OpenBSD /dev/urandom.


11
Đây là những gì tôi đã làm trong quá khứ, nhưng, hoặc ít nhất một câu hỏi, là WTF không thể những người viết thư viện cho các nền tảng này làm điều này cho chúng tôi? Tôi mong đợi quyền truy cập tệp và luồng (ví dụ) được trừu tượng hóa bằng triển khai thư viện, vậy tại sao không tạo số ngẫu nhiên?

2
OP ở đây: Sẽ rất tuyệt nếu câu trả lời này cho thấy khả năng gieo mầm tốt hơn một chút. Càng nhiều càng tốt, tôi hy vọng có câu trả lời tạo ra mã có thể sao chép thực hiện công việc tốt hơn ví dụ đơn giản mà tôi đã đăng trong câu hỏi của mình mà không yêu cầu nhiều kỹ thuật giải thích hoặc suy nghĩ từ phía người lập trình.
Richard

4
Tôi nghĩ /dev/randomsẽ là lựa chọn tốt hơn để gieo RNG, nhưng rõ ràng /dev/urandomvẫn được coi là an toàn về mặt tính toán ngay cả khi /dev/randomsẽ bị chặn vì entropy khả dụng thấp, vì vậy, urandomlựa chọn được khuyến nghị cho mọi thứ ngoại trừ có thể là miếng đệm một lần. Xem thêm unix.stackexchange.com/questions/324209/… . Tuy nhiên, hãy cẩn thận với những hạt giống có thể dự đoán được từ urandomrất sớm sau khi khởi động.
Peter Cordes

2
Lệnh getrandom(2)gọi hệ thống của Linux giống như mở và đọc /dev/urandom, ngoại trừ nó sẽ chặn nếu các nguồn ngẫu nhiên của hạt nhân chưa được khởi tạo. Tôi nghĩ rằng điều này giúp bạn thoát khỏi vấn đề ngẫu nhiên chất lượng thấp khởi động sớm mà không bị chặn trong các trường hợp khác /dev/random.
Peter Cordes

1
@PeterCordes, chắc chắn và đó là một tùy chọn tuyệt vời khi có sẵn. Tuy nhiên, nó không hoạt động trên BSD hoặc các * Nixes khác, đây là thứ /dev/urandomthường hoạt động. Thảo luận về danh sách gửi thư trong Python về vấn đề này là điều mà tôi thường đăng ký: bug.python.org/issue27266
Alexander Huszagh

22

Theo một nghĩa nào đó, điều này không thể được thực hiện một cách di động. Có nghĩa là, người ta có thể hình dung một nền tảng xác định đầy đủ hợp lệ chạy C ++ (giả sử, một trình mô phỏng điều khiển đồng hồ máy một cách xác định và với I / O "xác định") trong đó không có nguồn ngẫu nhiên nào để tạo ra một PRNG.


1
@kbelder: 1. Ai nói người dùng là một người? 2. Không phải tất cả các chương trình có sự tương tác người dùng và bạn chắc chắn không thể giả luôn có một người dùng xung quanh ...
einpoklum

8
Tôi đánh giá cao phản hồi này, nhưng cũng cảm thấy như thể một chương trình nên nỗ lực hợp lý nhất.
Richard

3
@Richard Đồng ý, nhưng vấn đề là người viết tiêu chuẩn C ++ phải (hoặc ít nhất là cố gắng hết sức để) thích ứng với những loại tình huống kỳ lạ này. Đó là lý do tại sao bạn nhận được các loại định nghĩa tiêu chuẩn khôn ngoan, nơi bạn có thể nhận được kết quả tốt, nhưng trình biên dịch vẫn có thể tuân thủ tiêu chuẩn ngay cả khi nó trả lại thứ gì đó vô giá trị về mặt chức năng. - Vì vậy, các hạn chế của bạn ("ngắn và không thể dựa vào các thư viện khác") loại trừ bất kỳ phản hồi nào, vì bạn thực sự cần một cách viết hoa đặc biệt của từng nền tảng / trình biên dịch theo trình biên dịch. (ví dụ: những gì Boost làm rất tốt.)
RM

2
Tuy nhiên, @Richard giải thích rằng bạn đạt được những gì bạn đạt được theo tiêu chuẩn bởi vì không có cách di động nào để làm tốt hơn. Nếu bạn muốn làm tốt hơn (đó là một mục tiêu cao quý), bạn sẽ phải chấp nhận một số lớn hơn hoặc ít hơn số lượng của sự ghê tởm :)
Hobbs

1
@Richard: Đôi khi bạn phải chấp nhận rằng có thể thực hiện triển khai C ++ tuân thủ các tiêu chuẩn không hữu ích. Vì các triển khai mà mọi người sử dụng cho bất kỳ thứ gì quan trọng được thiết kế để hữu ích, nên đôi khi bạn phải sống với những lập luận như "bất kỳ triển khai lành mạnh nào sẽ làm điều gì đó hợp lý". Tôi đã hy vọng rằng nó std::random_devicesẽ nằm trong danh mục đó, nhưng rõ ràng là không nếu một số triển khai thực tế sử dụng PRNG hạt giống cố định! Điều đó vượt xa lập luận của einpoklum.
Peter Cordes

14

Bạn có thể sử dụng a std::seed_seqvà điền nó đến ít nhất là kích thước trạng thái yêu cầu cho bộ tạo bằng phương pháp lấy entropy của Alexander Huszagh:

size_t sysrandom(void* dst, size_t dstlen); //from Alexander Huszagh answer above

void foo(){

    std::array<std::mt19937::UIntType, std::mt19937::state_size> state;
    sysrandom(state.begin(), state.length*sizeof(std::mt19937::UIntType));
    std::seed_seq s(state.begin(), state.end());

    std::mt19937 g;
    g.seed(s);
}

Nếu có một cách thích hợp để điền hoặc tạo SeedSequence từ UniformRandomBitGenerator trong thư viện tiêu chuẩn, sử dụng std::random_deviceđể gieo hạt đúng cách sẽ đơn giản hơn nhiều.



Không có gì trong tiêu chuẩn C ++ hoặc bất kỳ điều gì để đảm bảo rằng trình tạo số ngẫu nhiên sẽ sử dụng toàn bộ mảng khi bạn bắt nguồn từ seed_seq. Phương pháp này sẽ dẫn đến thất bại nếu bạn đang sử dụng rng cho một mô phỏng khoa học và rõ ràng là cho mật mã. Về trường hợp sử dụng duy nhất cho điều này sẽ là ngẫu nhiên một trò chơi điện tử, nhưng nó sẽ có mức quá mức cần thiết.
Kostas

5

Việc triển khai mà tôi đang thực hiện tận dụng state_sizetài sản của mt19937PRNG để quyết định số lượng hạt giống cần cung cấp khi khởi tạo:

using Generator = std::mt19937;

inline
auto const& random_data()
{
    thread_local static std::array<typename Generator::result_type, Generator::state_size> data;
    thread_local static std::random_device rd;

    std::generate(std::begin(data), std::end(data), std::ref(rd));

    return data;
}

inline
Generator& random_generator()
{
    auto const& data = random_data();

    thread_local static std::seed_seq seeds(std::begin(data), std::end(data));
    thread_local static Generator gen{seeds};

    return gen;
}

template<typename Number>
Number random_number(Number from, Number to)
{
    using Distribution = typename std::conditional
    <
        std::is_integral<Number>::value,
        std::uniform_int_distribution<Number>,
        std::uniform_real_distribution<Number>
    >::type;

    thread_local static Distribution dist;

    return dist(random_generator(), typename Distribution::param_type{from, to});
}

Tôi nghĩ rằng có chỗ để cải thiện vì std::random_device::result_typecó thể khác nhau std::mt19937::result_typevề kích thước và phạm vi nên điều đó thực sự cần được tính đến.

Lưu ý về std :: random_device .

Theo C++11(/14/17)(các) tiêu chuẩn:

26.5.6 Lớp random_device [ rand.device ]

2 Nếu các giới hạn triển khai ngăn cản việc tạo ra các số ngẫu nhiên không xác định, thì việc triển khai có thể sử dụng một công cụ số ngẫu nhiên.

Điều này có nghĩa là việc triển khai chỉ có thể tạo ra các giá trị xác định nếu nó bị ngăn cản việc tạo ra các giá trị không xác định bởi một số giới hạn.

Trình MinGWbiên dịch trên Windowsnổi tiếng không cung cấp các giá trị không xác định từ nó std::random_device, mặc dù chúng có sẵn dễ dàng từ Hệ điều hành. Vì vậy, tôi coi đây là một lỗi và không có khả năng xảy ra phổ biến trên các triển khai và nền tảng.


1
Điều này có thể lấp đầy trạng thái MT, nhưng vẫn chỉ dựa vào std::random_devicevà do đó dễ bị ảnh hưởng bởi các vấn đề bắt nguồn từ nó.
Richard

1
Tôi nghĩ rằng tôi đã trình bày chúng đủ rõ ràng trong câu hỏi. Tuy nhiên, rất vui khi được làm rõ / thảo luận.
Richard,

2
@Richard Có bất kỳ hệ thống thực sự nào không thực sự triển khai hợp lý std::random_devicekhông? Tôi biết tiêu chuẩn cho phép PRNGgiảm trở lại nhưng tôi cảm thấy điều đó chỉ để che đậy bản thân vì khó có thể yêu cầu mọi thiết bị sử dụng C++phải có nguồn ngẫu nhiên không xác định. Và nếu họ không làm vậy thì bạn có thể làm gì với điều đó?
Galik

5
@AlexanderHuszagh Tôi không chắc lắm. Ý định của tôi là làm cho "giải pháp di động" của tôi phụ thuộc vào thiết bị bởi vì nếu thiết bị hỗ trợ máy phát điện không xác định thì cũng nên std::random_device. Tôi tin rằng đó là tinh thần của tiêu chuẩn. Vì vậy, tôi đã tìm kiếm và chỉ có thể tìm thấy MinGWrằng bị hỏng ở khía cạnh này. Dường như không ai báo cáo vấn đề này với bất kỳ thứ gì khác mà tôi đã tìm thấy. Vì vậy, trong thư viện của tôi, tôi đã đánh dấu đơn giản MinGWlà không được hỗ trợ. Nếu có một vấn đề lớn hơn thì tôi sẽ suy nghĩ lại. Tôi chỉ không thấy bằng chứng về điều đó ngay bây giờ.
Galik

5
Tôi thực sự thất vọng vì MinGW đang hủy hoại std::random_devicemọi người bằng cách cung cấp nó ở dạng không mang lại khả năng ngẫu nhiên của nền tảng. Việc triển khai chất lượng thấp không đạt được mục đích của API hiện có. Sẽ tốt hơn IMO nếu họ không thực hiện nó cho đến khi họ có nó hoạt động. (Hoặc tốt hơn, nếu API cung cấp một cách để yêu cầu thất bại nếu chất lượng cao ngẫu nhiên đã không có sẵn, vì vậy MinGW có thể tránh gây rủi ro an ninh trong khi vẫn cho hạt giống khác nhau cho các trò chơi hoặc bất cứ điều gì.)
Peter Cordes

2

Không có gì sai khi gieo hạt theo thời gian, giả sử bạn không cần nó được bảo mật (và bạn đã không nói điều này là cần thiết). Sự hiểu biết sâu sắc là bạn có thể sử dụng hàm băm để khắc phục tính không ngẫu nhiên. Tôi thấy điều này hoạt động đầy đủ trong mọi trường hợp, bao gồm và đặc biệt đối với các mô phỏng Monte Carlo nặng.

Một tính năng hay của cách tiếp cận này là nó tổng quát hóa để khởi tạo từ các tập hợp hạt giống không thực sự ngẫu nhiên khác. Ví dụ: nếu bạn muốn mỗi luồng có RNG riêng (để an toàn cho luồng), bạn chỉ có thể khởi tạo dựa trên ID luồng đã băm.

Sau đây là một SSCCE , được chắt lọc từ codebase của tôi (để đơn giản hơn; một số cấu trúc hỗ trợ OO được giải thích):

#include <cstdint> //`uint32_t`
#include <functional> //`std::hash`
#include <random> //`std::mt19937`
#include <iostream> //`std::cout`

static std::mt19937 rng;

static void seed(uint32_t seed) {
    rng.seed(static_cast<std::mt19937::result_type>(seed));
}
static void seed() {
    uint32_t t = static_cast<uint32_t>( time(nullptr) );
    std::hash<uint32_t> hasher; size_t hashed=hasher(t);
    seed( static_cast<uint32_t>(hashed) );
}

int main(int /*argc*/, char* /*argv*/[]) {
    seed();
    std::uniform_int_distribution<> dis(0, 5);
    std::cout << dis(rng);
}

1
Tôi đồng ý với quan điểm của bạn rằng việc gieo hạt theo thời gian có lẽ đủ tốt trong thực tế, nếu bạn không cần nó phải an toàn. Nhưng tôi không thể đồng ý với phần còn lại của câu trả lời của bạn. Gieo hạt bằng băm của thời gian không tốt hơn gieo bằng chính thời gian.
DW

@DW Theo kinh nghiệm, nó tốt hơn nhiều . Lý do là hàm băm không liên tục và kéo dài một phạm vi giá trị rộng hơn nhiều (hãy tự thử điều này: gieo hạt với 12và quan sát rằng chuỗi các phao được tạo bởi chúng sẽ mất một thời gian để thực sự phân kỳ).
imallett, 14/07/17

Tôi không hiểu tại sao điều đó lại quan trọng. Chúng tôi chỉ chạy trên một hạt giống duy nhất tại một thời điểm. Không gian của các giá trị có thể có đối với hạt giống (entropy của hạt giống) cũng giống như vậy - băm không làm tăng entropi. Có lẽ bạn có thể chỉnh sửa câu hỏi để giải thích tại sao băm tốt hơn?
DW

0

Đây là câu hỏi của riêng tôi:

#include <random>
#include <chrono>
#include <cstdint>
#include <algorithm>
#include <functional>
#include <iostream>

uint32_t LilEntropy(){
  //Gather many potential forms of entropy and XOR them
  const  uint32_t my_seed = 1273498732; //Change during distribution
  static uint32_t i = 0;        
  static std::random_device rd; 
  const auto hrclock = std::chrono::high_resolution_clock::now().time_since_epoch().count();
  const auto sclock  = std::chrono::system_clock::now().time_since_epoch().count();
  auto *heap         = malloc(1);
  const auto mash = my_seed + rd() + hrclock + sclock + (i++) +
    reinterpret_cast<intptr_t>(heap)    + reinterpret_cast<intptr_t>(&hrclock) +
    reinterpret_cast<intptr_t>(&i)      + reinterpret_cast<intptr_t>(&malloc)  +
    reinterpret_cast<intptr_t>(&LilEntropy);
  free(heap);
  return mash;
}

//Fully seed the mt19937 engine using as much entropy as we can get our
//hands on
void SeedGenerator(std::mt19937 &mt){
  std::uint_least32_t seed_data[std::mt19937::state_size];
  std::generate_n(seed_data, std::mt19937::state_size, std::ref(LilEntropy));
  std::seed_seq q(std::begin(seed_data), std::end(seed_data));
  mt.seed(q);
}

int main(){
  std::mt19937 mt;
  SeedGenerator(mt);

  for(int i=0;i<100;i++)
    std::cout<<mt()<<std::endl;
}

Ý tưởng ở đây là sử dụng XOR để kết hợp nhiều nguồn entropy tiềm năng (thời gian nhanh, thời gian chậm, std::random-device vị trí biến tĩnh, vị trí đống, vị trí hàm, vị trí thư viện, giá trị theo chương trình cụ thể) để nỗ lực tối đa trong việc khởi tạo mt19937. Miễn là ít nhất một lần nguồn là "tốt", kết quả sẽ ít nhất là "tốt".

Câu trả lời này không ngắn gọn như mong muốn và có thể chứa một hoặc nhiều sai lầm về logic. Vì vậy, tôi đang coi nó là một công việc đang được tiến hành. Hãy bình luận nếu bạn có phản hồi.


3
Các địa chỉ có thể có rất ít ngẫu nhiên. Bạn luôn có các phân bổ giống nhau, vì vậy, trên các hệ thống nhúng nhỏ hơn, nơi bạn có quyền truy cập toàn bộ bộ nhớ, có thể sẽ nhận được kết quả giống nhau mỗi lần. Tôi muốn nói rằng nó có thể đủ tốt cho một hệ thống lớn, nhưng có thể làm hỏng việc trên một bộ vi điều khiển.
meneldal

1
Tôi đoán &i ^ &myseedsẽ có entropy ít hơn đáng kể so với một trong hai, vì cả hai đều là các đối tượng có thời lượng lưu trữ tĩnh trong cùng một đơn vị dịch và do đó có khả năng khá gần nhau. Và bạn dường như không thực sự sử dụng giá trị đặc biệt từ việc khởi tạo myseed?
aschepler 13/07/17

7
Chuyển đổi con trỏ giao dịch thành int là hành vi không xác định; làm điều đó trong khi nó vẫn tồn tại. ^là một bộ kết hợp băm khủng khiếp; nếu cả hai giá trị đều có nhiều entropy, nhưng ít so với nhau, nó sẽ loại bỏ nó. +thường tốt hơn (vì x + x chỉ đốt cháy 1 bit entropy trong x, trong khi x ^ x đốt cháy tất cả). Chức năng không phải là thead an toàn Tôi nghi ngờ ( rd())
Yakk - Adam Nevraumont

2
Ồ và +ý tôi là không có chữ ký ( +trên chữ ký là UB-mồi). Trong khi đây là những trường hợp UB hơi nực cười, bạn đã nói là xách tay. Cũng xem xét việc nhận địa chỉ của một hàm như là một giá trị không thể thiếu nếu có thể (không chắc chắn nếu nó là gì?)
Yakk - Adam Nevraumont

1
@meneldal: Ngay cả trên một PC cường độ cao, mặc dù việc phân bổ có thể có các vị trí vật lý khác nhau (tùy thuộc vào trạng thái của máy bên ngoài quy trình), các con trỏ được trừu tượng hóa bởi không gian địa chỉ ảo của quy trình và có khả năng lặp lại cao, đặc biệt là ASLR không có hiệu lực.
Ben Voigt

0
  • Sử dụng getentropy () để gieo một trình tạo số giả ngẫu nhiên (PRNG).
  • Sử dụng getrandom () nếu bạn muốn các giá trị ngẫu nhiên (thay vì, giả sử /dev/urandomhoặc /dev/random).

Chúng có sẵn trên các hệ thống giống UNIX hiện đại, chẳng hạn như Linux, Solaris và OpenBSD.


-2

Một nền tảng nhất định có thể có một nguồn entropy, chẳng hạn như /dev/random. Nanoseconds kể từ Epoch với std::chrono::high_resolution_clock::now()có lẽ là hạt giống tốt nhất trong Thư viện chuẩn.

Trước đây tôi đã sử dụng một cái gì đó như (uint64_t)( time(NULL)*CLOCKS_PER_SEC + clock() )để lấy thêm bit entropy cho các ứng dụng không quan trọng về bảo mật.


2
Bạn thực sự nên sử dụng /dev/urandom, đặc biệt là trong trường hợp như thế này. /dev/randomvà thường không có lý do chính đáng để làm như vậy ([chèn lời giải thích dài về việc có bao nhiêu hệ điều hành khác nhau ước tính độ ngẫu nhiên của các byte được tạo bởi / dev / random]).
Alexander Huszagh

2
@AlexanderHuszagh Đúng, mặc dù tôi đã phải viết mã trên các hệ thống /dev/urandomkhông tồn tại và giải pháp thay thế cho việc chặn là thuyết xác định. Một hộp có thể có /dev/hwrnghoặc /dev/hw_randomtốt hơn nữa.
Davislor

Được rồi, tôi nói, “như /dev/random”, và điều đó dường như đã làm dấy lên một cuộc thánh chiến về /dev/randomso với /dev/urandomtrên Linux mà tôi không có ý định khi tôi đưa ra ví dụ rằng ..
Davislor
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.