配置使用 OTPW 的一次性密码身份验证

密码身份验证包含许多关于安全和信任的假设。加密的 SSH 隧道和公钥验证是确保您的密码在传输过程中不被泄露的两种常见方法。但是,如果您当前正在使用的计算机不可信任呢?

这不仅仅是偏执的 Linux 爱好者的杞人忧天。在许多日常情况和常见场所,即使通过安全隧道,您也可能不应该使用您的系统密码。例如包括

  • 酒店、图书馆或网吧的公共计算机。

  • 同事感染病毒的计算机。

  • 结对编程时共享的工作站。

  • 任何可能有人看到您输入密码的地方。

所有这些示例有什么共同之处?本质上,您正尝试从不受信任的来源连接到受信任的目标。这与大多数身份验证系统旨在解决的问题完全相反。

以公钥身份验证为例。SSH 公钥身份验证当然绕过了远程主机上的密码提示,但它仍然要求您信任本地机器上的私钥密码。此外,一旦密钥通过您的密码解密,本地系统就可以完全访问内部的敏感密钥材料。

糟糕——幸运的是,对于这个经常被忽视的问题,已经有了一个解决方案:一次性密码。

SSH 和一次性密码的结合非常强大

  • SSH 协议提供了网络上登录序列的加密。

  • 一个好的 SSH 客户端允许您在输入凭据之前检查远程主机的公钥指纹。这可以防止恶意主机收集您的一次性密码。

  • 一次性密码系统确保密码不会被重用。因此,即使密码在传输过程中被捕获,一旦您使用它登录,它对攻击者来说也毫无价值。

许多一次性密码解决方案可用于类 UNIX 系统。最著名的两个是 S/KEY 和 OPIE (Everything 中的一次性密码)。

随着最近从 Debian 和 Ubuntu 存储库中删除 OPIE,Markus Kuhn 创建的 OTPW 一次性密码系统提供了一个可行的替代方案。虽然不是 OPIE 的直接替代品,但 OTPW 提供了类似的功能,同时提供了一些 S/KEY 或 OPIE 中没有的有趣功能。

从 Debian 和 Ubuntu 存储库中删除 OPIE

在对二进制文件的安全性、许可问题和缺乏上游活动进行了一些讨论之后,Debian 于 2011 年初开始删除与 OPIE 相关的软件包。

如果您对详细信息感兴趣,以下 Debian 错误报告是相关的

虽然 OPIE 软件包在撰写本文时仍保留在当前的 Debian 稳定版本(代号为“Squeeze”)中,并且可以在 debports 存储库中找到一些非官方平台端口,但 OPIE 在 testing 或 unstable 中不可用,并且似乎不太可能包含在下一个稳定版本中。

特别是,OTPW 提供

  • 双因素身份验证,包括“前缀密码”和一组自动生成的、可丢弃的后缀。即使后缀列表落入坏人之手,如果没有前缀密码,也需要暴力破解。

  • 通过使用密码锁和三元组质询来防止某些竞争条件和中间人攻击。

  • 共享文件系统支持。由于 OTPW 针对存储在用户主目录中的哈希值列表检查密码,因此一个密码列表将适用于所有挂载相同 $HOME 目录的系统。

接下来,我将介绍 OTPW 的安装和使用,特别关注与 OpenSSH 的集成。

软件包安装

要使用 OTPW,您需要两个二进制文件:otpw-bin 和 libpam-otpw。对于 Debian 和 Ubuntu,安装非常简单,只需


sudo apt-get install otpw-bin libpam-otpw

如果您的发行版未提供 OTPW,您可以直接从作者的主页下载源代码。源代码 tarball 未使用 GNU autoconf,因此您需要按照作者的说明手动编译和安装二进制文件。

配置 PAM

为系统准备 OTPW 的下一步是配置 libpam-otpw。PAM 的完整处理超出了本文的范围,但我将在此处介绍最常见的用例。

更改 PAM 配置可能会将您锁定在工作站或服务器之外,因此最好保持现有终端打开,直到您确定一切正常工作。如果您有控制台访问权限,请准备好可启动的发行版或救援磁盘。有关通过 SSH 测试 PAM 的更多信息,请参阅“使用 SSH 测试一次性密码身份验证”侧边栏。

使用 SSH 测试一次性密码身份验证

