Linux 系統用戶認證核心在於 /etc/shadow 中的加密密碼驗證。本文介紹基於 crypt() 函數的密碼驗證原理、完整實現代碼與安全最佳實踐。
/etc/shadow 存儲格式:$id$salt$hashedid:算法標識(1=MD5, 5=SHA-256, 6=SHA-512, y=yescrypt)salt:隨機鹽值,防彩虹表攻擊salt → crypt(input_pass, salt) → 與存儲值比較#define _XOPEN_SOURCE
#include
#include
int main(int argc, char *argv[]) {
unsigned char *md = NULL;
md = crypt("password", "salt"); // 使用固定salt演示
printf("%s\n", md);
return 0;
}
編譯:gcc -o crypt_test crypt_test.c -lcrypt
#define _XOPEN_SOURCE
#include
#include
#include
#include
#include
#include
#include
// 驗證用戶密碼
// 返回:0=成功, -1=用戶不存在, -2=權限不足, -3=密碼錯誤, -4=系統錯誤
int verify_user_password(const char* username, const char* password) {
struct spwd* sp = getspnam(username);
if (!sp) {
if (errno == EACCES) return -2; // 權限不足(需root讀取shadow)
return -1; // 用戶不存在
}
// 獲取存儲的加密密碼(含算法標識、salt、hash)
const char* stored_hash = sp->sp_pwdp;
if (!stored_hash || strcmp(stored_hash, "*") == 0 || strcmp(stored_hash, "!") == 0) {
return -3; // 賬戶鎖定/無密碼
}
// 關鍵:crypt 會自動從 stored_hash 解析算法和 salt
char* computed_hash = crypt(password, stored_hash);
if (!computed_hash) return -4;
// 恆定時間比較,防時序攻擊
int match = (strcmp(computed_hash, stored_hash) == 0);
// 清理敏感數據
explicit_bzero(computed_hash, strlen(computed_hash));
return match ? 0 : -3;
}
int main(int argc, char* argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s \n", argv[0]);
return 1;
}
int ret = verify_user_password(argv[1], argv[2]);
switch(ret) {
case 0: printf("認證成功\n"); break;
case -1: printf("用戶不存在\n"); break;
case -2: printf("權限不足(需root運行)\n"); break;
case -3: printf("密碼錯誤或賬戶鎖定\n"); break;
case -4: printf("系統錯誤\n"); break;
}
return ret == 0 ? 0 : 1;
}
| 風險 | 對策 |
|---|---|
| 時序攻擊 | 使用恆定時間比較(explicit_bzero + 手動比較或 CRYPTO_memcmp) |
| 內存殘留 | 驗證後立即 explicit_bzero 清理密碼緩衝區 |
| 權限最小化 | 僅驗證進程需 root,業務進程通過 PAM/IPC 與驗證服務通信 |
| 暴力破解 | 接入 fail2ban、帳號鎖定策略、雙因子認證 |
| 算法降級 | 拒絕 DES/MD5,強制 SHA-512/yescrypt(檢查 $6$ 或 $y$ 前綴) |