[转载]关于Linux系统中的密码加密流程及原理
2014-01-16 14:42阅读:
PAM是个什么东西,词典上没有。
放狗查了下,这是个简写,全称是:Pluggable Authentication
Modules,中文叫“可插入认证模块”。
它是一种统一的认证方案。PAM
让您能随时改变您的认证方法以及需求,并且不需要重新编译任何代码就封装了所有本地认证方法。具体见
PAM
网站。 对于 PAM 您只需要做:
- 对您的密码采用不同于 DES 的加密方式(让它们面对暴力解码(brute-force
decode)时更为坚固)。
-
对您所有用户使用资源限额,以防止他们进行拒绝服务(denial-of-service)攻击(进程数、占用内存量等等)。
- 随时启用 shadow password (见下文)
- 只在特定的时间允许特定的用户从特定的地方登录
上述介绍来源于http://man.ddvip.com/linux/Mandrakelinux10server/password-security.html。
========================================
我们看看shadow文件的格式:
root@localhost:~# cat /etc/shadow
root:$1$Bg1H/4mz
$X89TqH7tpi9dX1B9j5YsF.:14838:0:99999:7:::
daemon:*:14838:0:99999:7:::
bin:*:14838:0:99999:7:::
sys:*:14838:0:99999:7:::
sync:*:14838:0:99999:7:::
games:*:14838:0:99999:7:::
man:*:14838:0:99999:7:::
lp:*:14838:0:99999:7:::
mail:*:14838:0:99999:7:::
news:*:14838:0:99999:7:::
uucp:*:14838:0:99999:7:::
proxy:*:14838:0:99999:7:::
www-data:*:14838:0:99999:7:::
backup:*:14838:0:99999:7:::
list:*:14838:0:99999:7:::
irc:*:14838:0:99999:7:::
gnats:*:14838:0:99999:7:::
nobody:*:14838:0:99999:7:::
libuuid:!:14838:0:99999:7:::
Debian-exim:!:14838:0:99999:7:::
statd:*:14838:0:99999:7:::
sshd:*:14838:0:99999:7:::
test::14879:0:99999:7:::
格式是:
{用户名}:{加密后的密码字符串}:{口令最后修改时间距1970年1月1日的天数}:{
口令能被修改之前的天数(防止修改口令,然后立刻将它改回到老口令)}:{口令必须被修改之后的天数}:{口令期满前的天数}:{口令期满后的天数}:{保留}
shadow是个可读文件,普通用户没有读写权限,超级用户权限为可读写。
如果密码字符串为*,表示系统用户不能被登入,为!表示用户名被禁用,如果密码字符串为空,表示没有密码,通过passwd -d
用户名 可以清空一个用户的口令。
具体shadow可参考shadow
how-to,尽管这是一份已经过时了的文档。
下面探讨下shadow中的密码加密问题:
还是以上面的root用户为例:
root:$1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF.:14838:0:99999:7:::
其中的密码域为$1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF.
,参考linux标准的passwd.c源文件,在pw_encrypt函数中可以找到加密方法:
40 char *pw_encrypt (const char *clear, const char
*salt)
41 {
42 static char
cipher[128];
43 char *cp;
44
45 cp = crypt (clear,
salt);
46 if (!cp) {
47
52
perror ('crypt');
53
exit (1);
54 }
55
56
58 if (salt &&
salt[0] == '$' && strlen (cp) <= 13)
59 {
..........
79
fprintf (stderr,
80
_('crypt method not
supported by libcrypt? (%s)n'),
81
method);
82
exit (1);
83 }
84
85 if (strlen (cp) !=
13)
86
return cp;
87 strcpy (cipher,
cp);
88
89 return
cipher;
90 }
也就是说加密用明文密码和一个叫salt的东西用crypt()加密生成密文。
再来看看crypt的帮助:
http://www.kernel.org/doc/man-pages/online/pages/man3/crypt.3.html
可发现原来crypt密文里是由三部分组成的,即:$id$salt$encrypted
目前常用的是当id为1时,使用md5加密,id为5,采用SHA256进行加密,id为6采用SHA512进行加密。
分析上面的函数,可看出我们的shadow密码中,直接把$id$salt$encrypted
当做salt参数带入进行crypt加密。
那好,我们可以写个简短的代码进行试验:
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
if(argc < 2)
{
printf('no usrname input');
return 1;
}
if (geteuid() != 0)
fprintf(stderr, 'must be setuid root');
struct passwd *pwd;
pwd = getpwnam(argv[1]);
if(pwd ==NULL)
printf('no username found.n');
else
{
printf('passwd: %sn', pwd->pw_passwd);
if(strcmp(pwd->pw_passwd, 'x') == 0)
{
printf('shadow used.n');
struct spwd *shd= getspnam(argv[1]);
if(shd != NULL)
{
static char crypt_char[80];
strcpy(crypt_char, shd->sp_pwdp);
char salt[13];
int i=0,j=0;
while(shd->sp_pwdp[i]!=''){
salt[i]=shd->sp_pwdp[i];
if(salt[i]=='$'){
j++;
if(j==3){
salt[i+1]='';
break;
}
}
i++;
}
if(j<3)perror('file error or user cannot
use.');
if(argc==3)
printf('salt: %s, crypt: %sn', salt, crypt(argv[2],
salt));
printf('shadowd passwd: %sn', shd->sp_pwdp);
}
}
}
return 0;
}
保存后执行gcc passwd.c -lcrypt -o passwd
编译成功后运行./passwd root 123
其中./passwd是生成的命令,root是我们用来测试的账户,123是测试系统中root用户的密码,执行的结果是:
passwd: x
shadow used.
salt: $1$Bg1H/4mz$, crypt:
$1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF.
shadowd passwd:
$1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF.
可见,我们得到了和系统shadow文件下相同结果的密文。
根据我们的运行结果我们可以看到,在root用户的shadow中,他的salt是$1$Bg1H/4mz$
我们之前也是用这个salt来进行加密匹配的。但是,问题是:这个salt到底是怎么来的??
还是分析标准的passwd.c,
在passwd.c中,找到了生成salt的函数:crypt_make_salt
201 char *crypt_make_salt (const char *meth, void
*arg)
202 {
203
209 static char
result[40];
210 size_t salt_len =
8;
211 const char
*method;
212
213 result[0] =
'';
214
215 if (NULL !=
meth)
216
method = meth;
217 else {
218 if ((method = getdef_str
('ENCRYPT_METHOD')) == NULL)
219
method = getdef_bool ('MD5_CRYPT_ENAB') ? 'MD5' :
'DES';
220 }
221
222 if (!strcmp (method,
'MD5')) {
223
MAGNUM(result, '1');
224 #ifdef USE_SHA_CRYPT
225 } else if (!strcmp
(method, 'SHA256')) {
226
MAGNUM(result, '5');
227
strcat(result, SHA_salt_rounds((int *)arg));
228
salt_len = SHA_salt_size();
229 } else if (!strcmp
(method, 'SHA512')) {
230
MAGNUM(result, '6');
231
strcat(result, SHA_salt_rounds((int *)arg));
232
salt_len = SHA_salt_size();
233 #endif
234 } else if (0 != strcmp
(method, 'DES')) {
235
fprintf (stderr,
236
_('Invalid
ENCRYPT_METHOD value: '%s'.n'
237
'Defaulting to DES.n'),
238
method);
239
result[0] = '';
240 }
241
242
245 assert (sizeof (result)
> strlen (result) + salt_len);
246 strncat (result, gensalt
(salt_len),
247
sizeof (result) - strlen (result) -
1);
248
249 return
result;
250 }
除了一大段条件判断语句,其实最重要的只有一句gensalt(salt_len)。
看看gensalt的定义:
167
168 static char *gensalt (unsigned int
salt_size)
169 {
170 static char
salt[32];
171
172 salt[0] =
'';
173
174 assert (salt_size >=
MIN_SALT_SIZE &&
175
salt_size <=
MAX_SALT_SIZE);
176 seedRNG ();
177 strcat (salt, l64a
(random()));
178 do {
179
strcat (salt, l64a (random()));
180 } while (strlen (salt)
< salt_size);
181 salt[salt_size] =
'';
182
183 return salt;
184 }
哦,原来神秘的salt只是某个的固定长度的随机的可见的字符串而已。
每次改写密码时,都会再随机生成一个这样的salt。而用户登入时,会拿用户登入的明文密码经过上述演示的步骤生成密文后和shadow里的密码域进行比较。
有了上述的分析,要暴利破解linux下的口令也不是什么问题,但前提是你有机会拿到shadow文件,这个前提条件貌似很难。
常见的hash加密(来自国外某论坛)
---------------*nix系------------------------
系统:ES(Unix)
例子:IvS7aeT4NzQPM
说明:Linux或者其他linux内核系统中
长度: 13 个字符
描述:第1、2位为salt,例子中的'Iv'位salt,后面的为hash值
系统:MD5(Unix)
例子:$1$12345678$XM4P3PrKBgKNnTaqG9P0T/
说明:Linux或者其他linux内核系统中
长度:34个字符
描述:开始的$1$位为加密标志,后面8位12345678为加密使用的salt,后面的为hash
加密算法:2000次循环调用MD5加密
系统:SHA-512(Unix)
例子:$6$12345678$U6Yv5E1lWn6mEESzKen42o6rbEm
说明:Linux或者其他linux内核系统中
长度: 13 个字符
描述:开始的$6$位为加密标志,后面8位为salt,后面的为hash
加密算法:5000次的SHA-512加密
系统:SHA-256(Unix)
例子:$5$12345678$jBWLgeYZbSvREnuBr5s3gp13vqi
说明:Linux或者其他linux内核系统中
长度: 55 个字符
描述:开始的$5$位为加密标志,后面8位为salt,后面的为hash
加密算法:5000次的SHA-256加密
系统:MD5(APR)
例子:$apr1$12345678$auQSX8Mvzt.tdBi4y6Xgj.
说明:Linux或者其他linux内核系统中
长度:37个字符
描述:开始的$apr1$位为加密标志,后面8位为salt,后面的为hash
加密算法:2000次循环调用MD5加密
-----------------windows------------------------------------------
系统:windows
例子:Admin:b474d48cdfc4974d86ef4d24904cdd91
长度:98个字符
加密算法:MD4(MD4(Unicode($pass)).Unicode(strtolower($username)))
------------------mysql--------------------------------------------
系统:mysql
例子:606717496665bcba
说明:老版本的MySql中
长度:8字节(16个字符)
说明:包括两个字节,且每个字的值不超过0x7fffffff
系统:MySQL5
例子:*E6CC90B878B948C35E92B003C792C46C58C4AF40
说明:较新版本的MySQL
长度:20字节(40位)
加密算法:SHA-1(SHA-1($pass))
------------------其他系统---------------------------------------------
系统:MD5(WordPress)
例子:$P$B123456780BhGFYSlUqGyE6ErKErL01
说明:WordPress使用的md5
长度:34个字符
描述:$P$表示加密类型,然后跟着一位字符,经常是字符‘B’,后面是8位salt,后面是就是hash
加密算法:8192次md5循环加密
系统:MD5(phpBB3)
说明:phpBB 3.x.x.使用
例子:$H$9123456785DAERgALpsri.D9z3ht120
长度:34个字符
描述:开始的$H$为加密标志,后面跟着一个字符,一般的都是字符‘9’,然后是8位salt,然后是hash
值
加密算法:2048次循环调用MD5加密
系统:RAdmin v2.x
说明:Remote Administrator v2.x版本中
例子:5e32cceaafed5cc80866737dfb212d7f
长度:16字节(32个字符)
加密算法:字符用0填充到100字节后,将填充过后的字符经过md5加密得到(32位值)
------------------------md5加密--------------------------------------------
标准MD5
例子:c4ca4238a0b923820dcc509a6f75849b
使用范围:phpBB v2.x, Joomla 的 1.0.13版本前,及其他cmd
长度:16个字符
其他的加salt及变形类似:
md5($pass.$salt)
例子:6f04f0d75f6870858bae14ac0b6d9f73:1234
md5($salt.$pass)
例子:f190ce9ac8445d249747cab7be43f7d5:12
md5(md5($pass))
例子:28c8edde3d61a0411511d3b1866f0636
md5(md5($pass).$salt)
例子:6011527690eddca23580955c216b1fd2:wQ6
md5(md5($salt).md5($pass))
例子:81f87275dd805aa018df8befe09fe9f8:wH6_S
md5(md5($salt).$pass)
例子:816a14db44578f516cbaef25bd8d8296:1234
md5($salt.$pass.$salt)
例子:a3bc9e11fddf4fef4deea11e33668eab:1234
md5($salt.md5($salt.$pass))
例子:1d715e52285e5a6b546e442792652c8a:1234
$1开头为MD5
$5开头为SHA256
$6开头为SHA512
DES和明文开头无标记