getrandom系统调用及示例

getrandom 函数详解

getrandom是Linux系统中获取高质量随机数的系统调用,从内核熵池中提取真正的随机数据。它比传统rand()更安全可靠,适用于加密、安全令牌等场景。函数原型为ssize_t getrandom(void *buf, size_t buflen, unsigned int flags),支持GRND_RANDOM(阻塞模式)和GRND_NONBLOCK(非阻塞模式)两种标志位。文章详细介绍了函数参数、返回值、错误处理和示例代码,展示了如何生成随机数据和使用不同标志位。getrandom相比传统随机数设备更安全高效,是安全敏感应用的理想选择。

  1. 函数介绍

getrandom 是 Linux 系统中用于获取高质量随机数的系统调用。可以把这个函数想象成一个”真随机数生成器”——它从系统的熵池中获取真正的随机数据,就像从大自然的噪声中提取随机性一样。

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

在计算机系统中,随机数非常重要:

  • 加密操作: 生成密钥、初始化向量等

  • 安全令牌: 会话 ID、验证码等

  • 模拟和游戏: 游戏中的随机事件

  • 负载均衡: 分布式系统中的随机选择

getrandom 提供了比传统 rand() 函数更安全、更高质量的随机数,特别适合安全相关的应用。

  1. 函数原型
1
2
3
4
#include <sys/random.h>

ssize_t getrandom(void *buf, size_t buflen, unsigned int flags);

  1. 功能

getrandom 函数用于从内核的随机数生成器中获取随机字节,并将其存储在提供的缓冲区中。它可以直接访问系统的熵源,提供密码学安全的随机数。

  1. 参数
  • buf: 指向缓冲区的指针,用于存储生成的随机数据

  • buflen: 请求的随机数据字节数

  • flags: 控制随机数生成行为的标志位

  1. 标志位(flags 参数)

标志值说明GRND_RANDOM0x0001使用 /dev/random 而不是 /dev/urandomGRND_NONBLOCK0x0002非阻塞模式,熵不足时不等待

  1. 标志位详细说明

GRND_RANDOM

  • 默认情况下,getrandom 使用 /dev/urandom(非阻塞)

  • 设置此标志后,使用 /dev/random(阻塞模式)

  • /dev/random 在熵不足时会阻塞,直到收集到足够熵

GRND_NONBLOCK

  • 默认情况下,如果熵池未初始化,函数会阻塞等待

  • 设置此标志后,如果熵不足则立即返回错误(errno = EAGAIN)

  1. 返回值
  • 成功: 返回实际获取的随机字节数

  • 失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EAGAIN: 非阻塞模式下熵不足

  • EFAULT: buf 指针无效

  • EINVAL: flags 参数无效

  • EIO: 系统错误

  1. 相似函数或关联函数
  • /dev/random: 传统的阻塞随机数设备

  • /dev/urandom: 传统的非阻塞随机数设备

  • rand(): 标准库伪随机数生成器

  • random(): 更好的伪随机数生成器

  • arc4random(): BSD 风格的加密安全随机数

  • openssl RAND_bytes(): OpenSSL 库的随机数函数

  1. 示例代码

示例1:基础用法 - 生成随机数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#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&#91;i]);
}
hex&#91;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&#91;i] >= 32 && bin&#91;i] <= 126) {
printable&#91;i] = bin&#91;i];
} else {
printable&#91;i] = '.';
}
}
printable&#91;len] = '\0';
}

int main() {
unsigned char random_data&#91;32];
char hex_string&#91;65];
char printable_string&#91;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&#91;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:不同标志位的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#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&#91;i];
}
double average = (double)sum / len;
printf(" 平均值: %.2f\n", average);
printf(" 数据范围: 0x%02x - 0x%02x\n",
data&#91;0], data&#91;len-1]); // 简化的范围显示
}

int main() {
unsigned char buffer&#91;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:完整的随机数工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#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&#91;i]);
}
output&#91;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&#91;i++] : 0;
uint32_t octet_b = i < len ? data&#91;i++] : 0;
uint32_t octet_c = i < len ? data&#91;i++] : 0;

uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;

output&#91;j++] = base64_chars&#91;(triple >> 18) & 63];
output&#91;j++] = base64_chars&#91;(triple >> 12) & 63];
output&#91;j++] = base64_chars&#91;(triple >> 6) & 63];
output&#91;j++] = base64_chars&#91;triple & 63];
}

// 处理填充
int pad = len % 3;
if (pad == 1) {
output&#91;j-2] = '=';
output&#91;j-1] = '=';
} else if (pad == 2) {
output&#91;j-1] = '=';
}
output&#91;j] = '\0';
}

// 显示帮助信息
void show_help(const char *program_name) {
printf("用法: %s &#91;选项]\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&#91;]) {
size_t num_bytes = 32;
enum output_format format = FORMAT_HEX;
unsigned int flags = 0;
int c;

// 解析命令行参数
static struct option long_options&#91;] = {
{"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&#91;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&#91;i]);
}
printf("\n");
break;
}

free(buffer);
return 0;
}

示例4:加密安全的随机数应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#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&#91;] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";

char *session_id = malloc(length + 1);
if (!session_id) return NULL;

unsigned char random_bytes&#91;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&#91;i] = charset&#91;random_bytes&#91;i] % (sizeof(charset) - 1)];
}
session_id&#91;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&#91;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&#91;32]; // 256-bit AES key
unsigned char hmac_key&#91;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&#91;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&#91;j]);
}
printf("\n");
}
}

// 4. 生成初始化向量 (IV)
printf("\n4. 生成初始化向量 (IV):\n");
unsigned char iv&#91;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;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编译示例程序
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

系统要求检查

1
2
3
4
5
6
7
8
9
# 检查内核版本(需要 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/urandomrand()random()安全性高高低低阻塞可选否否否性能中等中等高高可移植性Linux 特有Unix-like标准 CBSD 扩展加密适用是是否否

实际应用场景

加密密钥生成: 生成 AES、RSA 等加密密钥

会话管理: 生成会话 ID、CSRF 令牌

密码盐值: 为密码哈希生成随机盐值

初始化向量: 生成加密算法的 IV

临时文件名: 生成唯一的临时文件名

游戏随机数: 游戏中的随机事件生成

最佳实践

默认使用: 一般情况下使用 getrandom(buffer, size, 0)

加密场景: 重要加密操作可考虑 GRND_RANDOM

批量获取: 一次获取较多随机数据,避免频繁调用

错误检查: 始终检查返回值确保成功

内存清理: 使用后及时清理敏感的随机数据

这些示例展示了 getrandom 函数的各种使用方法,从基础的随机数据生成到完整的安全应用,帮助你全面掌握这个重要的安全随机数生成接口。

data-ad-format="auto" data-full-width-responsive="true">