如果您正在为远程系统配置 OTPW,您应该在不关闭当前 SSH 连接的情况下测试您的 PAM 堆栈。请记住,如果您在 PAM 配置中犯了错误,您可能无法进行身份验证——即使使用控制台访问权限——因此请准备好可启动的发行版,例如 Knoppix、SystemRescueCD 或 Finnix,以防万一。同时,现有登录不受影响,因为它们已经过身份验证。

为了正确测试 PAM 堆栈,您不能重复使用现有的 SSH 连接。大多数最新发行版都开箱即用地支持 SSH 多路复用和持久连接,因此请明确禁用这些选项以进行测试。

此外,SSH 默认首选公钥身份验证。因此,为了测试 OTPW 身份验证,也需要临时禁用公钥身份验证。

以下调用允许准确测试 SSH PAM 堆栈,而无需进行任何系统更改


read -p 'Hostname: ' REMOTE_HOST &&
SSH_AGENT_PID= SSH_AUTH_SOCK= \
  ssh \
  -o PreferredAuthentications=keyboard-interactive \
  -o ControlPersist=no \
  -o ControlPath=none \
  "$REMOTE_HOST"

一旦您确信 OTPW 运行正常,您还应该验证您的其他身份验证机制(即 SSH 公钥和标准系统密码)是否继续按预期工作。

启用 OTPW 最简单的方法是将其紧挨着 pam_unix 放在您的 common-auth 配置文件中


# /etc/pam.d/common-auth
auth       sufficient pam_otpw.so
session    optional   pam_otpw.so

auth       sufficient pam_unix.so nullok_secure
auth       required   pam_deny.so

PAM 库的顺序非常重要。当将 OTPW 放在首位时,具有 ~/.otpw 文件的用户将首先被提示输入一次性密码,如果 OTPW 登录失败,则允许回退到标准系统密码。没有 ~/.otpw 文件的用户将只看到标准密码提示。

如果您希望颠倒顺序,在回退到一次性密码之前先提示输入系统密码,只需确保 pam_deny 放在最后


# /etc/pam.d/common-auth
auth       sufficient pam_unix.so nullok_secure

auth       sufficient pam_otpw.so
session    optional   pam_otpw.so

auth       required   pam_deny.so

如果您想完全删除标准系统密码,特别是从控制台登录中删除,请不要这样做。在某些系统上,最值得注意的是具有 ecryptfs 加密主目录的 Ubuntu 系统,如果没有标准系统密码,从 OTPW 事故中恢复非常困难。

修改 common-auth 通常是在无头服务器或仅控制台系统上要做的正确的事情。但是,提供 X Window 系统的​​工作站或服务器为一次性密码系统带来了特殊问题。

某些工具或应用程序无法与 OTPW 正常工作,因为它们无法向用户显示质询。典型的症状通常是密码对话框永远无法完成或似乎忽略用户输入。过去,gksu 和 GNOME Display Manager (GDM) 在使用 OPIE 时遇到了这个问题。在这种情况下,解决方案是将 OTPW 从 common-auth 中移出,并仅将其包含在特定服务中。

例如,您可以将 OTPW 身份验证添加到 SSH 连接,同时仅将标准密码提示用于控制台或 GUI 登录。您可以通过三个简单的步骤完成此操作

1. 从 common-auth 中删除任何引用 pam_otpw.so 的行


# /etc/pam.d/common-auth on Debian Squeeze
auth       sufficient pam_unix.so nullok_secure
auth       required   pam_deny.so

2. 为 PAM 创建一个新的 OTPW 包含文件


# /etc/pam.d/otpw
auth           sufficient      pam_otpw.so
session        optional        pam_otpw.so

3. 在 /etc/pam.d/sshd 中,将 OTPW 立即包含在 common-auth 之前


# Other stuff ...

# Enable OTPW authentication.
@include otpw

# Standard Un*x authentication.
@include common-auth

# More stuff ...
SSH 配置

除了配置 PAM 库之外,OTPW 还需要 SSH 守护程序的配置文件中的以下三个设置


# /etc/ssh/sshd_config
UsePrivilegeSeparation yes
UsePAM yes
ChallengeResponseAuthentication yes

这些通常都在那里,但可能被注释掉或设置为“no”,因此请相应地修改它们。接下来,修改其配置文件后重新加载 SSH 守护程序


