C语言password怎么实现安全存储?

99ANYc3cd6
预计阅读时长 33 分钟
位置: 首页 C语言 正文

永远不要在代码中硬编码密码,并且要安全地处理用户输入。

c语言password
(图片来源网络,侵删)

下面我将从几个关键方面来讲解:

  1. 安全地获取用户输入(防止回显)
  2. 安全地存储密码(哈希加盐)
  3. 避免硬编码密码
  4. 一个完整的、可运行的示例程序

安全地获取用户输入(防止回显)

当用户输入密码时,终端通常会显示他们输入的字符(即“回显”),这在输入密码时是不安全的,因为旁边的人可能会看到,我们需要一种方法来禁用回显,并处理用户的各种按键(如退格键)。

在C语言中,这通常通过直接操作终端的底层属性来实现,这是一个跨平台性较差的操作,因此代码会因操作系统而异。

Windows 平台 (<conio.h>)

在Windows上,我们可以使用<conio.h>库中的_getch()函数,这个函数会读取一个字符,并且不会在控制台显示。

c语言password
(图片来源网络,侵删)
#include <stdio.h>
#include <conio.h> // Windows特有
void get_password(char *buffer, int size) {
    int i = 0;
    char ch;
    printf("请输入密码: ");
    while (1) {
        ch = _getch(); // 读取一个字符
        if (ch == '\r' || ch == '\n') { // 按下回车键
            buffer[i] = '\0'; // 字符串结束符
            printf("\n");
            break;
        } else if (ch == '\b' && i > 0) { // 按下退格键
            printf("\b \b"); // 光标回退一格,打印空格,再回退一格
            i--;
        } else if (i < size - 1 && ch != '\b') { // 普通字符
            buffer[i++] = ch;
            printf("*"); // 打印一个星号作为占位符
        }
    }
}

Linux / macOS 平台 (<termios.h>)

在Linux和macOS上,我们需要使用<termios.h>库来修改终端的属性。

#include <stdio.h>
#include <termios.h> // POSIX (Linux/macOS)特有
#include <unistd.h>  // 用于 read()
void get_password(char *buffer, int size) {
    struct termios old_t, new_t;
    int i = 0;
    char ch;
    // 获取当前终端属性
    tcgetattr(STDIN_FILENO, &old_t);
    // 复制一份属性用于修改
    new_t = old_t;
    // 禁用回显 (ECHO) 和规范模式 (ICANON)
    // ICANON: 行缓冲模式,关闭后可以逐个字符读取
    new_t.c_lflag &= ~(ECHO | ICANON);
    // 设置新的终端属性
    tcsetattr(STDIN_FILENO, TCSANOW, &new_t);
    printf("请输入密码: ");
    while (1) {
        read(STDIN_FILENO, &ch, 1); // 读取一个字符
        if (ch == '\n' || ch == '\r') { // 按下回车键
            buffer[i] = '\0';
            printf("\n");
            break;
        } else if (ch == 127 && i > 0) { // 127是大多数Linux/macOS的退格键
            printf("\b \b");
            i--;
        } else if (i < size - 1 && ch != 127) {
            buffer[i++] = ch;
            printf("*");
        }
    }
    // 恢复旧的终端属性
    tcsetattr(STDIN_FILENO, TCSANOW, &old_t);
}

安全地存储密码(哈希加盐)

绝对不要以明文形式存储密码! 如果数据库或文件泄露,所有用户的密码都会暴露。

正确的做法是存储密码的哈希值,哈希是一种单向加密函数,可以从原始数据生成一个固定长度的“指纹”,但无法从指纹反向推算出原始数据。

为了防止彩虹表攻击,我们还需要使用,盐是一个随机生成的、与密码拼接在一起的字符串,然后再进行哈希,这样,即使两个用户使用相同的密码,由于盐不同,最终的哈希值也不同。

c语言password
(图片来源网络,侵删)

