最近做的一些開發和Linux使用者有密切的關係,以前沒有做過相關的學習,僅僅停留在使用shell下的useradd,
passwd命令,但對使用者的認證及密碼的管理基本沒有瞭解。在這裡做個小結。
1. 第一個重要檔案/etc/passwd
例:/etc/passwd中的一行
rwan:x:1000:1000:Robin:/home/rwan:/bin/bash
包含七個欄位,各個欄位間用冒號隔開(使用者名稱:密碼:使用者id:組id:使用者描述:使用者家目錄:使用者的登入shell)
POSIX.1 定義了兩個介面擷取使用者口令檔案
#include <pwd.h>struct passwd *getpwuid(uid_t uid); //通過uid擷取密碼項struct passwd *getpwnam(const char *name); //通過使用者名稱擷取密碼項struct passwd{ char *pw_name; //使用者名稱 char *pw_passwd; //使用者密碼 uid_t pw_uid; //使用者id uid_t pw_gid; //使用者組id char *pw_gecos; //使用者描述 char *pw_dir; //使用者家目錄 char *pw_shell; //使用者登入shell};
要查看整個口令檔案,POSIX.1介面則不能滿足要求,需使用下列介面
#include <pwd.h>struct passwd *getpwent(); //返回口令記錄的下一項void setpwent(); //定位到開頭,即從第一項開始讀void endpwent(); //讀取結束,關閉
問題:為什麼/etc/passwd中的所有密碼都是個x?
在最早的Unix系統中,使用者密碼確實存放在/etc/passwd,但為了安全,後來把密碼存放在/etc/shadow中,
因為/etc/paasswd對所有使用者都是可讀的,如果密碼放在這裡,會被暴力破解,而/etc/shadow只有root使用者可讀寫和
shadow組使用者可讀(這裡不同發行版可能不同,Fedora中/etc/shadow只有root可唯讀,此處測試在Ubuntu下)。
root@localhost:/home/rwan# ls -l /etc/passwd-rw-r--r-- 1 root root 1371 2009-08-29 05:05 /etc/passwdroot@localhost:/home/rwan# ls -l /etc/shadow-rw-r----- 1 root shadow 878 2009-08-29 05:05 /etc/shadow
2.第二個重要檔案/etc/shadow
例:/etc/shadow中的一行
rwan:$1$TA.EyKcB$GMnqWwkKY9cr8667xIwXE0:14574:0:99999:7:::
包含九項內容(使用者登入名稱:加密口令:之後的幾項用於控制口令改動頻率及賬戶活動狀態情況)。
而在/etc/shadow中大致有三種情形,密碼項為*,!,或一個字串,分別表示禁止賬戶登入,密碼未設定,及加密後的密碼。
使用useradd添加一個新使用者,就會發現該使用者的的密碼項為!
例:添加一個無密碼使用者
root@localhost:/home/rwan# useradd testroot@localhost:/home/rwan# cat /etc/passwd |grep testtest:x:1001:1002::/home/test:/bin/shroot@localhost:/home/rwan# cat /etc/shadow | grep testtest:!:14574:0:99999:7:::Linux中提供了類似讀取/etc/passwd的介面#include <shadow.h>struct spwd* getspnam(const char *name);struct spwd* getspent();void setspent();void endspent();struct spwd{ char *sp_namp; //使用者名稱 char *sp_pwdp; //密碼 ……}
3. 登入過程中如何進行密碼比對
為了提高安全性,Linux引入了salt,所謂的salt,即為一個隨機數,引入的時候為一個12-bit的數值,
當使用者佈建密碼時,會隨機產生一個salt,與使用者的密碼一起加密,得到一個加密的字串(salt以明文形式包含在該字串中),
儲存到密碼檔案中,這樣就將攻擊的難度擴大了212即4096倍。crypt將使用者的key和salt一起適應某種演算法進行加密(散列)
#inclue <stdlib.h>
char *crypt(const char *key, const char *salt);
crypt中可以使用多種加密(散列)機制,包括最初的DES,還有後來為提高安全性引入的md5,blowfish,sha-256,sha-512.
crypt為支援不同的方式,將salt進行格式化,格式為:
$id$salt$encoded (這也是儲存在密碼檔案中的格式)
這裡不同id代表不同的演算法,不同演算法salt的長度也不同。
Id |
Method |
實際加密後的密碼長度 |
1 |
MD5(12個salt字元) |
22 |
2a |
Blowfish |
|
5 |
SHA-256(12個salt字元) |
43 |
6 |
SHA-512(12個salt字元) |
86 |
例:一個小程式
首先給剛才建立的使用者test設定密碼123456,
root@localhost:/home/rwan# passwd testroot@localhost:/home/rwan# cat /etc/shadow | grep testtest:$1$svja5yi5$nGAXQLRtqM454THCcBv/50:14574:0:99999:7:::可以看到,Ubuntu採用的是Md5認證,salt = $1$svja5yi5$, 密鑰 = nGAXQLRtqM454THCcBv/50可以用下面的程式來實驗。#include <stdio.h>#include <stdlib.h>#include <string.h>void main(){ char *salt = "$1$svja5yi5"; printf("user test's secrect key = %s /n",crypt("123456", salt));}root@localhost:/home/rwan# gcc -o user.o user.c -lcryptroot@localhost:/home/rwan# ./user.ouser test's secrect key = $1$svja5yi5$nGAXQLRtqM454THCcBv/50
呵呵。不錯吧。和/etc/shadow中的一模一樣吧,廢話,不一樣的話,出大問題了。
那麼Linux是如何認證使用者密碼的,應該是使用者輸入密碼後,login程式從/etc/shadow中獲得該使用者的salt,
並計算密鑰,和/etc/shadow中的比對,有了上面的struct spwd* getspnam(const char *name)函數,想類比寫個也不難。
4.更多思考
既然/etc/shadow只有root可寫,那為什麼普通使用者可以修改自己的密碼?
這裡有一個suid的概念,什麼意思呢?
root@localhost:/home/rwan# ls -l /usr/bin/passwd-rwsr-xr-x 1 root root 29104 2006-12-19 15:35 /usr/bin/passwd
所有使用者對passwd命令均有可執行許可權,注意裡面的那個s,是說如果你有執行許可權,那麼運行該程式的時間,會以檔案屬主的許可權執行。
5.別走開,我們可以走的更遠
學習下怎麼製造salt吧。
以下代碼摘自busybox。
#include <stdio.h>#include <unistd.h>static int i64c(int i){ i &= 0x3f; if (i == 0) return '.'; if (i == 1) return '/'; if (i < 12) return ('0' - 2 + i); if (i < 38) return ('A' - 12 + i); return ('a' - 38 + i);}int crypt_make_salt(char *p, int cnt, int x){ x += getpid() + time(NULL); do { /* x = (x*1664525 + 1013904223) % 2^32 generator is lame * (low-order bit is not "random", etc...), * but for our purposes it is good enough */ x = x*1664525 + 1013904223; /* BTW, Park and Miller's "minimal standard generator" is * x = x*16807 % ((2^31)-1) * It has no problem with visibly alternating lowest bit * but is also weak in cryptographic sense + needs div, * which needs more code (and slower) on many CPUs */ *p++ = i64c(x >> 16); *p++ = i64c(x >> 22); } while (--cnt); *p = '/0'; return x;}void main() { int rnd = rnd; //use uninitialized data to generate random digital char salt[sizeof("$N$XXXXXXXX")]; strcpy(salt, "$1$"); rnd = crypt_make_salt(salt + 3, 4, rnd); printf("slat = %s/n", salt); printf("secret key = %s/n", crypt("123456", salt));}root@localhost:/home/rwan# gcc -o user2.o user2.c -lcryptroot@localhost:/home/rwan# ./user2.oslat = $1$rTWCKgPtsecret key = $1$rTWCKgPt$pIvnCsr3wAOw8XrJ9SQug1
思想:目的是產生8個隨機字元作為salt。對著ASCII表,去理解吧。
因為ASCII碼中有很多字元時不可列印字元,為了讓加密過的密碼看起來像普通的字串,系統使用了一種叫base64的編碼,
將散列後的結果轉化為可列印字元。base64的基本思想,base64的字元集為A-Za-z0-9/.