# Generic Linux
sudo /etc/init.d/ssh reload

# Debian 6.0.4+
sudo service ssh reload

# Ubuntu 11.04+
sudo reload ssh
生成 OTPW 密码

一旦正确配置了 OTPW PAM 模块,只有具有 ~/.otpw 文件的用户才会在登录期间被质询输入一次性密码对话框。此文件包含有关其内容的一些元数据,以及与质询的有效响应相匹配的单向哈希列表。

要创建此文件,或使用新密码重新填充它,请使用 otpw-gen 实用程序。默认情况下,它将创建 280 个密码后缀,格式化为适合单面美式信纸尺寸 (8.5" x 11") 的纸张。由于只有单向哈希存储在 ~/.otpw 中,而不是密码本身,因此您必须在生成密码时捕获或打印此命令的标准输出。事后您将无法检索密码列表;您需要生成新密码。

以下是首次运行命令时的情况,将输出通过管道传输到您的默认打印机


$ otpw-gen | lpr
Generating random seed ...

If your paper password list is stolen, the thief
should not gain access to your account with this
information alone. Therefore, you need to memorize
and enter below a prefix password. You will have to
enter that each time directly before entering the
one-time password (on the same line).

When you log in, a 3-digit password number will be
displayed. It identifies the one-time password on
your list that you have to append to the prefix
password. If another login to your account is in
progress at the same time, several password numbers
may be shown and all corresponding passwords have to
be appended after the prefix password. Best generate
a new password list when you have used up half of
the old one.

Enter new prefix password:
Reenter prefix password:

Creating '~/.otpw'.
Generating new one-time passwords ...

生成新密码列表时,标准错误上出现的提示略有不同


Overwrite existing password list '~/.otpw' (Y/n)?

Enter new prefix password:
Reenter prefix password:

Creating '~/.otpw'.
Generating new one-time passwords ...

第一个提示确保您不会意外覆盖现有的密码列表;第二个提示要求您输入新密码。没有什么可以阻止您在每次调用时重复使用相同的前缀密码——随机种子使得重复哈希不太可能——但最佳实践是每次重新生成密码列表时都使用新的前缀。

如果您想在远程主机上生成密码列表,但在本地打印机上打印,只要您信任您的 localhost,就可以通过 SSH 连接执行此操作


read -p 'Hostname: ' &

请注意使用 stty 来确保您的前缀密码不会回显到屏幕上。只要您的前缀密码保持安全,那么与您的密码列表落入坏人之手相比,使用不受信任的打印机并不会使您的情况更糟。这对于经常旅行的人来说通常是一个有价值的安全权衡。

最后,要为给定用户禁用 OTPW 质询,只需删除该用户主目录中的 .otpw 文件即可。

使用 OTPW 登录

一旦您手头有密码列表,您就可以为您的 SSH 连接使用一次性密码身份验证了。假设您没有将任何身份加载到您的 SSH 代理中,您的对话框应类似于这样


$ ssh localhost
Password 015:

带有数字的提示是 OTPW 质询。要响应,请在您之前打印的密码纸上找到匹配的质询 ID。接下来,输入您的前缀密码,后跟质询 ID 后的字符串。

使用“foo”作为前缀密码,生成了以下后缀列表。即使您使用相同的前缀密码,您的列表和后缀也会有所不同。


OTPW list generated 2012-05-06 13:40 on localhost

000 SWvv JGk5 004 =qfF q2Mv 008 sb5P h94r 012 o5aH +/GD 016 8eLV VxuA
001 xPZR :ceV 005 B=bq =mHN 009 WBSR smty 013 QMZ% +bm8 017 vjFL K4VU
002 Sj%n 9xD3 006 RrNx sJXC 010 Xr6J F+Wv 014 j=LO CMmx 018 Km8c 8Q3K
003 s7g8 NE%v 007 sd=E MTqW 011 fNKT vo84 015 fWI% MB9e 019 z8ui %eQ3

            !!! REMEMBER: Enter the PREFIX PASSWORD first !!!

要成功响应此质询,请键入


foo fWI% MB9e

在提示符下。空格是可选的;OTPW 会忽略它们(如果存在)。