在C语言中,你可以使用一些加密库,如 OpenSSL,它提供了强大的哈希算法(如SHA-256)。

使用 OpenSSL 进行哈希加盐

你需要安装 OpenSSL 开发库:

  • Ubuntu/Debian: sudo apt-get install libssl-dev
  • CentOS/RHEL: sudo yum install openssl-devel
  • macOS (使用 Homebrew): brew install openssl

你可以这样使用它:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
// 定义盐的长度
#define SALT_LENGTH 16
// 函数:计算加盐哈希
int hash_password(const char *password, const unsigned char *salt, unsigned char *hash_buffer) {
    const EVP_MD *md = EVP_sha256(); // 使用SHA-256算法
    EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
    EVP_DigestInit_ex(mdctx, md, NULL);
    EVP_DigestUpdate(mdctx, password, strlen(password));
    EVP_DigestUpdate(mdctx, salt, SALT_LENGTH);
    EVP_DigestFinal_ex(mdctx, hash_buffer, NULL);
    EVP_MD_CTX_free(mdctx);
    return 1;
}
int main() {
    const char *password = "my_secret_password";
    unsigned char salt[SALT_LENGTH];
    unsigned char hash[EVP_MAX_MD_SIZE]; // 存储哈希结果
    // 1. 生成一个随机的盐
    if (!RAND_bytes(salt, SALT_LENGTH)) {
        fprintf(stderr, "生成盐失败\n");
        return 1;
    }
    // 2. 计算密码的哈希值
    hash_password(password, salt, hash);
    // 3. (在实际应用中) 你需要将盐和哈希值一起存储
    printf("原始密码: %s\n", password);
    printf("随机生成的盐: ");
    for (int i = 0; i < SALT_LENGTH; i++) {
        printf("%02x", salt[i]);
    }
    printf("\n");
    printf("密码哈希值: ");
    for (int i = 0; i < EVP_MD_size(EVP_sha256()); i++) {
        printf("%02x", hash[i]);
    }
    printf("\n");
    return 0;
}

避免硬编码密码

这是一个极其重要的安全规则。不要在代码中直接写死密码

// 错误示例!
const char *db_password = "this_is_a_very_bad_password";

原因如下:

  1. 版本控制泄露:如果代码被提交到Git等版本控制系统,密码就会永久暴露在历史记录中。
  2. 二进制文件暴露:编译后的二进制文件(.exe)可以使用工具(如strings命令)轻松查看其中的字符串,密码可能会被轻易找到。

正确的做法

  • 从环境变量中读取密码。
  • 从一个受权限保护的配置文件中读取密码。
  • 在程序启动时由管理员交互式输入。

从环境变量读取密码

#include <stdio.h>
#include <stdlib.h> // 用于 getenv
int main() {
    const char *env_password = getenv("MY_APP_PASSWORD");
    if (env_password == NULL) {
        fprintf(stderr, "错误:环境变量 MY_APP_PASSWORD 未设置,\n");
        return 1;
    }
    printf("从环境变量获取的密码: %s\n", env_password);
    // ... 使用密码 ...
    return 0;
}

如何运行: 在终端中设置环境变量,然后运行程序。

# Linux/macOS
export MY_APP_PASSWORD="my_secure_password"
./your_program
# Windows (Command Prompt)
set MY_APP_PASSWORD="my_secure_password"
your_program.exe

一个完整的、可运行的示例程序

这个程序将结合上述所有要点:

  1. 跨平台安全地获取密码输入(使用预处理器判断操作系统)。
  2. 使用OpenSSL对密码进行加盐哈希。
  3. 提示用户不要硬编码密码。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
