密码文件:/etc/passwd

针对系统的每个用户账号,系统密码文件/etc/passwd会专列一行进行描述。每行都包含7个字段,之间用冒号分割,如下所示:

  • 登录名
  • 经过加密的密码
  • 用户ID(UID)
  • 组ID(GID)
  • 注释
  • 主目录
  • 登录shell

shadow密码文件:/etc/shadow

为了安全起见,/etc/shadow应运而生。其理念是用户所有非敏感信息存放于“人人可读”的密码文件中,而经过加密处理的密码则由shadow密码文件单独维护,仅供具有特权的程序读取。shadow密码文件包含有:

  • 登录名(用来匹配密码文件中的相应记录)
  • 经过加密的密码
  • 其他若干与安全相关的字段

组文件:/etc/group

对用户所属组信息的定义由两部分组成:

  1. 密码文件中相应用户记录的组ID字段
  2. 组文件列出的用户所属各组
    系统中的每个组在组文件/etc/group中都对应着一条记录。每条记录包含4个字段,之间以冒号分割,如下所示:
  • 组名
  • 经过加密处理的密码
  • 组ID(GID)
  • 用户列表

获取用户和组的信息

从密码文件获取记录

函数getpwnam()getpwuid()的作用是从密码文件中获取记录

#include <pwd.h>
struct passwd *getpwnam(const char *name);
struct passwd *getpwuid(uid_t uid);
                        Both return a pointer on success, or NULL on error;
                       see main text for description of the "not found" case
  • 参数

    • name:用户名
    • uid:用户ID
  • 返回值

    • 成功:返回指向passwd结构(静态分配)的指针,该结构字段对应密码文件字段
    • 失败:没有找到相关用户记录会返回NULL,且不会改变errno;如果出错会设置errno

从组文件获取记录

函数getgrnam()getgrgid()的作用是从组文件中获取记录

#include <grp.h>
struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);
                        Both return a pointer on success, or NULL on error;
                       see main text for description of the "not found" case
  • 参数

    • name:组名
    • gid:组ID
  • 返回值

    • 成功:返回指向group结构(静态分配)的指针,该结构字段对应组文件字段
    • 失败:没有找到相关用户记录会返回NULL,且不会改变errno;如果出错会设置errno

扫描密码文件和组文件中的所有记录

函数setpwent()getpwent()endpwent()的作用是按顺序扫描密码文件中的记录

#include <pwd.h>

struct passwd *getpwent(void);
                        Returns pointer on success, or NULL on end of stream or error

void setpwent(void);
void endpwent(void);

函数getpwent()能够从密码文件中逐条返回记录,当不再有记录(或出错)时,该函数返回NULLgetpwent()一经调用,会自动打开密码文件。当密码文件处理完毕后,可调用endpwent()将其关闭。

函数getgrent()setgrent()endgrent()针对组文件执行类似的任务。

shadow密码文件中获取记录

下列函数作用包括从shadow密码文件中获取个别记录,以及扫描该文件中的所有记录。

#include <shadow.h>

struct spwd *getspnam(const char *name);
            Returns pointer on success, or NULL on not found or error
struct spwd *getspent(void);
            Returns pointer on success, or NULL on end of stream or error
void setspent(void);
void endspent(void);
  • 参数

    • name:用户名
  • 返回值

    • 成功:返回指向spwd结构(静态分配)的指针
    • 失败:没有找到相关用户记录会返回NULL,且不会改变errno;如果出错会设置errno

密码加密和用户认证

由于安全方面的原因,UNIX系统采用单向加密算法对密码进行加密,这意味着由密码的加密形式将无法还原出原始密码。因此,验证候选密码的唯一方法是使用同一算法对其进行加密,并将加密结果存储于/etc/shadow中的密码进行匹配。加密算法封装于crypt()函数之中。

#define _XOPEN_SOURCE
#include <unistd.h>

char *crypt(const char *key, const char *salt);
                             Returns pointer to statically allocated string containing 
                           encrypted password on success, or NULL on error

crypt()算法会接受一个最长可达8字符的密钥(即密码),并施之以数据加密算法(DES)的一种变体。

  • 参数

    • key:秘钥(密码)
    • salt:盐(指向两字符的字符串),用来扰动DES算法。该值会附加在密文之前,即该值可以在shadow文件中密文的前两个字符取得
  • 返回值

    • 成功:返回加密后的字符指针(长度为13个字符的字符串)

在shell中输入密码一般都没有回显,这就要用到getpass()函数

#define _BSD_SOURCE
#include <unistd.h>

char *getpass(const char *prompt);
                    Returns pointer to statically allocated input password string on success, or NULL on error

getpass()函数首先会屏蔽回显功能,并停止对终端特殊字符的处理。返回结果之前会将终端设置还原。

  • 参数

    • prompt:提示信息,用来在输入密码前显示
  • 返回值

    • 成功:返回以NULL结尾的输入字符串

读取密码的程序应立即加密密码,并尽快将密码的明文从内存中抹去(以空字符覆盖)

总结

本章介绍了用户相关的3个主要文件,然后介绍了获取用户信息和用户认证的系统调用。

标签: Linux, C/C++, TLPI

添加新评论