getrandom 函数详解 見出しへのリンク
1. 函数介绍 見出しへのリンク
getrandom
是 Linux 系统中用于获取高质量随机数的系统调用。可以把这个函数想象成一个"真随机数生成器"——它从系统的熵池中获取真正的随机数据,就像从大自然的噪声中提取随机性一样。
在计算机系统中,随机数非常重要:
- 加密操作: 生成密钥、初始化向量等
- 安全令牌: 会话 ID、验证码等
- 模拟和游戏: 游戏中的随机事件
- 负载均衡: 分布式系统中的随机选择
getrandom
提供了比传统 rand()
函数更安全、更高质量的随机数,特别适合安全相关的应用。
2. 函数原型 見出しへのリンク
#include <sys/random.h>
ssize_t getrandom(void *buf, size_t buflen, unsigned int flags);
3. 功能 見出しへのリンク
getrandom
函数用于从内核的随机数生成器中获取随机字节,并将其存储在提供的缓冲区中。它可以直接访问系统的熵源,提供密码学安全的随机数。
4. 参数 見出しへのリンク
- buf: 指向缓冲区的指针,用于存储生成的随机数据
- buflen: 请求的随机数据字节数
- flags: 控制随机数生成行为的标志位
5. 标志位(flags 参数) 見出しへのリンク
标志 | 值 | 说明 |
---|---|---|
GRND_RANDOM | 0x0001 | 使用 /dev/random 而不是 /dev/urandom |
GRND_NONBLOCK | 0x0002 | 非阻塞模式,熵不足时不等待 |
6. 标志位详细说明 見出しへのリンク
GRND_RANDOM 見出しへのリンク
- 默认情况下,
getrandom
使用/dev/urandom
(非阻塞) - 设置此标志后,使用
/dev/random
(阻塞模式) /dev/random
在熵不足时会阻塞,直到收集到足够熵
GRND_NONBLOCK 見出しへのリンク
- 默认情况下,如果熵池未初始化,函数会阻塞等待
- 设置此标志后,如果熵不足则立即返回错误(errno = EAGAIN)
7. 返回值 見出しへのリンク
- 成功: 返回实际获取的随机字节数
- 失败: 返回 -1,并设置相应的 errno 错误码
常见错误码:
EAGAIN
: 非阻塞模式下熵不足EFAULT
: buf 指针无效EINVAL
: flags 参数无效EIO
: 系统错误
8. 相似函数或关联函数 見出しへのリンク
- /dev/random: 传统的阻塞随机数设备
- /dev/urandom: 传统的非阻塞随机数设备
- rand(): 标准库伪随机数生成器
- random(): 更好的伪随机数生成器
- arc4random(): BSD 风格的加密安全随机数
- openssl RAND_bytes(): OpenSSL 库的随机数函数
9. 示例代码 見出しへのリンク
示例1:基础用法 - 生成随机数据 見出しへのリンク
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/random.h>
#include <errno.h>
#include <string.h>
// 将二进制数据转换为十六进制字符串
void bin_to_hex(const unsigned char *bin, size_t len, char *hex) {
for (size_t i = 0; i < len; i++) {
sprintf(hex + i * 2, "%02x", bin[i]);
}
hex[len * 2] = '\0';
}
// 将二进制数据转换为可打印字符(用于显示)
void bin_to_printable(const unsigned char *bin, size_t len, char *printable) {
for (size_t i = 0; i < len; i++) {
if (bin[i] >= 32 && bin[i] <= 126) {
printable[i] = bin[i];
} else {
printable[i] = '.';
}
}
printable[len] = '\0';
}
int main() {
unsigned char random_data[32];
char hex_string[65];
char printable_string[33];
ssize_t bytes_read;
printf("=== getrandom 基础示例 ===\n\n");
// 生成 32 字节的随机数据
printf("生成 32 字节随机数据...\n");
bytes_read = getrandom(random_data, sizeof(random_data), 0);
if (bytes_read == -1) {
perror("getrandom");
return 1;
}
printf("成功生成 %zd 字节随机数据\n", bytes_read);
// 显示十六进制格式
bin_to_hex(random_data, bytes_read, hex_string);
printf("十六进制: %s\n", hex_string);
// 显示可打印字符格式
bin_to_printable(random_data, bytes_read, printable_string);
printf("可打印: %s\n", printable_string);
// 生成不同长度的随机数据
printf("\n生成不同长度的随机数据:\n");
for (int len = 1; len <= 16; len++) {
unsigned char small_data[16];
bytes_read = getrandom(small_data, len, 0);
if (bytes_read > 0) {
bin_to_hex(small_data, bytes_read, hex_string);
printf(" %2d 字节: %s\n", len, hex_string);
}
}
return 0;
}
示例2:不同标志位的使用 見出しへのリンク
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/random.h>
#include <errno.h>
#include <string.h>
#include <time.h>
// 显示随机数据的统计信息
void show_random_stats(const unsigned char *data, size_t len) {
unsigned long sum = 0;
for (size_t i = 0; i < len; i++) {
sum += data[i];
}
double average = (double)sum / len;
printf(" 平均值: %.2f\n", average);
printf(" 数据范围: 0x%02x - 0x%02x\n",
data[0], data[len-1]); // 简化的范围显示
}
int main() {
unsigned char buffer[64];
ssize_t result;
struct timespec start, end;
printf("=== getrandom 标志位使用示例 ===\n\n");
// 1. 默认模式(推荐)
printf("1. 默认模式 (GRND_URANDOM):\n");
clock_gettime(CLOCK_MONOTONIC, &start);
result = getrandom(buffer, sizeof(buffer), 0);
clock_gettime(CLOCK_MONOTONIC, &end);
if (result > 0) {
printf(" 成功获取 %zd 字节随机数据\n", result);
printf(" 耗时: %ld 纳秒\n",
(end.tv_sec - start.tv_sec) * 1000000000L +
(end.tv_nsec - start.tv_nsec));
show_random_stats(buffer, result);
} else {
perror(" getrandom 失败");
}
// 2. 使用 GRND_RANDOM(阻塞模式)
printf("\n2. GRND_RANDOM 模式 (使用 /dev/random):\n");
printf(" 注意: 如果熵不足可能会阻塞\n");
clock_gettime(CLOCK_MONOTONIC, &start);
result = getrandom(buffer, 16, GRND_RANDOM);
clock_gettime(CLOCK_MONOTONIC, &end);
if (result > 0) {
printf(" 成功获取 %zd 字节随机数据\n", result);
printf(" 耗时: %ld 纳秒\n",
(end.tv_sec - start.tv_sec) * 1000000000L +
(end.tv_nsec - start.tv_nsec));
} else {
perror(" getrandom 失败");
}
// 3. 非阻塞模式
printf("\n3. 非阻塞模式 (GRND_NONBLOCK):\n");
result = getrandom(buffer, sizeof(buffer), GRND_NONBLOCK);
if (result > 0) {
printf(" 成功获取 %zd 字节随机数据\n", result);
} else if (result == -1 && errno == EAGAIN) {
printf(" 熵不足,非阻塞模式下立即返回\n");
} else {
perror(" getrandom 失败");
}
// 4. 组合标志
printf("\n4. 组合标志 (GRND_RANDOM | GRND_NONBLOCK):\n");
result = getrandom(buffer, 16, GRND_RANDOM | GRND_NONBLOCK);
if (result > 0) {
printf(" 成功获取 %zd 字节随机数据\n", result);
} else if (result == -1 && errno == EAGAIN) {
printf(" /dev/random 熵不足,非阻塞模式下立即返回\n");
} else {
perror(" getrandom 失败");
}
printf("\n=== 使用建议 ===\n");
printf("1. 一般情况下使用默认模式(flags=0)\n");
printf("2. 加密应用可以考虑 GRND_RANDOM\n");
printf("3. 非阻塞场景使用 GRND_NONBLOCK\n");
printf("4. 避免在循环中频繁调用\n");
return 0;
}
示例3:完整的随机数工具 見出しへのリンク
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/random.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
// 输出格式类型
enum output_format {
FORMAT_HEX, // 十六进制
FORMAT_BASE64, // Base64
FORMAT_BINARY, // 二进制
FORMAT_DECIMAL // 十进制
};
// 将二进制数据转换为十六进制
void to_hex(const unsigned char *data, size_t len, char *output) {
for (size_t i = 0; i < len; i++) {
sprintf(output + i * 2, "%02x", data[i]);
}
output[len * 2] = '\0';
}
// 简化的 Base64 编码(仅用于演示)
void to_base64(const unsigned char *data, size_t len, char *output) {
const char *base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t i = 0, j = 0;
while (i < len) {
uint32_t octet_a = i < len ? data[i++] : 0;
uint32_t octet_b = i < len ? data[i++] : 0;
uint32_t octet_c = i < len ? data[i++] : 0;
uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;
output[j++] = base64_chars[(triple >> 18) & 63];
output[j++] = base64_chars[(triple >> 12) & 63];
output[j++] = base64_chars[(triple >> 6) & 63];
output[j++] = base64_chars[triple & 63];
}
// 处理填充
int pad = len % 3;
if (pad == 1) {
output[j-2] = '=';
output[j-1] = '=';
} else if (pad == 2) {
output[j-1] = '=';
}
output[j] = '\0';
}
// 显示帮助信息
void show_help(const char *program_name) {
printf("用法: %s [选项]\n", program_name);
printf("\n选项:\n");
printf(" -n, --bytes=NUM 生成 NUM 字节的随机数据 (默认 32)\n");
printf(" -f, --format=TYPE 输出格式: hex, base64, binary, decimal\n");
printf(" -r, --random 使用 /dev/random (阻塞模式)\n");
printf(" -b, --non-block 非阻塞模式\n");
printf(" -h, --help 显示此帮助信息\n");
printf("\n输出格式说明:\n");
printf(" hex - 十六进制字符串\n");
printf(" base64 - Base64 编码\n");
printf(" binary - 原始二进制数据\n");
printf(" decimal - 十进制数字序列\n");
}
int main(int argc, char *argv[]) {
size_t num_bytes = 32;
enum output_format format = FORMAT_HEX;
unsigned int flags = 0;
int c;
// 解析命令行参数
static struct option long_options[] = {
{"bytes", required_argument, 0, 'n'},
{"format", required_argument, 0, 'f'},
{"random", no_argument, 0, 'r'},
{"non-block", no_argument, 0, 'b'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "n:f:rbh", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'n':
num_bytes = atoi(optarg);
if (num_bytes == 0) {
fprintf(stderr, "错误: 无效的字节数\n");
return 1;
}
break;
case 'f':
if (strcmp(optarg, "hex") == 0) {
format = FORMAT_HEX;
} else if (strcmp(optarg, "base64") == 0) {
format = FORMAT_BASE64;
} else if (strcmp(optarg, "binary") == 0) {
format = FORMAT_BINARY;
} else if (strcmp(optarg, "decimal") == 0) {
format = FORMAT_DECIMAL;
} else {
fprintf(stderr, "错误: 未知的格式 '%s'\n", optarg);
return 1;
}
break;
case 'r':
flags |= GRND_RANDOM;
break;
case 'b':
flags |= GRND_NONBLOCK;
break;
case 'h':
show_help(argv[0]);
return 0;
case '?':
return 1;
}
}
// 分配缓冲区
unsigned char *buffer = malloc(num_bytes);
if (!buffer) {
fprintf(stderr, "错误: 内存分配失败\n");
return 1;
}
// 生成随机数据
ssize_t result = getrandom(buffer, num_bytes, flags);
if (result == -1) {
if (errno == EAGAIN && (flags & GRND_NONBLOCK)) {
fprintf(stderr, "错误: 熵不足(非阻塞模式)\n");
} else {
perror("getrandom");
}
free(buffer);
return 1;
}
// 根据格式输出
switch (format) {
case FORMAT_HEX: {
char *hex_output = malloc(result * 2 + 1);
if (hex_output) {
to_hex(buffer, result, hex_output);
printf("%s\n", hex_output);
free(hex_output);
}
break;
}
case FORMAT_BASE64: {
char *base64_output = malloc((result * 4 / 3) + 4);
if (base64_output) {
to_base64(buffer, result, base64_output);
printf("%s\n", base64_output);
free(base64_output);
}
break;
}
case FORMAT_BINARY:
fwrite(buffer, 1, result, stdout);
break;
case FORMAT_DECIMAL:
for (size_t i = 0; i < result; i++) {
printf("%d ", buffer[i]);
}
printf("\n");
break;
}
free(buffer);
return 0;
}
示例4:加密安全的随机数应用 見出しへのリンク
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/random.h>
#include <string.h>
#include <time.h>
// 生成加密安全的会话 ID
char* generate_session_id(size_t length) {
static const char charset[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
char *session_id = malloc(length + 1);
if (!session_id) return NULL;
unsigned char random_bytes[length];
ssize_t result = getrandom(random_bytes, length, 0);
if (result != (ssize_t)length) {
free(session_id);
return NULL;
}
for (size_t i = 0; i < length; i++) {
session_id[i] = charset[random_bytes[i] % (sizeof(charset) - 1)];
}
session_id[length] = '\0';
return session_id;
}
// 生成加密密钥
int generate_crypto_key(unsigned char *key, size_t key_size) {
ssize_t result = getrandom(key, key_size, GRND_NONBLOCK);
return (result == (ssize_t)key_size) ? 0 : -1;
}
// 生成盐值
int generate_salt(unsigned char *salt, size_t salt_size) {
return (getrandom(salt, salt_size, 0) == (ssize_t)salt_size) ? 0 : -1;
}
// 显示密钥(以十六进制格式)
void print_key(const unsigned char *key, size_t key_size, const char *label) {
printf("%s: ", label);
for (size_t i = 0; i < key_size; i++) {
printf("%02x", key[i]);
}
printf("\n");
}
int main() {
printf("=== 加密安全随机数应用示例 ===\n\n");
// 1. 生成会话 ID
printf("1. 生成会话 ID:\n");
for (int i = 0; i < 5; i++) {
char *session_id = generate_session_id(32);
if (session_id) {
printf(" 会话 ID %d: %s\n", i + 1, session_id);
free(session_id);
}
}
// 2. 生成加密密钥
printf("\n2. 生成加密密钥:\n");
unsigned char aes_key[32]; // 256-bit AES key
unsigned char hmac_key[64]; // 512-bit HMAC key
if (generate_crypto_key(aes_key, sizeof(aes_key)) == 0) {
print_key(aes_key, sizeof(aes_key), "AES-256 密钥");
} else {
printf(" AES 密钥生成失败\n");
}
if (generate_crypto_key(hmac_key, sizeof(hmac_key)) == 0) {
print_key(hmac_key, sizeof(hmac_key), "HMAC 密钥");
} else {
printf(" HMAC 密钥生成失败\n");
}
// 3. 生成盐值
printf("\n3. 生成盐值:\n");
unsigned char salt[16];
for (int i = 0; i < 3; i++) {
if (generate_salt(salt, sizeof(salt)) == 0) {
printf(" 盐值 %d: ", i + 1);
for (size_t j = 0; j < sizeof(salt); j++) {
printf("%02x", salt[j]);
}
printf("\n");
}
}
// 4. 生成初始化向量 (IV)
printf("\n4. 生成初始化向量 (IV):\n");
unsigned char iv[16]; // AES block size
if (getrandom(iv, sizeof(iv), 0) == sizeof(iv)) {
print_key(iv, sizeof(iv), "AES IV");
}
// 5. 性能测试
printf("\n5. 性能测试:\n");
const size_t test_size = 1024;
unsigned char *test_buffer = malloc(test_size);
if (test_buffer) {
struct timespec start, end;
const int iterations = 1000;
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < iterations; i++) {
if (getrandom(test_buffer, test_size, GRND_NONBLOCK) != test_size) {
printf(" 测试失败\n");
break;
}
}
clock_gettime(CLOCK_MONOTONIC, &end);
long long duration = (end.tv_sec - start.tv_sec) * 1000000000LL +
(end.tv_nsec - start.tv_nsec);
double avg_time = (double)duration / iterations / 1000000.0; // 毫秒
printf(" 生成 %zu 字节随机数据 %d 次\n", test_size, iterations);
printf(" 平均耗时: %.3f 毫秒\n", avg_time);
printf(" 吞吐量: %.2f MB/s\n",
(test_size * iterations) / (duration / 1000.0) / 1024.0);
free(test_buffer);
}
printf("\n=== 安全建议 ===\n");
printf("1. 使用 getrandom 而不是 rand() 生成安全随机数\n");
printf("2. 对于加密密钥,考虑使用 GRND_RANDOM\n");
printf("3. 避免在循环中频繁调用 getrandom\n");
printf("4. 检查返回值确保成功获取随机数据\n");
printf("5. 不要将随机数据存储在可预测的位置\n");
return 0;
}
编译和运行说明 見出しへのリンク
# 编译示例程序
gcc -o getrandom_example1 example1.c
gcc -o getrandom_example2 example2.c
gcc -o getrandom_example3 example3.c
gcc -o getrandom_example4 example4.c
# 运行示例
./getrandom_example1
./getrandom_example2
./getrandom_example3 --help
./getrandom_example3 -n 16 -f hex
./getrandom_example3 -n 32 -f base64
./getrandom_example4
系统要求检查 見出しへのリンク
# 检查内核版本(需要 3.17+)
uname -r
# 检查 glibc 版本(需要 2.25+)
ldd --version
# 检查 /dev/random 和 /dev/urandom
ls -l /dev/random /dev/urandom
重要注意事项 見出しへのリンク
- 内核版本: 需要 Linux 3.17+ 内核支持
- glibc 版本: 需要 glibc 2.25+ 支持
- 性能考虑: 避免频繁调用,批量获取随机数据
- 安全性: 比
rand()
等伪随机数生成器更安全 - 阻塞行为: 默认非阻塞,GRND_RANDOM 可能阻塞
- 错误处理: 始终检查返回值和 errno
与其他随机数生成器的比较 見出しへのリンク
特性 | getrandom | /dev/urandom | rand() | random() |
---|---|---|---|---|
安全性 | 高 | 高 | 低 | 低 |
阻塞 | 可选 | 否 | 否 | 否 |
性能 | 中等 | 中等 | 高 | 高 |
可移植性 | Linux 特有 | Unix-like | 标准 C | BSD 扩展 |
加密适用 | 是 | 是 | 否 | 否 |
实际应用场景 見出しへのリンク
- 加密密钥生成: 生成 AES、RSA 等加密密钥
- 会话管理: 生成会话 ID、CSRF 令牌
- 密码盐值: 为密码哈希生成随机盐值
- 初始化向量: 生成加密算法的 IV
- 临时文件名: 生成唯一的临时文件名
- 游戏随机数: 游戏中的随机事件生成
最佳实践 見出しへのリンク
- 默认使用: 一般情况下使用
getrandom(buffer, size, 0)
- 加密场景: 重要加密操作可考虑
GRND_RANDOM
- 批量获取: 一次获取较多随机数据,避免频繁调用
- 错误检查: 始终检查返回值确保成功
- 内存清理: 使用后及时清理敏感的随机数据
这些示例展示了 getrandom
函数的各种使用方法,从基础的随机数据生成到完整的安全应用,帮助你全面掌握这个重要的安全随机数生成接口。