首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux C 語言驗證用戶密碼:基於 crypt() 的影子文件認證實現

     編輯:關於C語言

Linux C 語言驗證用戶密碼:基於 crypt() 的影子文件認證實現

Linux 系統用戶認證核心在於 /etc/shadow 中的加密密碼驗證。本文介紹基於 crypt() 函數的密碼驗證原理、完整實現代碼與安全最佳實踐。

核心原理

  • /etc/shadow 存儲格式:$id$salt$hashed
  • id:算法標識(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;
}

關鍵安全要點

現代替代方案:PAM (Pluggable Authentication Modules)

生產環境強烈建議使用 PAM,而非直接讀取 /etc/shadow:

#include 
#include 

int pam_verify(const char* user, const char* pass) {
    pam_handle_t* pamh = NULL;
    struct pam_conv conv = { misc_conv, NULL };
    int ret = pam_start("system-auth", user, &conv, &pamh);
    if (ret != PAM_SUCCESS) return ret;
    
    ret = pam_authenticate(pamh, 0);  // 提示輸入密碼或使用 misc_conv 回調
    pam_end(pamh, ret);
    return ret == PAM_SUCCESS ? 0 : -1;
}

優勢:統一認證策略、支持 LDAP/Kerberos/智能卡、自動處理影子文件權限、审計日誌集成。

常見編譯鏈接問題

# 需鏈接 crypt 庫
gcc -o auth verify.c -lcrypt

# PAM 開發包
# Ubuntu/Debian: libpam0g-dev
# CentOS/RHEL: pam-devel
gcc -o pam_auth pam_verify.c -lpam -lpam_misc

總結

crypt() 是 Linux 密碼驗證的基石,理解其 $id$salt$hash 格式與自動算法選擇機制至關重要。但直接操作 /etc/shadow 存在權限、安全、維護風險,**生產環境請優先使用 PAM**。本文代碼適合教學、嵌入式無 PAM 環境或自定義認證服務開發參考。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
風險對策
時序攻擊使用恆定時間比較(explicit_bzero + 手動比較或 CRYPTO_memcmp)
內存殘留驗證後立即 explicit_bzero 清理密碼緩衝區
權限最小化僅驗證進程需 root,業務進程通過 PAM/IPC 與驗證服務通信
暴力破解接入 fail2ban、帳號鎖定策略、雙因子認證
算法降級拒絕 DES/MD5,強制 SHA-512/yescrypt(檢查 $6$ 或 $y$ 前綴)