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

(图片来源网络,侵删)
下面我将从几个关键方面来讲解:
- 安全地获取用户输入(防止回显)
- 安全地存储密码(哈希加盐)
- 避免硬编码密码
- 一个完整的、可运行的示例程序
安全地获取用户输入(防止回显)
当用户输入密码时,终端通常会显示他们输入的字符(即“回显”),这在输入密码时是不安全的,因为旁边的人可能会看到,我们需要一种方法来禁用回显,并处理用户的各种按键(如退格键)。
在C语言中,这通常通过直接操作终端的底层属性来实现,这是一个跨平台性较差的操作,因此代码会因操作系统而异。
Windows 平台 (<conio.h>)
在Windows上,我们可以使用<conio.h>库中的_getch()函数,这个函数会读取一个字符,并且不会在控制台显示。

(图片来源网络,侵删)
#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语言中,你可以使用一些加密库,如 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";
原因如下:
- 版本控制泄露:如果代码被提交到Git等版本控制系统,密码就会永久暴露在历史记录中。
- 二进制文件暴露:编译后的二进制文件(
.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
一个完整的、可运行的示例程序
这个程序将结合上述所有要点:
- 跨平台安全地获取密码输入(使用预处理器判断操作系统)。
- 使用OpenSSL对密码进行加盐哈希。
- 提示用户不要硬编码密码。
#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语言中的密码时,请始终牢记以下黄金法则:
- 输入安全:禁用回显,处理退格等特殊按键。
- 存储安全:永远存储加盐哈希,而不是明文密码,使用像OpenSSL这样成熟的库。
- 代码安全:绝对不要在源代码中硬编码密码,使用环境变量或受保护的配置文件。
- 依赖管理:如果你使用外部库(如OpenSSL),请确保正确安装和链接。
