第九章:一次性密码:谷歌身份验证器

一次性密码是只使用一次的密码。我们已经实现了许多一次性密码,从万物一次性密码(One-Time Passwords in Everything —— OPIE)到RSA令牌。基于时间的一次性密码系统(Time-Based One-Time Passwords —— TOTP)使用系统时钟和共享密钥按需计算有效密码。最著名的TOTP实现是Google身份验证器(Google Authenticator)。

Google身份验证器不需要访问Google。之所以称之为谷歌身份验证器,是因为谷歌提供了该软件,而不是因为它以任何方式连接到谷歌的系统。GA在连互联网都没有连接的机器上运行良好。

Google Authenticator使用客户端应用程序和Google Authenticator PAM模块将您的智能手机、平板电脑或Chrome浏览器转换为身份验证令牌。当您尝试使用Google Authenticator访问服务时,PAM模块会计算一个六位数的密码作为临时一次性密码。服务器会提示您输入此密码。您的设备使用相同的方法计算此主机的当前正确密码。在应用程序中输入设备提供的密码。如果两台设备进行了相同的计算,则密码匹配,您就可以访问。

Google Authenticator是一种比使用SSH代理更好的多因素身份验证方法,但它并不完美。任何平板电脑、智能手机或Chrome浏览器都可以成为身份验证令牌。如果坏人捕获了你的任何设备,或者窃取了用于配置客户端应用程序的共享密钥,他们就会获得你的身份验证令牌。但至少这些设备是物理对象,而不是入侵者可以轻易复制的文件。

每个使用GA的主机都有一个单独的GA配置和一个唯一的共享密钥。每个用户都必须登录到她应该访问的每个主机,并配置她的GA访问权限。谷歌身份验证器本身最适合只有少数服务器和少数帐户的环境。除非你投入员工时间来管理身份验证,否则你不会想让一个拥有数千个帐户的主机使用普通的谷歌身份验证器。您可以在主机之间共享GA配置,正如我们稍后将讨论的那样,但这会改变GA的安全配置文件,并需要额外的脚本和配置。我们将从最简单的情况开始,在单个主机上设置GA。

在开始使用谷歌身份验证器之前,请检查主机上的时钟和时区。谷歌身份验证器在时钟与世界其他地区同步的服务器上效果最佳。系统时钟经常偏离实时,尤其是在虚拟机上。使用 ntpd(8) 或其他计时软件将服务器的时钟固定在其他人的时钟上。如果GA莫名其妙地失败了,请仔细检查所有东西的时钟和时区。

仅使用具有身份验证策略的Google身份验证器。它不为帐户、会话或密码类型提供任何服务。

谷歌身份验证器几乎在任何地方都能工作;它没有像pam_ssh_agent_auth那样的环境要求。但是,我鼓励您在将其应用于所有系统身份验证之前仔细考虑。当服务器的时间喝醉并剧烈倾斜时会发生什么?如果你需要GA在控制台登录,解决这个问题需要进入单用户模式,可能需要重新启动。你有足够的冗余来处理这个问题吗?此外,谷歌身份验证器具有内置的登录速率限制。如果你同时需要SSH和sudo的GA,你将直接面临限速。

仔细考虑部署决策的影响。

安装 Google Authenticator

Google身份验证器客户端很简单。从设备的应用商店获取您的应用程序,或为您的桌面安装身份验证器Chrome扩展程序。

要使用Google身份验证器的每台服务器都必须安装Google身份验证器PAM模块。FreeBSD有一个pam_google_authenticator包,但大多数用户也希望安装libqrencode包。Debian包含libpam-google authenticator软件包,但很少是最新版本。目前还没有针对CentOS的官方软件包,尽管在撰写本文时,EPEL包括针对CentOS 6及更早版本的软件包。若要在当前版本的CentOS上使用Google身份验证器,或在Debian上使用当前的Google身份验证器,您可能必须从源代码安装。

从获取当前源代码https://github.com/google/google-authenticator,可以使用 git(1) 或下载并解压缩zip文件。libpam 目录包含Google身份验证器PAM模块。进入该目录以构建和安装库:

现在,您可以使用Google Authenticator PAM模块和辅助程序。

Google Authenticator 用户功能

许多GA教程告诉用户运行谷歌验证器,并对所有问题回答“是”。这就是疯狂。做出糟糕决定的用户可能会毁了他们的一天,更糟糕的是,会惹恼服务台。给你的用户非常严格的说明如何配置客户端,或者更好的是,为他们编写脚本。

密码类型

GA支持两种类型的密码:基于时间(time-based)的密码和基于计数器(counter-based)的密码。

time-based 的方法结合共享密钥和当前时间来创建临时一次性密码。这些密码的有效期只有30秒左右。基于时间的一次性密码记录在RFC 6238中。

