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

1. 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() 使用的时间作为种子,但这很容易受到时间攻击,特别是在多线程环境中,多个调用可能会发生在同一秒内,导致相同的种子。
2. 合适的替代方案

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

  • 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;
}
3.加密场景随机数生成

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

3.1.操作系统提供的熵源

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

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

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

    • 这个 API 是 Windows CryptoAPI 的一部分,提供了一种简单而安全的方式从操作系统获取随机字节。它内部使用了一个基于 ChaCha20 或 AES-CTR 的 CSPRNG。
3.2.密码学安全伪随机数生成器 (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,在计数器模式下运行。
3.3.硬件随机数生成器 (HRNG)

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

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

img

在这里插入图片描述

现在大部分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) {
    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;
}
3.4加密库的选择

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

  • OpenSSL:默认情况下使用操作系统的熵源(如 /dev/urandomBCryptGenRandom),同时也支持自定义 CSPRNG。

img

  • Libsodium:强烈推荐使用操作系统提供的 CSPRNG,并通过封装好的 API 提供给开发者使用。
  • BoringSSL:Google 开发的 OpenSSL 分支,同样依赖于操作系统提供的 CSPRNG。
  • Botan:一个全面的加密库,提供了多种 CSPRNG 实现,包括 HMAC_DRBG 和 Hash_DRBG。

enRandom`),同时也支持自定义 CSPRNG。

[外链图片转存中…(img-qiYX5YpX-1735569568267)]

  • Libsodium:强烈推荐使用操作系统提供的 CSPRNG,并通过封装好的 API 提供给开发者使用。
  • BoringSSL:Google 开发的 OpenSSL 分支,同样依赖于操作系统提供的 CSPRNG。
  • Botan:一个全面的加密库,提供了多种 CSPRNG 实现,包括 HMAC_DRBG 和 Hash_DRBG。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GarenJian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值