转自: http://msdn.microsoft.com/zh-cn/magazine/cc507635.aspx
安全性
使用一次性密码解决方案更安全地验证身份
Dan Griffin
本文讨论:
| 本文使用了以下技术: IIS 7.0、SQL Server |
目录
密码的安全和管理是令企业 IT 管理员 非常头疼的一个问题。用户往往创建非常简单的密码,或将其密码写下来以确保他们能够记住这些密码。此外,几乎没有安全有效的程序来重置密码。
既 然存在这些限制,如何才能在远程用户访问您的网络时减少此类安全问题呢?由于许多用户都会写下他们的密码,如何才能使公司的密码解决方案更加可靠呢?我将 介绍如何使用基于标准的技术以及 C# 和 C 来开发一次性密码 (OTP) 概念验证。但是,我想先大致介绍一下密码替代技术。
有多种方法可用于消除远程用户的标准密码。可使用证书颁发机构来向用户颁发证书,但这需要公钥基础结构 (PKI),并且其设置和维护成本比较昂贵。同时还难于管理远程用户的证书,尤其是使用基于硬件的令牌(如智能卡)时。此类高安全性常常导致高成本。
另外,可使用 RSA 提供的一次性密码解决方案 SecureID。但是,应当注意 SecureID 并非基于标准的技术,因此它可能导致不兼容问题和授权开销。
第三种选择是使用基于标准的 OTP 解决方案。但是,它有些什么类型的一次性密码选项,为什么 OTP 优于传统的密码?我们这就来研究一下。
一次性密码
传统的静态密码通常只在需要时才进行更改:当它过期时或者当用户忘记密码并需要重置时。由于密码缓存在计算机硬盘上并存储在服务器上,因此它们很容易被破解。便携式计算机尤其如此,因为它们容易被盗。
许多企业都为员工配备了便携式计算机,并将网络打开以允许远程访问。他们还会雇用临时员工和供应商。在此环境中,简单的静态密码解决方案可能成为一种不利因素。
与静态密码不同,一次性密码会在用户每次登录时发生更改。密码有以下两种生成方式:与时间同步或与计数器同步。两种方法通常都要求用户携带一个与服务器同步的小型硬件设备(通常可以挂在钥匙链上),并且通常都使用一些算法来生成密码。
与 时间同步的 OTP 已得到广泛部署,但可能出现时钟偏差问题。即,如果身份验证服务器和用户令牌的时间不同,则无法生成预期的 OTP 值,并且用户身份验证将失败。使用与时间同步的 OTP 时,用户通常必须在特定时间段内输入该密码,否则会将其视为过期且必须生成另一密码。
与计数器同步的 OTP 解决方案将同步客户端设备与服务器之间的计数器。每次请求设备的 OTP 值时,计数器都会增加。就像使用与时间同步的 OTP 一样,当用户想要登录时,他必须输入设备上当前显示的 OTP。
用 于应对复杂情况的 OTP 是一种特殊情况,它们通常还会使用硬件设备。但是,用户必须提供一个已知值(如个人标识号 (PIN))才能生成 OTP。此类 OTP 目前正在欧洲广泛应用,用于为信用卡和借记卡增加身份验证。目前使用的 OTP 解决方案均建立在某种加密处理之上,以根据同步参数(即时间或计数器值)、密钥以及可能需要的 PIN 生成当前密码。
例如,基于哈希的 OTP 使用加密哈希算法来计算密码。如您所知,加密哈希是一种单向函数,即将任意长度的消息映射为固定长度的摘要。因此,基于哈希的 OTP 首先进行输入(同步参数、密钥、PIN),然后通过单向函数运行它们,并且生成固定长度的密码。
那 么,究竟应选择哪种方法呢?由于希望了解其工作原理,因此我创建并测试了一个解决方案。接下来,我将介绍如何使用 IIS 7.0 和密钥哈希消息身份验证来创建与计数器同步的 OTP(如 RFC 2104 和 RFC 4426 标准所述。其中 RFC 2104 是“HMAC:键入-散列法用于信息身份验证”,网址是
go.microsoft.com/fwlink/?LinkID=112151;而 RFC 4226 是“HOTP:基于 HMAC 的一次性密码算法”,网址是
go.microsoft.com/fwlink/?LinkID=112153)。
因为是测试部署,我将使用简单的客户端应用程序来创建 OTP。如前所述,在实际情况中,您可能希望将其与防篡改硬件设备结合使用。我将大致介绍使用这种方法所需的基础知识,并提供一些入门资源。
完整的 OTP 解决方案
为构建 OTP 解决方案,我需要创建一个基于标准的 OTP 身份验证 Web 服务,该服务由 SQL Server
® 提供支持且集成到 ASP.NET 中。我将创建一个 OTP 生成器,通过将其安装到每台客户端计算机上,用户就可运行它来生成新的 OTP。
当 收到 Web 浏览器提示时,用户需要键入 OTP 值并单击“Submit”(提交)按钮进行身份验证。OTP 插件模块从 IIS 得到通知,并且随后调用 Web 服务来检验身份验证请求。Web 服务在 SQL Server 表中查找用户的密钥和计数器值、检验 OTP 计算,并响应身份验证为成功还是失败。
图 1 显示了示例解决方案的体系结构。请记住:在生产环境中,应通过在客户端和服务器之间配置信任关系以及限制无效的登录尝试等方法,进一步强化此体系结构来杜绝拒绝服务 (DoS) 攻击。
图 1
一次性密码解决方案组件
本文附带的示例代码(位于《MSDN
® 杂志》网站的下载区)包含一个 Visual Studio
® 2005 解决方案,其中有用于生成 OTP 的一个 C++ DLL (HmacOtpDll)。由于该 DLL 供 OtpClient 和 Web 服务使用,我将其放在 system32 文件夹中。(我使用生成后事件来将其自动复制到该位置。)此示例还包含一个名为 OtpClient 的控制台应用程序,可用它来生成 OTP 值。OtpClient 使用 XML 文件来存储加密代码和计数器。每次重建应用程序时,它都会将 XML 从项目根目录复制到目标目录,从而将计数器重置为 0。
名 为 IIS7Module 的 IIS 模块提供了 OTP 身份验证服务,并且该模块使用一个名为 WebService 的 Web 服务来检验 OTP 值。该 Web 服务包含一个位于 App_Data 中的 SQL Server Express 数据库。最后,我在 TestWebsite 项目中包括了用于测试该解决方案的网页。
测试 OTP 生成器客户端
测 试 OTP 生成器客户端应用程序是一个独立的工具,它允许用户获取 OTP 身份验证值。它免除了在实际部署中通常必需的硬件设备和挑战(如 PIN 请求)。为计算 OTP,该客户端组件使用与身份验证 Web 服务共享的 DLL。在本示例应用程序中,用户将运行此工具来创建下一个 OTP,然后将该值手动键入到 Web 浏览器的表格中。我将使用 C# 和一些 C 来完成所有工作。(我选择使用 C 来实现底层 OTP 加密。)
您 已了解 OTP 在用户级别的工作原理,但此解决方案是如何在功能级别上工作的呢?这个基于哈希的 OTP 解决方案有两个输入值:密钥和计数。但是,OTP 解决方案还具有与实现程序相关的元数据,包括执行身份验证时用户必须键入的密钥的长度和预期 OTP 值的长度。
我的示例程序将生成长度为六个字符的 OTP,并且可支持最多八个字符。为简单起见,该实现使用将密钥长度限制为 64 个字节的一些固定长度缓冲区。但是,假设密钥是密码学角度上的高质量随机数字,则会需要巨大的密钥空间。此类密钥将不会成为生产部署中的脆弱环节。(目前 随机密钥通常为 256 位,即 32 字节。)
特定用户(或从技术角度讲,具有特定密钥)每次进行身份验证尝试时,计数值都会增加。OTP 解决方案的安全性依赖于永不重复使用的计数值;这一点由 OTP 服务器保证。实际使用时,计数是 64 位无符号整数。如前所述,另一种部署方法是使用与服务器之间的时间同步。
密钥哈希消息身份验证代码 (HMAC) 是基于密钥的一种加密哈希。或换句话说,HMAC 接受任意消息和密钥,并将消息映射成固定长度的摘要值(如 20 字节),从而确保只有具有相同密钥的人才能从相同的消息生成相同的摘要值。
HMAC-OTP 的首个计算步骤是接受计数值,并将其编码为 HMAC 计算的输入消息。实际使用时,消息是设为计数器值的 8 字节缓冲区。
图 2 描述了这一步骤以及接下来的两个步骤。下一计算步骤是使用用户密钥计算上述消息的 HMAC。请注意,我在该实现中对字节顺序进行了编址以确保它与 RFC 兼容。
图 2
一次性密码流程 (单击该图像获得较大视图)
随 后,通过对 20 字节的 HMAC 结果进行十进制编码,从而将此结果转换为 OTP 值。其中包含两个实践要求:第一,我需要保留尽可能多的 HMAC 计算位,最多为 OTP 结果的长度(在本例中为六位数),因为丢失位会把计算暴露给加密攻击。第二,我需要创建与尽可能多的输入设备类型兼容的 OTP。这一兼容要求正是我采用十进制编码的原因所在。(这种强身份验证实现甚至与拨号式电话兼容!)
示例网站
我 的目标是确保所设计的站点能正确地显示用户是否使用 OTP 实现成功登录。为便于测试,此 OTP 解决方案包含一个示例网站。该网站的首页是 Default.htm,此登录页面用于向通过身份验证的用户展示 OTP 模块如何保护非 ASP.NET 页面。其中还包含用于显示当前已通过身份验证的用户名的 Test.aspx 文件,以及显示 Test.aspx 页面如何从 System.Web.UI.Page.User 属性检索已通过身份验证的用户名的 Test.aspx.cs 文件。该解决方案还包括一个 web.config 文件(其中包含对 OtpModule 的引用)以及引用了 IISModule.dll 文件的 Visual Studio 项目解决方案。
IIS HTTP OTP 插件模块是该网站的一个组件。该模块与 IIS 交互,并将用户重定向到可在其中输入用户名和 OTP 的 Web 窗体。当用户提交用户名和 OTP 时,该模块会检验输入并将用户重定向到相应的成功或失败页面。该模块还会将用户的身份验证状态与用户会话关联起来。
为实现可维护性和可支持性,我希望能够对该模块进行管理,因此我使用 C# 来编写该模块。插件模块是身份验证 Web 服务(稍后我将进行介绍)的客户端。
OTP 模块实现了 IHttpModule 接口。实际上,该模块非常简单,仅包括三个公共方法。第一个是 Init,如下所示:
public void Init(HttpApplication application)
{
application.BeginRequest += new EventHandler(application_BeginRequest);
}
如您所 见,该模块使用此方法来注册 BeginRequest 处理程序 application_BeginRequest。OTP BeginRequest 处理程序的目的是确保所有 HTTP 请求均来自通过身份验证的用户。具体实现方式为使用以下几个帮助函数:一个函数用于确定调用方是否已通过身份验证,还有几个函数用于在调用方尚未通过身份 验证时对其执行身份验证。还有一点值得一提:在 BeginRequest 事件过程中截取请求的方式不会遵循 ASP.NET 应用程序所使用的标准身份验证模式。但是,如果不希望其他模块看到此请求(甚至是那些设计为在进行身份验证之前截取请求的模块),则建议使用此方法。
IsAuthenticated 帮助函数将确定请求是否指明需要一个已通过身份验证的用户。实现方法是:使用 System.Web 命名空间中的 HttpContext、HttpCookie 和 Security.FormsAuthenticationTicket 类来检查应用程序上下文中是否包含正确加密的身份验证 cookie。如果存在 cookie 并且能够正确解密,则认为调用方已通过身份验证。否则,存在以下两种可能的状态:该请求是应处理的 OTP 身份验证请求,或者该 Web 客户端请求无效(未通过身份验证),这时会显示登录窗体。
OTP 模块包括一个名为 LoginPage.htm 的内置登录窗体。它由以下五个 HTML 元素组成:一个初始为空的错误消息字段,一个用户名字段、一个密码字段、一个提交按钮以及一个名为 hdLoginForm 的隐藏输入字段。
如果调用方已通过身份验证,则模块无需执行进一步操作。继续处理请求。在本演示中,将加载 Default.htm 页面。
如果调用方未通过身份验证,则会调用 IsAuthenticationPost 帮助函数。它将检查请求类型是否为 POST,以及请求窗体是否包含 hdLoginForm 输入字段。如果两个条件都得到确认,则此方法将返回 true。
如 果请求是身份验证请求,则调用 TryAuthenticate 帮助函数。从请求上下文中检索用户名和 OTP 值,并将其传递给身份验证 Web 服务的 VerifyOtpCode 方法。如果确认成功,则会将新的加密身份验证 cookie 附加到响应中。然后,将该响应重定向到默认页面 (Default.htm)。要演示这一效果,可先保存用户请求的原始页面,并在身份验证成功之后将用户重定向到该页面。
如 果 VerifyOtpCode Web 服务调用失败,请求将被重定向回登录窗体(此时将显示一个错误消息),并且调用帮助函数 ShowLoginForm。此帮助函数从模块的资源部分加载登录页面、设置页面中的错误消息字符串(如果适用),并将登录页面设为对当前请求的响应。然 后,它将请求标记为已完成(无论请求的性质如何都会完成这一步)。
身份验证 Web 服务
身份验证 Web 服务负责执行实际的 OTP 身份验证,其方法是确定提供的 OTP 值是否表明命名用户已拥有密钥,。
由 于重用底层加密库(此加密库实现了在之前的有关测试 OTP 生成器客户端的章节中所介绍的 OTP 计算),实现 OTP 身份验证 Web 服务将非常简单。该重用形式为对本机 HmacOtpDll.dll 的 GenerateOTP 导出的 P/Invoke 调用。
Web 服务提供一个 Web 方法 VerifyOtpCode(在身份验证成功时它将返回 true)。该方法首先加载与身份验证请求中指定的用户名相对应的 SQL Server 数据库行。如果 SQL Server 无法找到匹配行,该方法将返回 false。
如果在 SQL Server 数据库中找到相应用户名,该方法将把以下数据项传给本机 GenerateOTP:请求中指定的 OTP 值、用户密钥(从 SQL Server 中检索得到)以及计数器值(也从 SQL Server 中检索得到)。
GenerateOTP 将使用连续的计数器值进行反复尝试,直到返回匹配的 OTP 值或者已检查过 1,000 个连续的计数器值。尽管不太可能从上次成功身份验证后重复尝试一千次,但它可能使用户意外将客户端计数器运行至脱机!
通过缩小此范围,还可使攻击者实际猜测的 OTP 值与序列中的值恰好相等的机率更低,但它会增加用户意外将客户端计数器运行至超出服务器允许范围的可能性。在后一种情况下,需要请求管理员介入才能使用户能够再次进行身份验证。
如 果在计数器范围内找到匹配的 OTP 值,则将把新的计数器值写回数据库。然而遗憾的是,这需要身份验证 Web 服务具有数据库的写入权限,并且如前所述,由于必须确保计数器值不发生重用,因此它对于 OTP 的安全性而言也至关重要。(为使一次性密码唯一,必须保证它实际仅被使用一次。)
具 有简单架构的 SQL Server 数据库可用于存储用户名及对应的 OTP 密钥或种子值。还可扩展该构架以包含登录信息(如使用 OTP 成功登录和不成功登录的次数和时间)。该数据库由 Username、SecretCode 和 Counter 列组成。有关每个元素的解释,请参阅“身份验证 Web 服务”一节。示例代码附带的数据库副本仅包含一个存储用户名“testuser”的行。
完整的体系结构
图 3 显示了解决方案完成时的体系结构。如图所示,用户启动客户端应用程序、生成 OTP、然后导航至身份验证 Web 应用程序,并将 OTP 粘贴到 Web 浏览器窗体中。当 OTP 模块检测到请求尚未通过身份验证时,它将生成窗体。当用户单击“Submit”(提交)时,Web 浏览器窗体将把请求发送到服务器(在其中它将被 OTP 模块再次截取)。接下来,OTP 模块调用 OTP Web 服务来检验用户的身份验证数据。最后,如果成功,Web 服务器将针对请求页面(此页面可为任意类型:HTML、ASP.NET、PHP 等)调用处理程序。
图 3
详细的 OTP 解决方案体系结构 (单击该图像获得较大视图)
运行代码示例
要运行代码示例,需要具有 IIS 7.0 的 Windows Vista
® 或 Windows Server
® 2008 (并且针对 Windows Server 2008 启用“应用程序服务器”角色)、Visual Studio 2005、SQL Server 2005 或 SQL Server Express 以及 OTP 示例代码。要实验演示,建议使用 Windows Server 2008 和 SQL Server Express(因为我就使用的它们)。在下面的说明中,假设 OTP 示例代码解决方案文件位于 C:/Test/OTP/Otp.sln。
要准备环 境,使用“IIS 管理器”或通过编辑 web.config 文件来安装 IIS 模块。向 IIS_IUSRS 帐户或配置为自定义应用程序池标识的帐户授予对 C:/Test/OTP/webservice/app_data 的读写权限。然后,为 OtpTest 和 OtpService 添加网站。
使用 web.config 文件注册 IIS 模块(此步骤已在示例中完成 — 请参阅 TestWebsite/Web.Config)需要以下配置标记:
<system.webServer>
modules>
<add name="OtpModule" type="OtpModule" />
</modules>
</system.webServer>
还应将模块 DLL 添加到 bin 文件夹或全局程序集缓存 (GAC) 中。要向 GAC 注册 OTP 模块,请使用如下所示命令:
gacutil.exe /i iis7module.dll
要使用 “IIS 管理器”,请打开此管理器并在控制台树中单击计算机名称。在中间窗格中,双击“Modules”(模块)图标,然后单击操作窗格中的“Add Managed Module”(添加托管模块)。接下来,从下拉列表框中选择 OtpModule。如果将 DLL 放入 GAC 中并使用“IIS 管理器”来添加模块,则可能需要重启 IIS 才能刷新模块列表。
为正确增加 SQL Server Express 中的尝试次数值,NETWORK SERVICE 帐户需要具有对 C:/Test/Otp/WebService/App_Data 库的读写权限。此对象所需权限是读取和执行、列出文件夹内容、读取以及写入(如
图 4 所示)。
图 4
所需的对 App_Data 目录的权限
还 必须在 IIS 中为测试网站设置一个网站。为此,打开“IIS 管理器”控制台,并在其控制台树中,展开具有您的计算机名称的节点,右键单击“Sites”(网站)并单击“Add Web Site”(添加网站)。将以下设置用于新网站,然后单击“OK”(确定)(图 5 显示了这些设置):
图 5
OtpTest 网站设置 (单击该图像获得较大视图)
- “Site name”(网站名称):OtpTest
- “Physical path”(物理路径):C:/test/Otp/TestWebsite
- “Port”(端口):8000
还必须为 Web 服务创建一个网站。将以下设置用于该网站:网站名称为 OtpService,物理路径为 C:/Test/Otp/WebService,并且端口为 8080。
接 下来,在 Visual Studio 中打开 Otp.sln 解决方案,在解决方案资源管理器中展开 IIS7Module,展开“Web References”(Web 引用)、右键单击 OtpService,单击“Properties”(属性)并确认“Web Reference URL”(Web 引用 URL)已设为 http://localhost:8080/service.asmx。在“Build”(生成)菜单中,单击“Build Solution”(生成解决方案)并确认没有发生生成错误。
现在,应执行测试来确保已适当注册并正确加载 OTP 模块。为此,导航到 http://localhost:8000 并确认出现
图 6 所示的登录页面。要测试实现,键入测试用户名 testuser。
图 6
测试网站的首页
要获得 OTP 代码文本框的值,以管理员身份打开命令提示符窗口并导航到 OTP 客户端程序的生成目录 (C:/Test/Otp/otpclient/bin/debug)。运行 OtpClient.exe 获得下一 OTP 值。结果应如
图 7 所示。
图 7
运行 OtpClient.exe 以 获得 OTP 值 (单击该图像获得较大视图)
将 OTP 值键入 OTP 代码文本框,然后单击“Submit”(提交)。
图 8 在页面中显示了示例 OTP 代码。如果身份验证成功,您会转到 Default.htm 页面。
图 8
具有示例 OTP 值的测试页面
单击默认页面上的 Text.aspx 链接进入演示页面,该页面将显示当前通过身份验证的用户(如果有)的名称。如果身份验证凭据不正确,则会出现
图 9 所示的页面。
图 9
身份验证 不成功
部署注意事项
如 果要在实际环境中部署此示例解决方案,有许多需要考虑的事项。实际部署将用户密钥/种子存储在防篡改的设备中(如硬件令牌)。建议将解决方案修改为在多次 连续失败的身份验证尝试后锁定帐户。可通过将多个 SQL Server 列和一个功能添加到 Web 服务来实现此功能。
如 果互操作性是一个考虑因素,建议针对底层 HMAC 代码执行互操作性测试。还应意识到 MD5 哈希算法已不再安全。我在此示例解决方案中使用它的目的是使用 RFC 文档中提供的算法来运行一些基本的“已知矢量测试”。但是,可部署解决方案应使用其中一种 SHA-2 算法来创建哈希。
在开发更加用户友好的身份验证页面时,切勿区分无效用户名和无效密码。否则会让攻击者获得有效的用户名。还需要一些设置解决方案来添加和删除用户或者将身份验证数据库与另一存储库(如 Active Directory
®)同步。
如 果目标只是 ASP.NET,可将模块实现为标准的 ASP.NET HTTP 模块。但是,此类解决方案仅是安全的 .aspx 文件。请注意,无论是使用 ASP.NETI 运行时还是 IIS 7.0 来配置 IHttpModule 接口,这个接口都是相同的。因此如果初始解决方案是特定于 ASP.NET 的模块,然后通过更改配置就可使其支持所有的文件类型。
我 使用 Visual Studio 来编辑数据库(我以前并未注意到这一很酷的 IDE 功能)。要编辑数据库,转到“Solution Explorer”(解决方案资源管理器)树视图,展开 WebService、展开 App_Data 并从上下文菜单中选择“Open”(打开)选项。在出现的 Server Explorer(服务器资源管理器)面板中,展开“Data Connections”(数据连接),展开 otp.mdf,展开“Tables”(表格),右键单击“Users”(用户)、然后单击“Show Table Data”(显示表数据)。
图 10 显示了 Visual Studio IDE 中的表数据。测试时切勿在 IDE 中打开数据库;否则 Web 服务将无法打开它,并且因此导致身份验证失败。
图 10
一次性密码解决方案组件
尝试一下
一 次性密码是优先于标准密码的选择。由于用户对于密码的使用非常随意(如将密码写下来并成为网络钓鱼诈骗攻击的牺牲品),因此您必须强化身份验证过程。通过 使用 IIS 7.0 插件模块模型,可利用硬件令牌和质询创建基于标准的 OTP 解决方案以及更加可靠且可立即投入生产运行的 OTP 解决方案。
应按照此处的示例亲自尝试一下。由于许多雇主以及面向消费者的界面(如在线银行)都采用 OTP,因此您应亲自熟悉一下此技术。
如需更多相关资料,请参阅 Mike Volodarsky 在 2008 年 1 月撰写的
《MSDN 杂志》文章“IIS 7.0:使用集成的 ASP.NET 管道增强应用程序”,网址为 msdn.microsoft.com/msdnmag/issues/08/01/PHPandIIS7。它将提供一些有用的背景信息。
Dan Griffin 是华盛顿州西雅图市的一位软件安全顾问。他曾经在 Microsoft 的 Windows 安全开发团队工作过七年。您可以通过 www.jwsecure.com 与 Dan 联系。