counter-based 的密码计算一系列密码,每个密码只能使用一次。这是一个简单的列表,不依赖于时间。基于计数器的密码基于RFC 4226。

这两种方法都需要服务器和设备之间的同步。基于时间的密码需要设备上的时钟和服务器上的时钟之间达成一致。基于计数器的密码要求用户在用餐时避免拨打数百个密码。虽然我可以想象基于计数器的密码有意义的情况,但在通风的现实中,我建议使用基于时间的密码。

文件管理

Google身份验证器使用文件 $HOME/.google_authenticator 来管理用户的身份验证设置和当前的身份验证状态。(系统管理员可以更改文件位置,我们稍后会讨论。)甚至在此文件中也会进行速率限制。

定时和速率限制

试图访问经过身份验证的服务的入侵者使用暴力攻击,他们每分钟尝试数百或数千个凭据,看看其中是否有任何一个有效。任何一组凭据有效的几率都很低,但计算机非常有耐心,在点击之前不介意尝试所有可能的组合。每种服务都需要速率限制,以将这种攻击从洪流遏制到涓涓细流。如果您的服务没有其他速率限制方式,Google身份验证器可以对密码的使用次数和用户的身份验证频率进行速率限制。

GA建议您允许每个密码只使用一次。对于某些环境,这是完全合理的。许多组织使用的开放式身份验证(Open Authentication —— OATH)标准只需要一次密码。

我们中的一些人有更宽松的安全要求,发现一次性密码无法忍受。如果解决一个紧急问题需要登录到服务器并评估其状况,我不想要一个SSH会话:我想要三到四个终端窗口,而且我现在就想要它们。如果我使用GA对SSH和 sudo(1) 进行身份验证,并且每个密码只能使用一次,那么获得特权访问需要更长的时间。

即使在不需要OATH且安全要求较宽松的环境中,如果你使用GA允许访问未加密的服务,允许每个密码只使用一次也是完全有意义的。选择满足组织需求的解决方案。

用户可以调整时钟灵敏度。每个基于时间的密码有效期为30秒。GA PAM模块默认情况下允许少量的时钟偏差,将上一个和下一个密码视为有效。如果时钟正确同步,用户的密码有效期约为90秒。用户可以在她的GA配置文件中启动此功能。

谷歌身份验证器还可以限制用户每30秒登录尝试次数不超过三次。高级用户和系统管理员会发现这比只允许一次登录更受欢迎。不过,允许多次登录尝试违反了OATH标准。

所有这些选项都在用户配置程序中设置。

GA 用户配置

要配置GA,每个用户都需要一个具有合适软件和每个用户配置的设备。

设备软件

