HTML5 离线认证
Posted
技术标签:
【中文标题】HTML5 离线认证【英文标题】:HTML5 offline authentication 【发布时间】:2014-06-08 23:59:52 【问题描述】:我正在寻求有关如何最好地控制对主要离线使用的 html5 应用程序的访问的建议/批评。
应用程序结合使用 IndexedDB、本地和会话存储来存储数据,以便可以离线使用。数据/页面通过 HTTPS 提供。
目的是最大程度地降低平板电脑/PC 丢失/被盗时查看数据的风险。
目前,该应用程序使用 Stanford javascript Crypto 库对用户/密码进行加密,然后如果用户能够成功通过服务器验证,则将其保存到本地存储中。如果应用程序随后脱机,则用户必须针对本地存储中的加密用户/密码进行“本地”身份验证。
此外,如果用户能够成功通过服务器身份验证,则未加密的用户/密码将存储在会话存储中。这样做是为了让应用程序可以定期尝试重新建立与服务器的联系并“看似”地重新进行身份验证,而无需用户重新输入他们的凭据。
我知道一些关于客户端加密错误的帖子/讨论请参阅 http://www.matasano.com/articles/javascript-cryptography/ 和 http://rdist.root.org/2010/11/29/final-post-on-javascript-crypto/ 和 .nczonline.net/blog/2010/04/13/towards-more-secure-client-侧面数据存储/ +其他。但是我不确定这些论点在这种情况下如何应用。
鉴于需要离线存储数据,我正在寻找对这种方法的批评。如果有更好的方法,请详细说明。
谢谢
【问题讨论】:
用户名+密码是否作为本地数据的加密密钥?如果不是,我会说这是最安全的做事方式(确保凭据没有保存在任何地方),因为没有密钥就很难解密其他数据,即使攻击者可以访问JavaScript。 @SilverlightFox,感谢您的快速回复。抱歉,我确实忘记说应用程序确实使用密码作为加密密钥。但是,为了避免在每次更改密码时重新加密数据,我正在考虑使用服务器生成的不变的第二个密钥来加密数据。第二个密钥又可以由“密码密钥”加密,并在用户会话期间不加密。 【参考方案1】:身份验证与安全存储
我将从一个重大的设计问题开始:您似乎在处理该问题时好像是关于身份验证,其中(可能是恶意的)用户需要向您的应用程序证明她确实是有效用户。但实际上您正面临存储问题,因为如果计算设备被盗,整个运行时环境(包含您的应用程序正在使用的所有敏感信息)都在攻击者手中。在 javascript 应用程序的情况下,离线数据和代码的分析比在某些仅二进制代码的情况下更加舒适。
例如,如果我想攻击您的应用程序,我会首先查看会话存储(cookie?只需使用浏览器界面查找它们),看看是否可以在那里找到用户名和密码。如果不是,我将遵循用于解密本地存储中的密码的代码(可能使用 javascript 调试器)。您描述应用程序的方式似乎函数可以在没有用户提供的密钥的情况下对其进行解密。也许我可以通过将if(authenticateUser())
更改为if(true)
之类的内容来注释掉用户的本地身份验证。
因此,您真正需要做的是使用根本不存储在客户端的密钥加密所有敏感的本地数据。例如,每次访问您的应用程序时都要求用户提供解密密钥,使用该密钥解密本地存储的数据(并加密您存储的每个新数据)并在一段时间不活动后将密钥扔掉强>。或者,您可以在用户每次访问您的应用程序时对服务器进行身份验证,并从那里检索解密密钥,并在一段时间不活动后将其丢弃。
此时,javascript 环境的选择确实会妨碍您的事业,因为您无法强制运行时环境在您希望解密密钥消失时丢弃它。即使使用 C 应用程序也很难,因为您必须小心地将 RAM 换到 HDD 上。根据您的应用程序使用的信息的敏感程度,可能足以要求用户在完成后关闭浏览器并假设攻击者没有足够的动机在浏览器换出的 RAM 中寻找密钥。
本地保存登录数据
由于它是您使用的最敏感信息,因此您永远不应将用户登录信息存储在客户端上。而是对服务器进行一次身份验证,并从中检索身份验证令牌以供将来交互。这基本上与会话 cookie 相同,并在一段时间后过期(如果它根本没有过期,它与密码一样好)。
【讨论】:
您在面对存储问题时提出了很好的观点。对不起,我忘了说用户的密码被用作密钥。鉴于此,我假设访问 javascript 最多只能让用户能够查看加密数据。 Javascript 源不应是攻击媒介(Kerkhoffs 原则)。问题是在合法使用(如上文详述)和(如上文未详述)从某处获得安全密钥之后剩余的数据。一般来说,密码作为加密密钥的来源是相当糟糕的(bzfvgi9o2ui
包含的熵仅是普通 AES 密钥的一半,并且大约 60 位仍然在蛮力的范围内)。您可以使用良好的密钥派生函数(在本例中为 PBKDF)通过密钥加强或拉伸来缓解该问题,但我不知道在 JS 中这有多快。
我不确定您所说的“Javascript 源......成为攻击媒介”与建议的解决方案相关的意思。我假设 Javascript 是公开的,因此容易受到攻击,因此密钥没有存储在 Javascript 中,所以我认为 Kerkhoffs 的原则是满足的。我想我理解您关于令牌的观点,但我看不出如何在满足强制用户在离线时进行身份验证的要求的同时实现令牌解决方案。当离线应用程序只有一个令牌可以使用时,系统如何在离线时验证输入密码的用户?【参考方案2】:
我现在已经实现了一个解决方案,我将在下面描述它以防它对其他人有用。我确实明白这不是我的问题的“答案”,即不提供批评,但鉴于应用程序必须“离线”工作以及无缝重新验证的要求,我看不出如何实现@Perseids 答案,尽管我很欣赏对话(来自@SilverlightFox 和@Perseids)。
如果有一种解决方案可以在满足我的问题中概述的要求的同时不必“离线”存储用户的凭据,我很想听听。
应用程序必须能够在应用程序“在线”和“离线”时对用户进行身份验证。对于“在线”应用程序,通常会采用会话令牌解决方案,即仅将会话标识符存储在客户端(通常在 cookie 中)而不是用户的凭据。然而,用户的凭据必须存储在客户端上(也许有人会想出一个聪明的替代方案?)以便在应用程序离线时可以强制执行安全性,即允许用户在离线时进行身份验证并解密/加密 IndexedDB 数据。为了使应用程序更加安全,用户的用户名和密码以加密形式存储。一些敏感的 IndexedDB 数据也以加密形式存储。因此,即使恶意用户要获取具有应用程序缓存实例的计算机或平板电脑,他们也只能以加密形式查看用户名、密码和数据(前提是用户已注销或关闭浏览器)。
不幸的是,目前似乎没有任何“标准”协议来保护 HTML5 离线应用程序。几乎所有文献都警告不要在客户端存储用户凭据或任何敏感数据。然而这是一个悖论,因为此应用程序必须在离线时工作,因此数据必须离线存储。
这里实现的安全协议有两个密钥,虽然一旦第一个密钥被破解,就很容易获得第二个密钥。在第一级中,用户的密码使用他们自己的密码作为密钥进行加密,同时他们的用户名作为盐被反转。还有第二个密钥,“数据加密密钥”,在成功验证服务器后从服务器返回。此密钥用于加密用户名和任何 IndexedDB 数据。这个“数据加密密钥”又使用用户的密码进行加密。因此,如果攻击者能够解密用户的密码,他们就可以轻松地使用密码来解密“数据加密密钥”,然后使用解密的“数据加密密钥”解密用户的用户名和任何加密的 IndexedDB 数据.只有用户名、密码和数据的加密形式需要永久存储在客户端上,因为通过使用在登录屏幕中输入的用户名和密码,就可以解密任何持久化的数据。
但是,在登录后,用户名和密码以未加密的形式存储在客户端的会话中,以便 1) 应用程序可以定期重新与服务器进行身份验证,如果存在间歇性连接和2) 随时检索解密后的数据加密密钥,以便能够查询/保存IndexedDB数据并在必要时对其进行解密/加密。如果 1) 不是必需的,则只需将数据加密密钥存储在会话中。如果用户没有注销或没有关闭浏览器,这会导致漏洞,因为恶意用户将能够以解密形式查看用户的密码和用户名(使用调试工具)。然而,这并不比发生在让用户能够更改密码的传统在线应用程序中发生的同样事情差多少,尽管通常传统的在线应用程序具有会话超时,因此恶意用户只有有限的时间采取行动。此外,如果浏览器崩溃,通常它会为用户提供使用会话信息恢复之前的窗口/选项卡的选项,因此应该正确关闭浏览器。
上面采用的协议几乎可以肯定没有遵循最佳实践。例如盐不是随机的(用户名颠倒),可能很短,容易受到字典攻击,同样可能适用于密码(密码的强度是服务器的函数),没有密钥拉伸例如PBKDF2。但是,鉴于施加的限制,我看不出如何遵循“最佳实践”并满足要求。可以稍微改进散列,例如改进盐,可能是用户名和特定于站点的字符串的组合,但即使这样也需要 javascript 中的逻辑,这可以被坚定的攻击者理解。 Javascript 可以被混淆,但这也只会让它变得更加困难,但并非不可能,任何能够破解加密密钥的人都不会发现混淆 Javascript 是一个很大的障碍。也许未来有一些来自浏览器的聪明的内在内置支持将有可能实现显着的改进。
【讨论】:
这也是我正在研究的一个问题 - 类似的要求。我想知道是否可以使用安装在手机上的 Google Authenticator 之类的东西 - 只是不确定本地 javascript 应用程序如何存储秘密。以上是关于HTML5 离线认证的主要内容,如果未能解决你的问题,请参考以下文章