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()
函数返回的整数值在0
到RAND_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
提供非阻塞式的访问,即使没有足够的熵也会继续返回数据,但它的输出仍然被认为是足够安全的,特别是在系统启动后经过一段时间。
-
Windows:
BCryptGenRandom
函数- 这个 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),这使得它可以生成高质量的随机数,这些随机数对于加密应用来说至关重要。
现在大部分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/urandom
或BCryptGenRandom
),同时也支持自定义 CSPRNG。
- 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。