问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

你还在用rand()生成随机数?

创作时间:
作者:
@小白创作中心

你还在用rand()生成随机数?

引用
CSDN
1.
https://m.blog.csdn.net/qq_23905237/article/details/144835902

在编程中,随机数的生成是一个常见需求,但C语言标准库中的rand()函数却存在诸多局限性。本文将深入探讨rand()函数的缺陷,并介绍更安全、更高质量的随机数生成方法,包括C11标准库、C++11、操作系统API以及专门的加密随机数生成器。

rand()的缺陷

伪随机数生成器使用数学算法来产生具有良好统计特性的数字序列,但这些数字并非真正随机。C标准库中的rand()函数并不保证所生成的随机序列的质量。某些rand()实现生成的数字周期较短,且这些数字是可以预测的。对于有强伪随机数要求的应用程序,必须使用已知能满足其需求的生成器。

#include <stdio.h>
#include <stdlib.h>

enum { len = 12 };

void func(void) {
  /*
   * id will hold the ID, starting with the characters
   *  "ID" followed by a random integer.
   */
  char id[len]; 
  int r;
  int num;
  /* ... */
  r = rand();  /* Generate a random integer */
  num = snprintf(id, len, "ID%-d", r);  /* Generate the ID */
  /* ... */
}

rand()自身的局限性

  • 有限范围rand()函数返回的整数值在0RAND_MAX之间,而RAND_MAX至少为32767(即 2^15 - 1)。对于某些应用来说,这个范围可能太小。
  • 低质量随机性rand()的实现通常基于线性同余生成器(LCG),它提供的随机序列的质量较差,特别是低位上的周期性较强,容易被预测。
  • 缺乏可移植性:不同平台上rand()的具体实现和特性可能有所不同,这会影响程序行为的一致性。
  • 种子问题:如果使用相同的种子(通过srand()设置),rand()将总是产生相同的序列。这虽然可以用于调试,但在需要真正随机性的场合是一个隐患。
#include <stdio.h>
#include <stdlib.h> // 包含 rand() 和 srand()
#include <time.h>   // 包含 time()
void print_random_sequence(int seed, int count) {
    printf("Generating random sequence with seed %d:\n", seed);
    srand(seed); // 使用给定的种子初始化随机数生成器
    for (int i = 0; i < count; ++i) {
        printf("%d ", rand() % 100); // 打印 0 到 99 之间的随机数
    }
    printf("\n");
}
int main() {
    int seed = 12345; // 设定一个固定的种子
    int count = 10;   // 每次打印的随机数个数
    // 第一次生成随机序列
    print_random_sequence(seed, count);
    // 第二次生成同样的随机序列
    print_random_sequence(seed, count);
    return 0;
}

运行结果:

Generating random sequence with seed 250:
22 69 7 43 87 96 63 22 73 1 
Generating random sequence with seed 250:
22 69 7 43 87 96 63 22 73 1 

安全问题

  • 加密安全性不足rand()不适合用于生成加密密钥或其它对安全性要求高的随机数据。因为它的输出是可以被预测的,攻击者可以通过观察部分输出推断出后续值。
  • 不可控的种子来源:默认情况下,rand()使用的时间作为种子,但这很容易受到时间攻击,特别是在多线程环境中,多个调用可能会发生在同一秒内,导致相同的种子。

合适的替代方案

为了获得更好的随机性,建议使用以下替代方法之一:

  • C11标准库:如果你的编译环境支持C11或更新的标准,可以使用<stdlib.h>中引入的新函数如rand_r()random(),或者更推荐的是使用<stdalign.h><random>库中的设施。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
enum
{
    len = 12
};
void func(void)
{
    /*
     * id will hold the ID, starting with the characters
     *  "ID" followed by a random integer.
     */
    char id[len];
    int r;
    int num;
    /* ... */
    struct timespec ts;
    if (timespec_get(&ts, TIME_UTC) == 0)
    {
        /* Handle error */
    }
    srandom(ts.tv_nsec ^ ts.tv_sec); /* Seed the PRNG */
    /* ... */
    r = random();                        /* Generate a random integer */
    num = snprintf(id, len, "ID%-d", r); /* Generate the ID */
                                         /* ... */
}
  • C++11及以上版本:在C++中,应该使用<random>头文件提供的高级随机数生成工具,例如std::mt19937(Mersenne Twister)或其他引擎与分布相结合。

  • 操作系统提供的接口:一些操作系统提供了专门的随机数生成API,比如Linux上的/dev/urandom或Windows上的BCryptGenRandom等,这些API通常更适合安全需求较高的应用。