如果您正确回答了质询,登录将继续进行。否则,将提示您进行标准系统登录。此时,您可以输入您的标准系统密码,或按 Return 键再次尝试 OTPW。在系统定义的密码尝试次数(通常为三次)之后,登录将失败并返回到命令提示符


$ ssh localhost
Password 013:
Password:
Password 013:
Password:
Password 013:
Password:
Permission denied (publickey,password,keyboard-interactive).

为了防止同时登录,或者当 SSH 在 OTPW 身份验证期间中断时,OTPW 可能会锁定密码。当密码被锁定时,您的下一次登录尝试将显示三元组质询,该质询需要一个前缀和三个后缀来响应


$ ssh localhost
Password 004/011/005:

给定与之前相同的密码列表,以单行输入您的三元组响应,带或不带空格。以下显示了如何组成响应(请注意,下面的第一行只是信息辅助;您将仅键入下面的第二行,不带管道字符)


prefix | suffix 004 | suffix 011 | suffix 005
foo    | =qfF q2Mv  | fNKT vo84  | B=bq =mHN

一旦您成功响应了三元组质询,登录将继续进行,并且应删除 ~/.otpw.lock 符号链接,并且您的下一个质询将再次是单个质询 ID 号。

在某些情况下,锁未正确删除。如果您继续被提示输入三元组,您可以手动删除锁定文件


rm ~/.otpw.lock

在登录之前未挂载加密主目录的用户需要采取一些额外的步骤。有关示例,请参阅“OTPW 和加密主目录”侧边栏。

OTPW 和加密主目录

ecryptfs 文件系统为 SSH 和 OTPW 带来了特殊问题。默认情况下,像 Ubuntu 这样的发行版会使用用户的系统密码解包挂载加密主目录所需的特殊密码。

这由 pam_ecryptfs.so 模块处理,该模块包含在 /etc/pam.d/common-auth 和其他文件中。如果您使用系统密码以外的任何方式进行身份验证,该模块会提示您输入系统登录密码,以便挂载加密的主目录。

实际上,这意味着当挂载远程主目录时,您的系统密码会在不受信任的终端上暴露。这显然不是理想的。

避免这种情况的最佳方法是始终保持控制台会话运行。例如,使用您的系统密码在控制台登录,然后锁定屏幕。只要您的控制台会话保持活动状态,您的主目录就会保持挂载状态。因此,您可以使用 OTPW 身份验证,而无需对系统进行进一步更改,并且您不会在登录或挂载期间泄露您的系统密码。

但是,如果您仍然希望能够在控制台会话未运行时使用 OTPW 进行 SSH 登录——并且了解这样做的安全隐患——以下是操作方法。

首先,您需要创建一个包装器脚本来调用 otpw-gen


#!/bin/bash
set -e
otpw-gen "$@"
mv ~/.otpw /usr/local/lib/otpw/$LOGNAME/
ln -s /usr/local/lib/otpw/$LOGNAME/.otpw ~/

包装器应放置在您的路径中并使其可执行。

接下来,将 otpw4ecryptfs.sh(如下所示)放置在 ~/bin 或 /usr/local/sbin 中


#!/bin/bash

# Purpose:
#     Enable OTPW for all users on systems with
#     ecryptfs-mounted home directories.

set -e

# Expose the underlying directories that may be
# hidden by ecryptfs.
sudo mkdir -p /mnt/real_home
sudo mount -o bind /home /mnt/real_home

# Collect all non-system users.
users=$(
    awk -F: '$1 != "nobody" \
             && $3 >= 1000  \
             && $3 < 65534  \
             {print $1}' /etc/passwd
)

# Enable OTPW for each non-system user.
for user in $users; do
    sudo mkdir -p /usr/local/lib/otpw/$user
    sudo touch /usr/local/lib/otpw/$user/.otpw
    sudo chown -R $user: /usr/local/lib/otpw/$user
    sudo chmod 755 /mnt/real_home/$user
    ln -sf /usr/local/lib/otpw/$user/.otpw \
           /mnt/real_home/$user/
    ln -sf /usr/local/lib/otpw/$user/.otpw \
           /home/$user/
done < /etc/passwd

sudo umount /mnt/real_home

当您运行该脚本时,它会创建 pam_otpw.so 即使在用户的主目录未挂载时也可以读取的 OTPW 文件。

