无需存储、加密或记忆密码!






4.98/5 (25投票s)
基于加密哈希和主密码生成高度安全的密码,主密码需要记忆;不涉及密码存储。
一只谷仓猫头鹰给纳威带来了一个他祖母的小包裹。他兴奋地打开它,向大家展示了一个像大理石一样大小的玻璃球,里面似乎充满了白烟。
“这是一个冥想球!”他解释道。“奶奶知道我经常忘记事情——它会告诉你是否忘记了要做什么。看,你像这样紧紧地握住它,如果它变成红色——哦……”他的脸色垮了下来,因为冥想球突然变成了鲜红色,“……你忘记了什么……”
纳威正试图回忆起自己忘了什么,这时,德拉科·马尔福经过格兰芬多餐桌旁,从他手里抢走了冥想球。
- J. K. 罗琳,《哈利·波特与魔法石》
纳威·隆巴顿
——唯一的问题是,我记不起我忘了什么。
- 史蒂夫·克洛夫斯,《哈利·波特与魔法石》,根据 J. K. 罗琳的小说改编的电影剧本版本
目录
动机
密码管理器有什么问题?
洞见
用法
基本设置
处理账户、用户名和密码
密码更新
服务如何试图破坏您的安全以及如何规避
测试账户:当心你身后的人
是否使用公共网络存储?
实现
加密系统
高级用法
自定义加密系统
实时演示
测试
下一步?
结论
致谢
动机
让我们为纳威·隆巴顿和所有其他使用密码保护的网络服务的用户提供一个更好的冥想球。
一个广为人知的密码问题是,多个密码保护的服务要求人们创建多个密码,并鼓励他们不要重复使用密码,并使用强密码。这是合理的,但大多数人几乎不可能记住多个密码,这迫使人们记录密码,从而大大损害了安全性。
一个显而易见的解决方案是使用密码管理器,但有不同的因素阻碍人们使用它们。
Storage-Free Pass 解决了许多与密码管理器相关的问题。让我们来讨论一下。
密码管理器有什么问题?
第一次看维基百科上的密码管理器列表,你会发现其中一半是专有软件。什么?它们不仅让我存储密码,还让我使用一些闭源代码进行……谁知道是什么?不,谢谢,我没那么疯狂。
此外,那些开源的通常太复杂,无法确保它们只做我想做的事情。
密码集是太重要的个人资产,不容冒险。
另一个问题是,大多数密码管理器不是跨平台的。基本上,我只能在安装了该工具的电脑上使用。
如果一个产品是开源的,许多人可能很难构建它。而且,要确定代码的安全性并不容易。
我提供的产品没有这些问题。它可以保存在几乎任何设备上,而不会有风险,即使设备丢失或被盗。它也可以保存在一些网站上,即使是公共网站。它有一个清晰可观察的核心文件,其余代码相当容易检查和评估其安全性。
洞见
这个想法是这样的:还记得老式冒险电影里,两个人必须一起行动,同时使用两把不同的钥匙才能打开一个特别重要的保险箱吗?
我们这样做:组合两个字符串,一个主密码和一个“种子”(seed)。种子不必保密。现在,看看下面的数据流图。目前,只有上半部分很重要。
让我们计算组合主密码 + 种子 的加密哈希。
在我们的例子中,哈希函数返回 256 位数据,我们可以使用这些数据根据一个字符集生成密码,该字符集是密码可接受的字符集。根据密码的大小,哈希值中包含的信息量可能相对于最大密码大小是冗余的或不足的,但这不重要。重要的是,我们可以为密码保护的服务施加的限制生成一个最大强度的密码。更重要的是,即使服务密码被盗,也无法通过密码学方式重建主密码。
我们将在实现部分进一步讨论细节。
用法
基本设置
基本用法
<head>
<script> src="../storage-free-pass.api/API.js"</script>
</head>
在 `<body>` 元素中,添加一个脚本。它应该定义一个常量 `userData = () => {/*...*/};` 函数,并返回用户账户的结构。
请参阅“storage-free-pass.api/help.html”了解账户信息结构的描述,“user-demo/index.html”了解 JavaScript 示例。
处理账户、用户名和密码
让我们更详细地考虑使用方法,看看所有相关问题是如何解决的。
密码更新
密码更新问题是人们批评某些密码管理器时提出的问题之一。
为了使密码易于更新,我建议维护一个合理的种子值形式。它可以是一个字符串,包含服务名称,然后是密码创建、初始设置或更新的日期。日期的格式无关紧要,但我总是建议使用“可排序”的格式,如 YYYY/MM/DD。也就是说,格式不依赖于当前文化,但其字母顺序与时间顺序相同,最低有效数字位于字符串的末尾。这样,您可以轻松确保种子值不会与过去的值相同。这不是规则,只是我的建议。我不认为任何人需要一天的时间来获得更高的准确性,因为密码不太可能一天更新一次以上,但如果出于某种原因发生这种情况,也可以使用。
这样,日期更改将是更新密码所需的唯一操作。在大多数情况下,需要记录旧密码,因为它需要在密码保护服务提供的密码更新过程中提交。
不幸的是,偶尔会出现不够的情况。由于一些密码保护服务施加了一些奇怪的“好密码”规则,这些规则实际上会损害我们密码的质量,从而可能出现问题。所有这些问题都可以解决,因为 Storage-Free Pass 为所有这些问题提供了解决方案。让我们考虑这些问题。
服务如何试图破坏您的安全以及如何规避
密码保护的服务试图通过添加一些限制来推广“强”密码。自然,任何限制都无法提高密码强度。同时,我理解,这些模式的作者将限制视为一种防愚蠢的功能,用于对抗客户端创建的可能愚蠢的密码。
我并不认为这有多大帮助,但它阻止了接受算法生成的、显然是最强的密码。
其中一些限制非常愚蠢,并造成了一些麻烦。在所有情况下,Storage-Free Pass 都提供了规避所有限制的方法。让我们来考虑一下。
-
字符集。我不会以任何方式限制字符集。事实上,我确实使用至少一个服务,它接受非常广泛的字符集。人们可以轻松地在同一个密码中混合使用任何标点符号、拉丁字符、亚美尼亚字符、格鲁吉亚字符、希伯来字符、梵文(Devanāgarī)、西里尔字符、波斯字符、数学符号等等。为什么不行?如果一个客户端创建了这样一个密码,那么这个人有理由期望他们可以在所有情况下再次输入它。
不幸的是,这种情况很少见。一个服务可能会强制执行最愚蠢的字符集限制。这就是为什么 Storage-Free Pass 账户信息的创建者可以存储几个单独的字符集字符串,以应用于不同的服务。请参阅“storage-free-pass.api/help.html”了解账户信息结构的描述,“user-demo/index.html”了解 JavaScript 示例。 -
模式。一些密码规则要求密码不包含某些“模式”,例如连续或重复的字符序列。为什么?!模式有什么问题?为什么“123”或“111”是“模式”,而“qwerty”不是?没有答案——一些“安全专家”就是白痴,仅此而已。
我甚至需要解释这种限制只会降低密码的强度吗?嗯,显而易见,通过完全随机的密码生成过程,似乎产生了最强的密码,这种短模式出现的概率足够高了。某些随机模式的存在并不会使密码猜测技术更有效。无论如何,服务器端的密码猜测技术是不同的,而且众所周知。
不幸的是,无法在所有情况下自动阻止所有不同的“模式”。通过一些试错法可以找到更好的规避方法。我在种子字符串后面加上一个任意的后缀。这个后缀可以是当天的时间,或者任何其他东西。很有可能,下一次某一天更新密码时,就不需要它了。 -
“特殊字符”。没有人定义过“特殊字符”,不存在这样的东西。然而,许多密码要求中包含“至少包含一个小写字母、一个大写字母、一个数字和一个特殊字符”这样的项目。有时他们会定义特殊字符的含义,有时则不会,那么可以进行实验确定。
为了规避这个问题,Storage-Free Pass 提供了带有任意插入点的“插入”。可以添加一个包含所有必需字符的永久子字符串,或多个这样的子字符串。请注意,插入项的长度不计入密码长度,因此它们的长度会添加到最终密码的长度中。 -
密码长度。我不会限制密码长度。为什么?服务使用关系数据库,并且不应存储任何密码,而加密合理系统实际上存储密码的加密哈希值,因此存储数据的大小是固定的。然而,密码长度通常是有限制的,有时是令人沮丧的低字符数。看起来他们关心客户记住密码的能力,但这种假设根本不现实。
对此您无能为力,但如果密码限制在很短的长度,例如 8 个字符,那么这就是不信任该服务的一个好理由。如果该服务是一家金融机构,那么它可能是一个拒绝其服务并寻找其他东西的好理由,不是因为密码长度,而是因为对该组织安全水平的总体担忧。
请参阅“storage-free-pass.api/help.html”了解账户信息结构的描述,“user-demo/index.html”了解 JavaScript 示例。
测试账户:当心你身后的人
即使有人可以看到您显示器上发生的一切,您也能保守密码的秘密。
我个人从未遇到过这个问题,但这是我做的:我的第一个账户是测试账户,我专门用它来验证我的主密码的正确性。
主密码、账户用户名和生成的账户密码,在表示选定账户的表中可视化时,有两种模式:隐藏和显示,通过显示人类眼睛(可见)或太阳镜(隐藏)的三个按钮进行切换。
当我选择测试账户时,我输入主密码,并显示其生成的账户密码。测试账户的种子字符串选择方式使得生成的密码易于识别。我不必记住生成的密码,因为我永远不需要输入它。只要感觉到它有些熟悉就足够了。主密码中的任何错误都会导致字符串截然不同。
当我确信主密码是正确的时候,我点击按钮隐藏我的账户密码,并执行我需要的所有身份验证操作。即使有人在监视我的显示器,也没有机会窃听我的通信以获取任何关键信息。
是否使用公共网络存储?
这没什么不对。您的 Storage-Free Pass 实例的网页本身可以进行密码保护(但那时您需要记住另一个密码),这是一种相对较弱的保护。那又怎样?如果有人窃取了您的文件,他只会获得您的账户信息和用户名。但是,如果您认为这些信息过于敏感,请不要这样做,但您无需担心您的密码。无论是您的主密码还是您的账户密码,都无法获得。
实现
请参阅数据流
只需展示一个定义了加密系统的密钥文件。它可以是自定义的。它根据 `generatePassword` 函数的参数实现了数据流。它可以是具有与 `passwordGenerator` 返回的相同配置文件的任何其他函数。
在此实现中,`password` 和 `seed` 的组合是简单的连接。它用于获取表示 `arrayOfBytes` 数组的加密缓存值。数组的值用于从 `characterRepertoire` 中获取密码。字符在 `characterRepertoire` 中的索引是循环的,并且循环取决于 `length`、`start` 和 `shift` 参数。
在此基础上,可以向生成的密码中插入一个固定字符串数组。此数组是可选的,作为 `inserts` 参数传递。这是为了满足某些密码保护服务施加的密码要求。请注意,这会使密码长度大于参数 `length`。换句话说,`lengths` 指定的不是最终密码长度,而是插入前的长度。此功能的解释已在上方给出。
加密系统
所有加密都基于 JavaScript 引擎自带的标准 API `crypto.subtle`,它实现了Web Crypto API 的低级加密功能。
"use strict";
const passwordGenerator = (() => {
const hashBits = 256;
const cryptographicHashAlgorithmPrefix = "SHA-";
async function digestSHA2(message) {
// encode as (utf-8) Uint8Array
const msgUint8 = new TextEncoder().encode(message);
const hashBuffer =
// hash the message:
await crypto.subtle.digest(
`${cryptographicHashAlgorithmPrefix}${hashBits}`, msgUint8);
// convert buffer to byte array:
return Array.from(new Uint8Array(hashBuffer));
} //digestSHA2
async function generatePassword(
masterPassword, seed,
start, length, characterRepertoire,
shift, inserts)
{
if (!masterPassword) return String.empty;
const arrayOfBytes = await digestSHA2(masterPassword + seed);
const maxLength = arrayOfBytes.length;
if (!start) start = 0;
start = start % maxLength;
if (!length) length = maxLength;
if (length > maxLength) length = maxLength;
if (!shift) shift = 0;
shift = shift % characterRepertoire.length;
let output = String.empty;
for (let index = start; index < start + length; ++index)
output += characterRepertoire.charAt(
(shift + arrayOfBytes[index % maxLength])
% characterRepertoire.length);
if (!inserts) return output;
if (!(inserts instanceof Array)) inserts = [inserts];
for (let insert of inserts)
output = insert.position ?
output.slice(0, insert.position) + insert.value
+ output.slice(insert.position)
:
insert.value + output;
return output;
}; //generatePassword
return generatePassword;
})();
请注意,`generatePassword` 函数是异步的。因此,密码生成的实际结果通过函数返回的*promise*的解析来暴露。为此,在主应用程序文件“storage-free-pass.api/ui.js”中,该函数被包装在另一个 `generatePassword` 函数中。
const generatePassword = () => {
for (let accountIndex in inputData.accounts)
generatedData[accountIndex] = undefined;
const index = accountIndexMap[elements.accountSelector.selectedIndex];
passwordGenerator(
elements.masterPassword.value,
inputData.accounts[index].identity.seed,
inputData.accounts[index].identity.selection.start,
inputData.accounts[index].identity.selection.length,
inputData.accounts[index].identity.selection.characterRepertoire,
inputData.accounts[index].identity.selection.shift,
inputData.accounts[index].identity.selection.inserts)
.then(autoGeneratedPassword => {
generatedData[index] = autoGeneratedPassword;
showPassword();
});
};
代码中的其他一切都相当简单。
高级用法
实际上,现场演示代码样本已经展示了该产品的先进语法。即使它没有启用任何高级功能,也可以作为高级语法的模板。
自定义加密系统
<head> <script data-crypto="../storage-free-pass.api/crypto.js" src="../storage-free-pass.api/API.js"></script> </head>
当然,它不应该是提供的文件“../storage-free-pass.api/crypto.js”,而是其他文件。文件的内容应该提供相同的接口:它应该返回一个函数,一个密码生成器,并且该函数应该返回一个带有 7 个参数的异步函数,返回密码,就像默认的加密系统代码所示的那样。
我还强烈建议数据流至少通过一次标准实现的加密哈希函数。您可以组合不同的哈希函数,并使用不同的字符选择算法来生成密码,但只有强大的加密哈希函数才能使算法在加密上强大。
自定义加密系统是否具有实际用途?我认为,既有又没有。
从理论上讲,它可以提高您的保护强度,但是如何提高呢?让我们考虑一种极端的假设情况,即某人拥有几乎无限的计算能力,并且知道您的账户信息。此外,这个人还需要您的其中一个账户密码,因为否则,就不可能检查计算出的字符串是否是实际密码,而且 Web 服务通常会防止重复的身份验证尝试。我们还需要假设这个人无法访问您的自定义加密系统。
然后,假设恶意攻击者成功找到了一个主密码,该主密码产生了一个已知的账户密码。但是,这是使用默认加密系统找到的。使用您的自定义加密系统,该重构的主密码将无法用于其他账户,因此所有工作都将白费。
这是否是更好的保护?首先,保护仅针对不现实的情况进行了改进,因为主密码的恢复无论如何都是加密不可行的。此外,这是典型的通过模糊实现的安全性的例子。结论?请自行判断。
实时演示
这是现场演示。
测试
Storage-Free Pass 应用程序已在实际的金融机构、购物系统、通信、供应商和其他服务上进行了三年的广泛实践。特别是,这项实践包括频繁的密码更新。
下一步?
-
账户结构的创建仍然是手动编程。即使没有编程经验,也可以通过产品提供的示例包来完成。然而,创建另一个工具来以图形方式编程账户并生成账户代码并不是一个大问题。制作一个基于 Web 浏览器的工具也不是问题。用户可以打开一个包含账户信息的 `script` 元素的 HTML 文件,以图形方式添加或编辑账户,然后保存新的 HTML 文件。除其他外,这将简化密码更新。
-
也许我会添加使用加密密码的可能性。我批评了它们,但有些情况它们很有用。其中一种情况是在剪贴板不可用时登录设备。在这种情况下,用户可以提醒在另一台设备上存储的密码。它可以是独立的应用程序,也可以是现有应用程序中的一种模式。
结论
思维惯性是一个坏习惯。
使用存储密码的想法似乎很自然,但实际上,它源于记住心智生成密码的实践。同样,密码应该是易于阅读和易于键入的想法也是如此。不,它不必如此。
本文提出的并在 Storage-Free Pass 中实现的替代方案非常有效且安全。密码可以在需要时即时生成。
只有在极少数情况下,我们才需要心智生成密码并记住或存储它。
致谢
目前的工作受到 Simon Sheppard 的这项工作的启发。