#include <iostream>
#include <random>
#include <array>
#include <fstream>
// 如果是在Windows上,需要包含以下头文件
#ifdef _WIN32
#include <windows.h>
#include <bcrypt.h>
#pragma comment(lib, "Bcrypt.lib")
#endif
void generate_random_bytes(std::vector<uint8_t>& buffer) {
#if defined(_WIN32)
    // Windows平台使用BCryptGenRandom
    NTSTATUS status = BCryptGenRandom(NULL, buffer.data(), static_cast<ULONG>(buffer.size()), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
    if (status != STATUS_SUCCESS) {
        throw std::runtime_error("BCryptGenRandom failed");
    }
#else
    // Linux和其他Unix系统使用/dev/urandom
    std::ifstream urandom("/dev/urandom", std::ios::binary);
    if (!urandom.is_open()) {
        throw std::runtime_error("Failed to open /dev/urandom");
    }
    urandom.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
    if (!urandom) {
        throw std::runtime_error("Failed to read from /dev/urandom");
    }
#endif
}
int main() {
    try {
        // 定义一个缓冲区来存储随机字节
        std::vector<uint8_t> randomBytes(16); // 例如,16字节
        // 生成随机字节
        generate_random_bytes(randomBytes);
        // 打印生成的随机字节
        std::cout << "Generated random bytes: ";
        for (uint8_t byte : randomBytes) {
            std::cout << std::hex << static_cast<int>(byte) << ' ';
        }
        std::cout << std::dec << '\n';
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << '\n';
        return 1;
    }
    return 0;
}

加密场景随机数生成

加密库在生成随机数时通常依赖于操作系统提供的高质量熵源,或者使用专门设计的密码学安全伪随机数生成器(CSPRNG)。这些方法确保了生成的随机数具有足够的不可预测性和随机性,以满足加密应用的需求。以下是几种常见的做法:

操作系统提供的熵源

许多现代操作系统都提供了专门用于获取高熵随机数据的接口,这些接口通常被认为是安全的,因为它们从多个难以预测的硬件和软件事件中收集熵。例如:

  • Linux和Unix系统/dev/random/dev/urandom

  • /dev/random提供阻塞式的访问,只有当有足够的熵时才会返回数据,这保证了最高级别的安全性。

  • /dev/urandom提供非阻塞式的访问,即使没有足够的熵也会继续返回数据,但它的输出仍然被认为是足够安全的,特别是在系统启动后经过一段时间。

  • WindowsBCryptGenRandom函数

  • 这个API是Windows CryptoAPI的一部分,提供了一种简单而安全的方式从操作系统获取随机字节。它内部使用了一个基于ChaCha20或AES-CTR的CSPRNG。

密码学安全伪随机数生成器 (CSPRNG)

CSPRNG是专门为加密应用设计的伪随机数生成器,它们的特点是即使攻击者知道部分输出或状态,也很难推断出未来的输出。一些常见的CSPRNG包括:

  • ChaCha20:一种快速且安全的流密码,也可以用作CSPRNG。
  • AES-CTR:高级加密标准(AES)算法在计数器模式下的实现,可以作为CSPRNG使用。
  • HMAC_DRBG:基于哈希消息认证码(HMAC)的确定性随机位生成器(DRBG),常用于FIPS 140-2标准中。
  • Hash_DRBG:基于安全哈希函数(如SHA-256)的DRBG。
  • CTR_DRBG:基于对称密钥算法(如AES)的DRBG,在计数器模式下运行。

硬件随机数生成器 (HRNG)

比如可信平台模块(Trusted Platform Module, TPM)

TPM是一种专门设计用于增强计算机安全性的硬件组件,它提供了一系列的安全功能,其中包括密码学操作、密钥生成和存储等。TPM内置了硬件随机数生成器(HRNG),这使得它可以生成高质量的随机数,这些随机数对于加密应用来说至关重要。

现在大部分PC或设备都配备有TPM2.0芯片,可以通过TSS库调用TPM生成随机数。

#include <tss2/tss2_sys.h>
#include <iostream>
#include <vector>
void generate_random_bytes_from_tpm(TSS2_SYS_CONTEXT *sysContext, size_t numBytes) {
![](https://wy-static.wenxiaobai.com/chat-rag-image/7228119747219994758)
    std::vector<uint8_t> randomBytes(numBytes);
    TPM2B_DIGEST *outData = reinterpret_cast<TPM2B_DIGEST*>(randomBytes.data());
    outData->size = static_cast<UINT16>(numBytes);
    TSS2_RC rc = Tss2_Sys_GetRandom(sysContext, NULL, numBytes, outData, NULL);
    if (rc != TSS2_RC_SUCCESS) {
        throw std::runtime_error("Failed to get random bytes from TPM");
    }
    std::cout << "Random bytes from TPM: ";
    for (uint8_t byte : randomBytes) {
        std::cout << std::hex << static_cast<int>(byte) << ' ';
    }
    std::cout << std::dec << '\n';
}
int main() {
    // 初始化TPM系统上下文和其他必要的设置...
    TSS2_SYS_CONTEXT *sysContext;
    // ...这里省略了初始化代码...
    try {
        generate_random_bytes_from_tpm(sysContext, 16); // 例如,生成16字节的随机数
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << '\n';
        return 1;
    }
    // 清理资源...
    // ...这里省略了清理代码...
    return 0;
}

加密库的选择

不同的加密库可能会选择不同的方式来生成随机数,具体取决于其设计目标和应用场景。例如:

  • OpenSSL:默认情况下使用操作系统的熵源(如/dev/urandomBCryptGenRandom),同时也支持自定义CSPRNG。
  • Libsodium:强烈推荐使用操作系统提供的CSPRNG,并通过封装好的API提供给开发者使用。
  • BoringSSL:Google开发的OpenSSL分支,同样依赖于操作系统提供的CSPRNG。
  • Botan:一个全面的加密库,提供了多种CSPRNG实现,包括HMAC_DRBG和Hash_DRBG。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号