getrandom 函数详解 Link to heading

1. 函数介绍 Link to heading

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

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

  • 加密操作: 生成密钥、初始化向量等
  • 安全令牌: 会话 ID、验证码等
  • 模拟和游戏: 游戏中的随机事件
  • 负载均衡: 分布式系统中的随机选择

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

2. 函数原型 Link to heading

#include <sys/random.h>

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

3. 功能 Link to heading

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

4. 参数 Link to heading

  • buf: 指向缓冲区的指针,用于存储生成的随机数据
  • buflen: 请求的随机数据字节数
  • flags: 控制随机数生成行为的标志位

5. 标志位(flags 参数) Link to heading

标志 说明
GRND_RANDOM 0x0001 使用 /dev/random 而不是 /dev/urandom
GRND_NONBLOCK 0x0002 非阻塞模式,熵不足时不等待

6. 标志位详细说明 Link to heading

GRND_RANDOM Link to heading

  • 默认情况下,getrandom 使用 /dev/urandom(非阻塞)
  • 设置此标志后,使用 /dev/random(阻塞模式)
  • /dev/random 在熵不足时会阻塞,直到收集到足够熵

GRND_NONBLOCK Link to heading

  • 默认情况下,如果熵池未初始化,函数会阻塞等待
  • 设置此标志后,如果熵不足则立即返回错误(errno = EAGAIN)

7. 返回值 Link to heading

  • 成功: 返回实际获取的随机字节数
  • 失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EAGAIN: 非阻塞模式下熵不足
  • EFAULT: buf 指针无效
  • EINVAL: flags 参数无效
  • EIO: 系统错误

8. 相似函数或关联函数 Link to heading

  • /dev/random: 传统的阻塞随机数设备
  • /dev/urandom: 传统的非阻塞随机数设备
  • rand(): 标准库伪随机数生成器
  • random(): 更好的伪随机数生成器
  • arc4random(): BSD 风格的加密安全随机数
  • openssl RAND_bytes(): OpenSSL 库的随机数函数

9. 示例代码 Link to heading

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

#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:不同标志位的使用 Link to heading

#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:完整的随机数工具 Link to heading

#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:加密安全的随机数应用 Link to heading

#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;
}

编译和运行说明 Link to heading

# 编译示例程序
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

系统要求检查 Link to heading

# 检查内核版本(需要 3.17+)
uname -r

# 检查 glibc 版本(需要 2.25+)
ldd --version

# 检查 /dev/random 和 /dev/urandom
ls -l /dev/random /dev/urandom

重要注意事项 Link to heading

  1. 内核版本: 需要 Linux 3.17+ 内核支持
  2. glibc 版本: 需要 glibc 2.25+ 支持
  3. 性能考虑: 避免频繁调用,批量获取随机数据
  4. 安全性: 比 rand() 等伪随机数生成器更安全
  5. 阻塞行为: 默认非阻塞,GRND_RANDOM 可能阻塞
  6. 错误处理: 始终检查返回值和 errno

与其他随机数生成器的比较 Link to heading

特性 getrandom /dev/urandom rand() random()
安全性
阻塞 可选
性能 中等 中等
可移植性 Linux 特有 Unix-like 标准 C BSD 扩展
加密适用

实际应用场景 Link to heading

  1. 加密密钥生成: 生成 AES、RSA 等加密密钥
  2. 会话管理: 生成会话 ID、CSRF 令牌
  3. 密码盐值: 为密码哈希生成随机盐值
  4. 初始化向量: 生成加密算法的 IV
  5. 临时文件名: 生成唯一的临时文件名
  6. 游戏随机数: 游戏中的随机事件生成

最佳实践 Link to heading

  1. 默认使用: 一般情况下使用 getrandom(buffer, size, 0)
  2. 加密场景: 重要加密操作可考虑 GRND_RANDOM
  3. 批量获取: 一次获取较多随机数据,避免频繁调用
  4. 错误检查: 始终检查返回值确保成功
  5. 内存清理: 使用后及时清理敏感的随机数据

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