请注意,此脚本为所有用户的主目录授予读取和执行权限,以便 pam_otpw.so 可以读取 OTPW 密码文件。这本身不是风险,但依赖于更严格的目录权限的用户可能希望立即收紧其主目录中文件和文件夹的权限。

最后,所有用户都应运行 otpw-gen-wrapper.sh 来填充和维护其 OTPW 密码列表。始终使用包装器而不是直接调用 otpw-gen,否则密码生成将破坏正常操作所需的符号链接。

检查剩余密码

如果您的密码列表已用尽,您将无法再使用 OTPW 登录,直到生成新的列表。同样,如果您的密码列表不包含至少三个未使用的响应,您将无法在 ~/.otpw.lock 存在时使用 OTPW 登录,因为没有足够的质询 ID 来发出三元组。

此外,OTPW 的某些安全性来自剩余质询的随机性。特别是三元组的使用会迅速耗尽您未使用的密码,因此最好在未使用的密码数量低于最低量时重新生成密码列表。

OTPW 作者建议在剩余未使用密码少于原始密码一半时重新生成密码列表,但未定义确保质询充分随机性所需的最小密码数量的下限。少量未使用的密码会使您更容易受到暴力攻击,因为可以呈现的质询更少。

pam_otpw.so PAM 模块应该在未使用的密码低于生成密码的一半时通知用户。但是,PAM 会话功能似乎在 Debian 或 Ubuntu 上不起作用。此外,即使它有效,该模块也没有建立下限以确保质询的充分随机性。

清单 1 中显示的 otwp-stats.sh 脚本提供了此缺失的功能。它还允许您通过调整脚本顶部的 MIN_PASSWORDS 变量来定义未使用的密码的合理最小值。

清单 1. otwp-stats.sh

#!/bin/bash

# 30 unused passwords seems like a reasonable, if
# arbitrary, floor to ensure randomness and a small
# cushion against triplet exhaustion. Feel free to
# adjust this number to suit your needs.
MIN_PASSWORDS=30
OTPW_LIST="$HOME/.otpw"

# Stop processing if OTPW isn't set up for this
# user.
[ -f "$OTPW_LIST" ] || exit

# The top two lines of an OTPW file are meta-data.
TOTAL_PASSWORDS=$((`wc -l < "$OTPW_LIST"` - 2))
# Lines with dashes represent used passwords.
USED_PASSWORDS=$(egrep '^-' "$OTPW_LIST" | wc -l)
# The number of passwords remaining is a calculated
# value.
PASSWORDS_LEFT=$((TOTAL_PASSWORDS - USED_PASSWORDS))

cat << EOF
OTPW Password Statistics
------------------------
    Passwords used: ${USED_PASSWORDS:=0}
    Passwords left: $PASSWORDS_LEFT

EOF

if [ $PASSWORDS_LEFT -le $((TOTAL_PASSWORDS / 2)) ]
then
    echo "It's time to generate new OTPW passwords."
elif [ $PASSWORDS_LEFT -le $MIN_PASSWORDS ]; then
    echo "Remaining passwords at critical levels."
    echo "It's time to generate new OTPW passwords."
fi

将 otwp-stats.sh 添加到您的 ~/.profile(或其他 shell 启动脚本)以在登录时提供反馈


# Only run script when logging in via SSH.
[ -n "$SSH_CONNECTION" ] && ~/bin/otpw-stats.sh
结论

OTPW 提供了一种一次性密码实现,与 OPIE 和 S/KEY 相比,它具有优势。它很容易在大多数 Linux 系统上与 SSH 集成,并且仍然可以在具有加密主目录的 Ubuntu 系统上使用。

资源

OTPW 源代码:http://www.cl.cam.ac.uk/~mgk25/otpw.html

密码图片,来自 Shutterstock.com

Todd A. Jacobs 是 Flow Capital Group 的首席执行官,该公司收购和管理专门从事 IT 自动化、DevOps 和敏捷转型、安全与合规性、兼职 CIO/CTO 服务以及网络风险和其他热门技术问题的董事会咨询服务的公司。Todd 一生都在等待 Matt Smith 在《神秘博士》中扮演的角色再次让领结变得酷炫。他与妻子和儿子住在马里兰州巴尔的摩附近,他希望将他对 Linux 和技术的热爱传递给他们——但也许不包括他的时尚感。

加载 Disqus 评论