Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4067219
  • 博文数量: 251
  • 博客积分: 11197
  • 博客等级: 上将
  • 技术积分: 6862
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-05 14:41
个人简介

@HUST张友东 work@taobao zyd_com@126.com

文章分类

全部博文(251)

文章存档

2014年(10)

2013年(20)

2012年(22)

2011年(74)

2010年(98)

2009年(27)

分类: LINUX

2011-01-19 17:10:44

本文更正http://blog168.chinaunix.net/space.php?uid=20196318&do=blog&id=28742中出现的几个错误。错误1:对salt的描述;错误2:密码验证方式。

花了点时间研究了一下linux的密码认证机制,当系统启动出现login时,用户登录的过程是如何得到验证的。

首先linux系统里管理用户及密码的两个重要的文件,/etc/passwd, /etc/shadow

/etc/passwd包含各个用户的信息,linux平台包含七个字段,各个字段间用冒号隔开,分别是:

用户名:密码:用户id:组id:用户描述:用户家目录:用户的登录shell

POSIX.1 定义了两个获取用户口令文件

#include

struct passwd *getpwuid(uid_t uid);      //通过inode中的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

struct passwd *getpwent();  //返回口令记录的下一项

void setpwent();          //定位到开头,即从第一项开始读

void endpwent();         //读取结束,关闭


/etc/shadow包含用户的密码信息,在linux中包含九项内容,分别是:

用户登录名:加密口令:之后的几项用于控制口令改动频率及账户活动状态情况

Linux中提供了类似读取/etc/passwd的接口

 

#include

struct spwd* getspnam(const char *name);

struct spwd* getspent();

void setspent();

void endspent();

 

struct spwd

{

      Char *sp_namp;   //用户名

      Char *sp_pwdp;   //密码

     ……..

}

/etc/passwd, /etc/shadow中的密码项到底是怎么回事?

早先linux密码放在/etc/passwd项,出于安全考虑,后将密码移到/etc/shadow,而在/etc/passwd第二项以x作为标记。而在/etc/shadow中大致有三种情形,密码项为*,!!,或一个字符串,分别表示禁止账户登录,密码未设置,及加密后的密码。

使用useradd添加一个新用户,就会发现该用户的的密码项为!!。


用户密码如何产生,/etc/passwd的字符串含义是?

最初linux将用户的密码通过某种one-way function得到一个散列(加密)后的字符串,并存储该字符串在密码文件中,但这种方式易遭受字典攻击,攻击者只有准备好字典,使用相同的one-way function计算出对应的值,逐个对比就ok就可以攻破。

为了提高安全性,引入salt,所谓的salt,即为一个随机数,引入的时候为一个2字符的字符串(从[A-Za-z0-9./]64个字符选取,后来salt扩展到最多12个字符),当用户设置密码时,会随机生成一个salt,与用户的密码一起加密,得到一个加密的字符串(salt以明文形式包含在该字符串中),存储到密码文件中,这样就将攻击的难度扩大了64*644096倍。

crypt将用户的keysalt一起适应某种算法进行加密(散列)

#define _XOPEN_SOURCE

#inclue

char *crypt(const char *key, const char *salt);

crypt中可以使用多种加密(散列)机制,包括最初的DES,还有后来为提高安全性引入的md5blowfishsha-256sha-512.

crypt为支持不同的方式,将salt进行格式化,格式为:

$id$salt$encoded (这也是保存在密码文件中的格式)

这里不同id代表不同的算法,不同算法salt的长度也不同。

Id

Method

实际加密后的密码长度

1

MD512salt字符)

22

2a

Blowfish

只在某些发行版中支持

5

SHA-25612salt字符)

43

6

SHA-51212salt字符)

86

而最初的DES方式的salt2个字符(A-Za-z0-9/+)提供了64*64=4096种可能性。

然后将用户keysalt拼接成一个新的字符串,用这个字符串作为密钥对某个原始串(通常为全0)进行DES加密,得到11个字符,然后将这11个字符接到salt后面即为用户加密后的密码。

如何验证某个用户的密码?

1,  根据用户名调用getspnam获取对应的spwd项。

2,  根据用户输入的密码key,调用cryptkeyspwd->sp_pwdp)(其中sp_pwdp中前面的部分包含salt的值)得到加密后的值encoded_str

3,  encoded_strspwd->sp_pwdp进行对比,如果相等,则通过验证。


我的系统中密码的格式为:

$6$MLjJx2Ga$Rf36llsl8zknJUI8RYg6.U6T8BfdAp5fCfL0cFQkMoj7ZC5egPK85OhR    JG9kki16TVmDcGh1BrQ20I3tZp35S0

第一、二个$之间的为使用的加密算法类型标示,第二、三个$之间的部分为salt,第三个$后面的部分为加密后的密码。

$6$可以知道,crypt使用sha-512算法。

 

通过以下程序可以验证用户:

#include

#include

#include

#include

int main(int argc, char **argv)

{

       struct spwd  *sp;

       sp = getspnam(argv[1]);

       if(sp == NULL)

       {

              printf("get spentry error\n");

              return -1;

       }

 

       if(strcmp(sp->sp_pwdp, (char*)crypt(argv[2], sp->sp_pwdp)) == 0)

      {

              printf("yes\n");

       }

       else

       {

              printf("no\n");

       }

       return 0; 

}

gcc –o user user.c –lcrypt

./user ydzhang a  //ydzhang的密码为a,输出为yes


为什么SHA-512得到的结果是86个字符,不是512/8=64个字符吗?

因为ASCII码中有很多字符时不可打印字符,为了让加密过的密码看起来像普通的字符串,系统使用了一种叫base64的编码,将散列后的结果转化为可打印字符。

base64的基本思想,base64的字符集为A-Za-z0-9/+

base64将原始串每3个八位字节,先整理成46位形式(6位表示的范围0-63,与base64的集合大小一样),然后在每个6位前补两个0,得到48位的形式。

例如00111100 01010101 10101010—000011111 00000101 00010110 00101010

如果原始串的字节数不能被3整除,base64推荐的方法是补0至能整除,后面的每60用一个“=”表示,但crypt不是这么做的。

SHA-512为例,生成64字节的字符串,643余数为1,按照base64 =号补全的方法,应该得到长度为88的串,且以两个=号结束,而实际上长度为86字节。

个人猜测crypt的处理方式为:以余一个字节的情况为例,00110011 直接转化为00001100 00000011(前补0 或者是 00001100 00110000(后补0),我看了rootydzhang两个账户的末尾字符,分别是/ 0,所以在后面补0的可能性较大,而且处理也简单下,直接左移位即可。

另外没有看过crpyt的代码,内部对saltkey的拼接方式,和base64的转化方式都不能确定,所以我直接通过拼接saltkey,并通过opensslsha512工具验证没有成功。


阅读(10841) | 评论(2) | 转发(3) |
给主人留下些什么吧!~~

zyd_cu2011-01-20 21:54:30

GFree_Wind: 不知道博主队PAM了解如何,Linux可以通过PAM使用各种不同的认证方式。本文中仅是最普遍的本地认证。——我对PAM了解也不多。.....
我对pam不了解,只是认识了下本地认证的相关机制。

GFree_Wind2011-01-20 17:48:21

不知道博主队PAM了解如何,Linux可以通过PAM使用各种不同的认证方式。本文中仅是最普遍的本地认证。——我对PAM了解也不多。