官方的谷歌身份验证器软件可用于许多智能手机、平板电脑和Chrome浏览器。然而,由于一次性密码是基于公开可用的标准,人们已经编写了其他可以充当GA客户端的应用程序。有一个Firefox插件。Authy(https://www.authy.com)是一个受欢迎的选择。Windows用户可能更喜欢WinAuth(https://WinAuth.com)。四处看看,找到一个你喜欢的客户。

配置设备的最简单方法需要一个二维码阅读器。如果您没有,Google Authenticator安装程序会为您的设备推荐一个。

我的示例使用官方的Google身份验证器客户端。虽然我的测试设备是Chrome浏览器和几台Android设备,但官方GA客户端应该在其他平台和操作系统上同样工作。

在继续之前,请在设备上安装您选择的客户端。

用户配置

在配置PAM之前,请登录到将使用Google身份验证器的服务器。运行谷歌验证器以创建配置。

该程序会询问一系列问题,以确定您的帐户应如何进行身份验证。

如前所述,使用基于时间的代码。

安装程序会立即输出设备配置,包括密钥、二维码和URL。它还提供五个一次性代码。然而,这些代码和链接还不是永久性的。在将其永久化之前,请使用其中一个来配置您的设备。

如果你的服务器安装了libqrencode库,谷歌验证器会显示一个二维码。使用二维码是在手机上获取共享密钥的最简单方法。在您的设备上捕获二维码,它会自动为设备配置谷歌身份验证器。

如果您将Chrome用作Google身份验证器设备,请将URL复制到浏览器中。你准备好了。 如果这两种方法在您的设置中都不起作用,谷歌验证器还会显示共享密钥,这是一个烦人的长字符串。请手动将其输入您的设备。

使用这三种设置方法中的任何一种,您的GA应用程序都应该立即开始显示六位数字代码。 将秘密和紧急划痕代码复制到安全位置,最好是在纸上。你甚至可以打印二维码,在页面顶部写上服务器名称,然后把它塞进一个锁着的抽屉里。如果您丢失了设备或出现了严重问题,您将需要紧急情况和二维码,如本章后面的“设备盗窃”和“灾难恢复”所述。

一旦你的设备知道代码,通过更新用户的GA配置文件使其永久化。

回答 n ,安装程序将丢弃所有代码。回答 y ,您的帐户已准备好进行GA。

然后,你会得到三个关于用户GA应该如何表现的问题。我们在本章前面的“Google身份验证器用户功能”中讨论了这些选项。希望在走到这一步之前,你已经决定了GA应该如何表现。

回答 y 意味着用户每30秒只能进行一次身份验证。回答n意味着代码在有效时是可重用的。

回答 y 可以延长个人密码的使用寿命。回答n会使其处于默认状态。

回答 y 将登录尝试限制为每30秒三次。回答 n 将禁用速率限制。

编写用户设置脚本

Google身份验证器用户设置过程很简单。当然,你的用户可以处理键入 y 、捕获二维码以及键入三个 yn 的特定序列吗?

任何担任系统管理员一周以上的人都知道这个问题的答案是“绝对不会”。如果不止几个人会使用受GA保护的服务,请编写配置过程脚本。这是一个简单的脚本,它运行谷歌身份验证器,两次回答 y ,三次回答 n

\n 是回车键或ENTER键。printf命令输出y-ENTER、y-ENTER、n-ENTER、n-ENTER、n-ENTER,并将它们输入到谷歌验证器中。运行此脚本的用户可以获得他们的二维码、网址、密码和紧急刮刮码(emergency scratch codes),而无需做出任何决定。

如果你运行 google-authenticator –h ,你会得到一个命令行标志和选项列表,让你预先回答问题。写你喜欢的剧本。

如果你的组织足够大,你的脚本可能应该隐藏除二维码、URL和密码之外的所有输出。你甚至可以将输出内容简化为二维码。

GA 和 PAM

配置帐户后,您可以直接运行服务的PAM配置并激活pam_google_authenticator。(如果您想将GA与SSH一起使用,请记住查看第0章中的SSH和PAM信息。)

但是,如果你有自己以外的用户呢?用户需要登录主机来配置GA,但启用GA会将其锁定。Bootstrap使用nullok选项解决了这个问题。缺少GA配置的用户可以在没有GA配置的情况下登录。然而,一旦GA配置存在,用户必须输入代码才能登录。

如果你这样做,让系统将用户直接转储到谷歌身份验证配置脚本中。否则,您的一些用户将永远不会真正配置GA。要真正对许多服务器上的许多用户实施多因素身份验证,您必须集中管理GA。

中央GA管理

拥有许多服务器的企业几乎肯定会使用Ansible或Puppet等管理工具来将其环境保持在一起。该组织的系统管理员比保姆用户、复制配置文件和重启服务有更好的事情要做。企业软件必须集中管理。

默认的Google身份验证器配置假定用户是受信任的。企业身份验证系统不信任用户。例如,企业用户可以更改密码,但不允许禁用速率限制。Google身份验证器可以在这些环境中使用。

每个用户的 $HOME/.google_authenticator 文件不仅包含用户的共享密钥。它还包含用户的定时、速率限制和时钟灵敏度设置。每个组织都有聪明的人,他们可以弄清楚如何使用该系统,但也有不明智的人去尝试。此外,有时用户的主目录是通过NFS加密或挂载的,因此在身份验证完成之前不可用。在这些企业中使用Google身份验证器需要通过文件和所有者选项从用户的控制中删除文件。

file选项允许系统管理员为用户的 .google_authenticator 文件设置不同的位置。使用等号和文件位置。该选项将变量~和${HOME}识别为用户的主目录,将${user}识别为用户名。

在这里,每个用户的GA配置都位于其主目录之外。这使系统可以等待装载主目录,直到身份验证完成。通过使用用户拥有的用户特定目录,每个用户都可以继续管理自己的GA配置。该文件应归用户所有,并具有模式400。

您可能不希望用户管理自己的GA配置。多个组织要求其用户在受到严格保护的注册服务器上配置其Google身份验证器。注册服务器会自动将该配置传播到组织中的所有其他服务器。此类企业中的用户不应有权在所有其他服务器上编辑其配置文件,可能在注册服务器上也没有。(注册脚本可能会对用户隐藏除二维码之外的所有内容。)在这种情况下,文件应由系统用户拥有,并且禁止用户访问文件本身。为此使用用户选项。

在这里,用户的GA配置隐藏在/etc/GA目录中,在一个以用户命名的文件中。我的身份验证配置将是 /etc/ga/mwl 。该文件归用户 google 所有,应具有模式400。虽然系统管理员可以轻松识别单个用户的文件,但更新配置的唯一方法是通过管理系统。

不允许用户访问其配置会阻止Google身份验证器使用某些功能。该模块将所有计时信息保存在配置文件中。如果用户无法更新文件,则速率限制功能将不起作用。在设置过程中,除了写入原始文件外,对所有功能都回答 n

虽然以这种方式使用谷歌身份验证器需要额外的编程,但它并不比使用其他“enterprise-ready(企业就绪) ”的令牌系统差。

时间偏移调整

Google身份验证器可以尝试解释不准确的系统时钟。三次身份验证失败,每次间隔30秒,向GA PAM模块表明时钟有偏差,并暗示偏差有多严重。

使用 noskewadj 选项关闭此行为。如果系统时钟非常不准确,以至于偏斜调整是有意义的,那么修复时钟比微妙地暗示存在问题更有意义。

密码显示

就像密码一样,密码不会回传给用户。要将密码回传给用户,请使用模块选项 echo_verification_code

密码是短暂的实体。在许多部署中,密码只能使用一次。即使有人越过用户的肩膀查看密码,密码也会在一分钟左右过期。大多数时候,为用户显示输入的密码会大大减少支持电话。用户——是的,甚至是你的用户——可以在他们看到的时候认出他们输入了错误的六个数字。

同时输入密码和密码

一些启用PAM的应用程序和模块(如Radius身份验证或将PasswordAuthentication设置为yes的SSH)无法很好地处理多个身份验证提示。您可以使用 forward_pass 选项告诉Google Authenticator在单个请求中同时请求密码和通行码。GA将消化密码,但将密码交给下一个模块。下一个模块必须使用 use_first_pass 选项来接受转发的密码:

试图进行身份验证的用户将收到一个提示,如下所示:

用户需要输入她的密码、空格和Google身份验证器密码。如果密码正确,GA会将密码转发给下一个模块。在这种情况下,由于下一个模块就足够了,正确的密码会立即允许访问。

不能将forward_pass和echo_verification_code组合使用。好吧,你可以,但GA PAM模块既会用密码回显密码,也会拒绝身份验证请求,因此这种组合没有用。

新设备

一旦配置了Google身份验证器,您几乎可以忽略它……直到您获得新的智能手机、平板电脑或电脑。您可能希望使用与旧设备相同的身份验证设置配置新设备。有时,你真的会把旧手机换成新的。

还记得当你第一次为GA配置帐户时,我说过要备份安全代码吗?这正是原因所在。Google身份验证器不会重新显示现有安装中的密钥、配置URL或二维码。您必须使用您保留的备份副本。手动输入安全码很烦人,但比重新配置所有服务器并在所有其他设备上加载这些新配置要好得多。

灾难恢复

使用Google Authenticator,您可能会遇到两种不同的灾难:设备被盗或PAM模块损坏。两者都有类似的灾难恢复考虑。

失去多因素身份验证系统的一个因素并不是灾难性的,但确实需要快速、深思熟虑的行动。如果您丢失了设备和密码、SSH密钥或其他身份验证,请以接近恐慌的速度采取行动。让受影响的用户在所有受影响的主机上重新运行谷歌身份验证器或您组织的包装器脚本。如果情况紧急,请使用 su(1) 为用户运行它。

另一个灾难是系统的时钟坏了。那么GA密码将不起作用。

当你运行谷歌验证器时,它会在二维码后显示五个八位数的代码。这些是紧急代码(emergency codes)。用户可以使用它们进行身份验证,但每个代码只工作一次。如果您备份了这些代码并使其可用,则可以使用它们进入系统并修复时间。如果您的紧急代码不足,请重新运行谷歌验证器以更改您的密码并生成新的紧急代码。

GA在允许访问之前从用户的配置中删除一次性代码。这要求用户具有对其配置文件的写访问权限。如果您的用户配置由其他用户拥有,则紧急代码将不起作用。

GA 文件格式

Google身份验证器的配置文件 .google_authenticator 仅包含几个可能的选项。

第一行总是共享的秘密。不要搞砸了。并非所有可能的代码都是有效的,所以你不能设定一个公司标准,即每个用户的秘密都是他们的名字和社会安全号码。

RATE_LIMIT 选项的存在启用了速率限制。它需要两个参数:允许多少次身份验证尝试,以及允许多少秒。速率限制要求用户能够写入配置文件。

WINDOW_SIZE 选项决定了您的密码对时间的敏感程度。1表示只有系统时钟指示的当前密码才是可接受的。每增加两个意味着在当前密码之前和之后都可以接受额外的密码。将其设置为17,如果您接受谷歌身份验证器的建议,允许四分钟的窗口,则使用该值,这意味着当前时间的密码加上八个较早的代码和八个较晚的代码是可以接受的。

DISALLOW_REUSE 选项意味着每个密码只能使用一次。

该文件以用户剩余的一次性密码结束。虽然您可能很想将自己的一次性代码添加到此文件中,但GA会对紧急代码进行额外的验证。在文件末尾添加00000001无效。此外,编辑紧急代码的行为会使剩余的紧急代码无效。

现在我们已经保护了远程访问,让我们考虑一些对本地用户有用的东西。