如果我正确理解身份验证机制,当我们在登录提示中输入凭据时,会计算密码的哈希值,然后将该哈希值与存储在某处的哈希值进行比较,如果我没有记错的话,“某处”就是/etc/shadow
.如果匹配,则认证成功,否则认证失败。
我的问题是,什么程序,我的意思是哪个二进制文件,计算输入密码的哈希值?还是内核实现的?
答案1
不具体的二进制进行散列;这是通过库调用完成的crypt(3)
。因此,理论上,任何程序(甚至perl
脚本)都可以生成哈希值。例如
perl -e 'print crypt("hello","\$6\$0abcdef")'
$6$0abcdef$JhsHMeFo8zYQPa8/HDXGoMWZgIxiCJKu2BQqCGjkyh/NadMax6GiJWQOT5ZL7POkUzrwbvL/Yhbx9f8XtLr.F/
您可以阅读有关此功能的更多信息man 3 crypt
。
现在,普通的 Unix 登录过程有点复杂。通常我们使用 PAM(“可插入身份验证模块”)堆栈。这与 NSS(“名称服务交换”)一起,在密码存储方式、位置以及身份验证方式方面提供了很大的灵活性。
使用 PAM 时,程序本身不会调用crypt()
,它会调用 PAM 堆栈并决定使用什么。
一个非常常见的模式是使用pam_unix
and 它将在pam_unix.so
其中完成调用,并在其中完成crypt()
与(或 LDAP 或任何地方)的比较。/etc/shadow
这种 PAM/NSS 组合非常强大,因为它意味着可以添加新的身份验证方法(例如加入 Active Directory 域),而无需每个程序都知道它;只需更新 PAM 配置,“神奇”就会发生。
答案2
当我们在登录提示中输入凭据时,会计算密码的哈希值,然后将该哈希值与存储在某处的哈希值进行比较,如果我没有记错的话,“某处”就是
/etc/shadow
.
这基本上是正确的(并且比网络上的许多简化解释更正确,所以做得很好)。更准确地说,数据流是:
- 阅读一些配置文件。我假设它们会导致具有正常默认值的本地帐户的常见情况。此阶段的其他可能性可能导致查询网络帐户数据库,或执行非基于密码的身份验证,或者(但这并不常见)使用密码散列的不同位置执行基于密码的身份验证。
- 输入用户密码磷。 (这可能会在下一步之后发生。)
- 找到包含所需用户名的行
/etc/shadow
并提取“加密的密码”字段。请注意,此步骤需要读取权限/etc/shadow
。 - 将 Shadow 字段分成两部分²:配置+盐S和预期的哈希值H。经过磷和S到
crypt
库函数1,获取结果右。比较右和H:如果相等则密码认证成功,否则密码认证失败。 - 如果存在非基于密码的方法,请继续进行用户身份验证。
- 如果认证成功,则登录用户,否则出错。
在典型的嵌入式 Linux 系统上,所有这些步骤都发生在同一个程序中:login
对于控制台登录,su
或者sudo
当提升权限时,dropbear
用于 SSH 登录等。步骤 1 可以完全省略,因为嵌入式系统此时通常不具有任何运行时可配置性。函数的实现crypt
来自系统的标准库,eg穆斯勒。因此,执行计算的代码存储在类似的位置/lib/libc.so
,而执行周围配置和数据库查找的代码则存储在类似的/bin/login
位置。除了提供基本的输入输出原语(打开文件、读取文件等)之外,内核不参与身份验证。内核只会在身份验证后更直接地参与进来,以跟踪身份验证后进程的权限。 (/etc/shadow
如果身份验证程序不始终以 root 身份运行,则还可能涉及临时权限升级以进行读取。)
在典型的非嵌入式 Unix 系统上,此过程的大部分分包给聚丙烯酰胺图书馆。 PAM 由一个主库(/lib/libpam.so.0
此处和其他地方的确切路径取决于系统)以及许多辅助库和程序组成。我不会详细介绍,因为它们都是同一软件套件的一部分。验证程序调用一系列PAM 库中的函数对用户进行身份验证并决定成功身份验证(会话建立)后要执行的操作。我认为pam_authenticate
是执行步骤 1 以及步骤 3-5 的部分功能(我不确定,因为我不熟悉 PAM 的这一面)。
使用 PAM,步骤 3-4(查找密码散列并根据它验证密码)在 中专门处理/lib/security/pam_unix.so
,用于传统基于密码的身份验证的 PAM 模块。该pam_unix
模块在执行身份验证的进程的上下文中运行,该进程可能无法以足够的权限运行来读取/etc/shadow
(但需要以足够的权限运行才能获得这些权限³)。为了最大限度地减少可以访问密码哈希的代码量,这部分在专用程序中运行unix_chkpwd
。该程序从 读取密码哈希/etc/shadow
,调用crypt
函数并验证其输出。
1具有误导性的名称,因为密码是散列的,而不是加密的 - 您无法“解密”内容以找到原始密码。
²由于历史原因,界面有点奇怪。中的字段/etc/shadow
包含 4 个部分:算法标识符、一些成本参数、盐字符串和预期输出字符串。算法标识符选择哪个密码散列使用算法 - 请注意,尽管顾名思义,这些都不是哈希算法。参见例如所有 Linux 发行版都使用相同的加密哈希函数吗?了解更多信息。成本参数取决于算法;较高的成本会使正常身份验证速度变慢,但如果攻击者设法检索哈希值,也会使破解密码变得更加困难。盐是唯一的,可以防止多帐户攻击(例如,尝试使用最弱密码进入员工的帐户,以在组织中获得立足点)。在内部,该crypt
函数使用算法标识符来确定要调用哪个辅助函数,并且在某些系统上该辅助函数可以位于不同的库中。
典型地,程序(login
、su
、 …)以root权限启动,并保留(至少其自身的一部分)root作为其已保存的用户 ID但改变了它的有效用户ID专用系统用户(登录时)或调用用户(提升权限时)。这最大限度地降低了登录程序中存在安全漏洞的风险,该漏洞允许攻击者获得登录程序的部分控制权(例如读取文件),但无法执行任意代码。提升权限需要调用专用系统调用,例如seteuid
。
答案3
负责计算输入密码哈希值的二进制文件通常是身份验证系统的一部分,并且不是由内核实现的。在大多数 Linux 发行版中,这是由一个名为的程序处理的地穴或者密码,并且使用的具体二进制文件可能因发行版而异。
以下是其工作原理的简单概述:
- 当用户登录或更改密码时,密码将传递到 crypt 或 passwd 实用程序。
- crypt 或 passwd 实用程序使用单向加密哈希函数计算输入密码的哈希值。使用的特定哈希函数可能有所不同,但通常基于 SHA-256 或 SHA-512 等算法。
- 然后将计算出的哈希值与
/etc/shadow
相应用户的文件中存储的哈希值进行比较。请注意,这意味着该实用程序需要具有读取影子文件的权限。 - 如果两个哈希值匹配,则身份验证成功,并且用户被授予访问权限。
散列和密码管理通常由用户空间程序处理,以确保灵活性以及与各种密码存储方案和散列算法的兼容性。内核仅负责用户空间身份验证实用程序之间的低级交互,并跟踪每个进程以什么用户身份运行。