// --- 跨平台密码输入 ---
#if defined(_WIN32) || defined(_WIN64)
#include <conio.h>
void get_password(char *buffer, int size) {
    int i = 0;
    char ch;
    printf("请输入密码: ");
    while (1) {
        ch = _getch();
        if (ch == '\r' || ch == '\n') {
            buffer[i] = '\0';
            printf("\n");
            break;
        } else if (ch == '\b' && i > 0) {
            printf("\b \b");
            i--;
        } else if (i < size - 1 && ch != '\b') {
            buffer[i++] = ch;
            printf("*");
        }
    }
}
#else // Linux/macOS
#include <termios.h>
#include <unistd.h>
void get_password(char *buffer, int size) {
    struct termios old_t, new_t;
    int i = 0;
    char ch;
    tcgetattr(STDIN_FILENO, &old_t);
    new_t = old_t;
    new_t.c_lflag &= ~(ECHO | ICANON);
    tcsetattr(STDIN_FILENO, TCSANOW, &new_t);
    printf("请输入密码: ");
    while (1) {
        read(STDIN_FILENO, &ch, 1);
        if (ch == '\n' || ch == '\r') {
            buffer[i] = '\0';
            printf("\n");
            break;
        } else if (ch == 127 && i > 0) {
            printf("\b \b");
            i--;
        } else if (i < size - 1 && ch != 127) {
            buffer[i++] = ch;
            printf("*");
        }
    }
    tcsetattr(STDIN_FILENO, TCSANOW, &old_t);
}
#endif
// --- 密码哈希 ---
#define SALT_LENGTH 16
int hash_password(const char *password, const unsigned char *salt, unsigned char *hash_buffer) {
    const EVP_MD *md = EVP_sha256();
    EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
    EVP_DigestInit_ex(mdctx, md, NULL);
    EVP_DigestUpdate(mdctx, password, strlen(password));
    EVP_DigestUpdate(mdctx, salt, SALT_LENGTH);
    EVP_DigestFinal_ex(mdctx, hash_buffer, NULL);
    EVP_MD_CTX_free(mdctx);
    return 1;
}
int main() {
    printf("========================================================\n");
    printf("  C 语言密码处理安全示例\n");
    printf("  - 安全输入(无回显)\n");
    printf("  - 密码哈希加盐\n");
    printf("  - 避免硬编码密码\n");
    printf("========================================================\n\n");
    char password[256];
    unsigned char salt[SALT_LENGTH];
    unsigned char hash[EVP_MAX_MD_SIZE];
    // 1. 安全地获取用户输入
    get_password(password, sizeof(password));
    // 2. 生成随机盐
    if (!RAND_bytes(salt, SALT_LENGTH)) {
        fprintf(stderr, "错误:无法生成随机盐,\n");
        return 1;
    }
    // 3. 计算哈希
    hash_password(password, salt, hash);
    // 4. 打印结果(演示用,实际应用中应存储盐和哈希)
    printf("\n--- 安全凭证(请勿泄露) ---\n");
    printf("盐: ");
    for (int i = 0; i < SALT_LENGTH; i++) {
        printf("%02x", salt[i]);
    }
    printf("\n哈希: ");
    for (int i = 0; i < EVP_MD_size(EVP_sha256()); i++) {
        printf("%02x", hash[i]);
    }
    printf("\n----------------------------\n");
    printf("\n重要提示:在实际应用中,请将“盐”和“哈希”值存储在安全的地方,\n");
    printf("请务必从环境变量或配置文件中读取密码,而不是硬编码!\n");
    return 0;
}

处理C语言中的密码时,请始终牢记以下黄金法则:

  1. 输入安全:禁用回显,处理退格等特殊按键。
  2. 存储安全:永远存储加盐哈希,而不是明文密码,使用像OpenSSL这样成熟的库。
  3. 代码安全:绝对不要在源代码中硬编码密码,使用环境变量或受保护的配置文件。
  4. 依赖管理:如果你使用外部库(如OpenSSL),请确保正确安装和链接。
-- 展开阅读全文 --
头像
dede批量修改标题,如何操作?
« 上一篇 今天
dede如何采集文章作者信息?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]