尽管「取消密码」的风越刮越猛,但距离我们真正摆脱密码大概还有些时日。换句话说,我们还有大把机会「享受」盯着键盘反复编造密码的体验。
对此,不少密码管理工具都体谅用户,提供了自动生成密码的功能。1Password 这样的专业选手自不必说,iOS 和 macOS 内置的密码管理功能也可以实现。
但如果你尝试过这些工具,可能会注意到它们建议的密码往往不那么好使:如果完全是乱序组合,虽然足够难猜,但记忆和输入就过于麻烦;即使像 1Password 那样支持生成「易记」(memorable)形式的密码(即由若干单词组合、修改而得的字符串),其选用的单词又经常过于晦涩生僻,对于国内用户并不能起到易记的效果。
看来,合适的密码还是得按自己的规则来。考虑到如今的密码一般需要有一定长度,并且规定同时包含大小写字母、数字和特殊字符,我们可以按照这样一种格式编写密码,以便在易记的同时符合大多网站的要求:
- 由三个常见小写英文单词构成,单词长度在 4—6 个字母之间,之间用连字符 连接。这样,所得密码的长度在 14 到 20 字符之间,符合常见强密码要求。
- 将第一个单词中的部分字母替换为形态类似的数字(例如 换为 , 换为 , 换为 等,即早年 BBS 文化中称为 leetspeak 的做法),其他字母替换为大写。这就满足了含有数字和大小写的长度要求。
- 将第二个单词中的部分字母替换为形态类似的特殊符号(例如 换为 , 换为 , 换为 等)。这就满足了含有特殊字符的长度要求。
例如:
- (由 修改而来,13 字符长);
- (由 修改而来,18 字符长);
- (由 修改而来,15 字符长)。
下面,我们看看如何用自动化方法批量获得这样的密码。为了兼顾易用和泛用,本文将介绍快捷指令和终端脚本两种方法。前者适合在 iOS 上运行,后者适合在电脑上整合到各种第三方效率工具中,也可以跨平台使用。文末还会跑题介绍一种不那么酷、但如今无法回避的思路——当然,我是在说 GPT。
首先,分别下载我做好的成品:快捷指令 | 终端脚本
如开头所说,现有易记密码生成方案的普遍问题是单词过于晦涩,记忆和输入都不够方便。因此,本文需求的前置步骤就是挑选一个合适的词表,作为生成密码的词汇来源。
什么词表比较合适呢?我见过英文社区有些用户选择了 Scrabble、Spelling Bee 等单词游戏的词表(例如 Dr Drang 使用 Python 制作的脚本),但这些游戏是为了比拼词汇量而生的,其词表里也有不少生僻词,对母语使用者都有些门槛,非母语使用者就更不用说了。
为此,我们先用 工具将 PDF 中的文本都提取出来。 是命令行 PDF 工具集 Poppler 中附带的,需要先通过 安装。当然,你也可以选择直接复制粘贴,但取决于所用 PDF 阅读器,复制结果可能没那么干净。
在终端运行:
这里, 选项忽略在遇到换页时生成的特殊字符,结尾的 将结果输出到终端而不是保存成文件。
结果如下(节选):
可以看到,每个词条主要由三项信息构成:单词、词性、CEFR 难度等级,各项信息之间用空格分开。此外,个别多义词会后接数字区分不同词性的条目。
这种用固定字符分开的格式,是典型适合用 命令处理的类型。经过一番尝试,我们可以使用下面这串命令来做清洁工作:
这里:
- :是指对于输入中的每一行,只保留第一列()。 默认以空格作为列的分隔符,因此对于「牛津 3000」词表的格式,保留第一列就是只保留单词部分(少数不遵守该格式的行除外)。
- :是指检查每一行的长度(),只保留 4 到 6 个字符长度的行。
- :是指检查每一行的列数(),只保留列数大于零的行。这本质上就是在删除空行。
- :是指删除()每行中后接表达式不能匹配()的部分。使用的表达式是 ,即任何字母加上换行;除此范围之外的内容都会被删除。( 使用的是类似 的「基础」正则表达式 BRE,看着会比较啰嗦。)这样做的目的是删除词表中多余的句号、数字、空白符等。
- 最后, 的组合用来删除重复行(考虑到多义词词条),并获得按字母排序的输出结果。
打开输出的 ,可以看到处理效果比较完美,得到了大约 1500 个长度适中、简洁易记的单词作为密码素材。唯一的漏网之鱼是 ,这是因为原文件个别条目拆写为两行,第二行开头就是词性。不过,对于这种一次性的任务,我们也没有必要过度优化,手动删除即可。
动手之前,先做一下宏观规划。根据文首确定的需求,我们的快捷指令大致依次要完成这些工作:
- 从词表中随机提取三个单词;
- 对单词做字母替换、大小写替换等改动;
- 将处理结果组合成密码并输出。
下面分别说明其中的要点或难点。
从词表中随机提取单词
修改单词中的字母
如上所述,为了满足密码含有数字和特殊字符的要求,我们需要将单词修改为所谓的 leetspeak 版本,也就是把字母换成形态类似的数字或符号,常见的对应关系如下表所示:
这样,我们就快速做出了两个词典,分别将字母翻译为形近的数字和字符。
每个自动化工具都有自己最适合的领域。快捷指令更擅长的是调用设备传感器、以及调用系统原生的功能和服务;但对于本文这种以处理文本为主的需求,实现起来就很折腾。如果不是为了写这篇文章,我根本没有任何动力打开快捷指令做这种事情。
相比之下,不太复杂的文本自动化正是终端脚本的「舒适区」。从前面给出的成品内容就能看出,本文需求用终端脚本做起来是很快的,这里也只简单解释几处要点:
- 函数负责从词表中随机提取单词。这里,为了避免将词表单独存储在外部文件中(因为那样不方便整合进第三方工具),我们用 heredoc 的形式将整个词表输入给 ,通过管道原样交给 随机挑选一行()作为输出。
- 函数是程序主体。其实本来这几个简单的步骤没有必要放在函数里,这里唯一的目的是方便和美观;否则,其中的命令得写到占据一千多行的词表之后去,为了我们的手指健康考虑还是避免这么做。这样,只要在最后一行调用一下 就行了,主要的编写工作都可以在脚本开头完成。
- 单词的修改通过 命令完成。这里充分体现了终端脚本的优势:快捷指令中用上 JSON 词典的奇技淫巧还要折腾好几步的功能,在这里一行就可以完事。具体而言,当 后接两个字数相当的字符串(或小写字母 、大写字母 等代号)时,其功能是逐一检查输入中的每个字符,如果在第一个字符串中找得到,就将其转为第二个字符串对应位置的字符。
这个方法写在最后,因为:
- 要花钱;
- 原理决定了输出结果可能是别人用过的;
- 缺少了一些庸人的乐趣。
但还是得写,因为现在是 2023 年。
请打开你的 ChatGPT、OpenAI Playground、Bing Chat,或者你能摸得到的任何 LLM 聊天工具,把下面的内容喂给它(为了表述简洁,跟文首需求稍有差别,但不影响效果):
注意:
- 最后的列表空了一截是为了让模型照葫芦画瓢,输出会自然续写下去;
- 如果有不同格式要求,可以相应调整 prompt 中的表述;
- 我无法确认它知不知道 Oxford 3000 是什么东西,但还是先写上了,反正没有坏处;
- 如果你有得选,实测最合适的模型是 text-davinci-003 或 gpt-3.5-turbo,更简单的如 curie、babbage 领会不了,更复杂的 gpt-4 有点大炮打蚊子。