Matrix 首页推荐 

Matrix 是少数派的写作社区,我们主张分享真实的产品体验,有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章,展示来自用户的最真实的体验和观点。 
文章代表作者个人观点,少数派仅对标题和排版略作修改。


最近一时兴起,又在从 1Password「逃离」。这次找到了一款在终端环境下运行的密码管理器 Pass,不仅开源免费,而且还能使用 PGP 算法进行加密,从各种角度来说,都是 1Password 不错的替代品。

话虽如此,单纯使用终端来管理密码始终不太方便。在几经研究之下,终于利用 Alfred 和 AppleScript 研究出可用于系统全域的自动填充系统(而不仅仅是在浏览器范围内)。

话不多说,先看疗效。

在这篇文章中,我会简单地描述如何利用 Pass 构建一个密码保险库,并尝试与 Alfred Workflow 连接起来,完成密码自动填充。

安装 Pass 并初始化保险库

注:本文着重于讲解使用 Alfred Workflow 配合 Pass 快速填充密码,因此本节内容将会较为简略。你可以在互联网搜索相关资料获取更多教程。

要安装 Pass,叫出 macOS 用户的的老朋友 Homebrew 就行。

brew install pass

由于 Pass 不利用主密码、而是 GPG 密钥对来完成保险库加密,因此这个过程中会额外安装 GnuPG。对于大部分普通用户而言,我个人推荐再额外安装诸如 GPG Suite 的密钥管理工具。你可以前往 GPG Suite 的官网 下载到 GPG Suite。

安装好 GPG Suite 之后,打开 GPG Keychain 程序,你可以看到一些密钥展示在屏幕上。

GPG Suite 主界面

点击顶部「新建」按钮,然后填写你的姓名、电邮地址和一些额外信息。密码(Passphrase)可以不填(可以之后再设置),但如果你填写,建议你检查一下 pinentry-mac 是否被正确安装、并能正常在终端中使用(输入密钥时弹出对话框),否则使用 Alfred Workflow 的时候会卡住。你可以前往 pinentry-mac 的 Homebrew 页面 查看详情。

创建密钥后,在 GPG Keychain 中双击刚才创建好的密钥,找到「密匙标识」字符串,将它记录下来。

接下来,我们在终端输入以下指令来初始化 Pass 保险库。

pass init <key-id>

当然了,<key-id> 记得换成密匙标识,代表 Pass 需要用刚才新建的 PGP 密钥对来加密你的密码保险库。

接下来,利用 pass generate <account-description> 来创建密码,然后用 pass edit <account-description> 编辑它,以 login: <my-username> 的方式来写入用户名。

iTerm 窗口,执行 pass edit my-account,启动 vim 编辑器,在编辑模式下

你也可以利用官方提供的导入脚本,从 1Password 和其他密码管理器中直接将帐户信息导入到 Pass 中。你可以在 Pass 的官网 找到对应的脚本。

配置 Alfred Workflow

终于进入正题了。首先,我们确定一下需求:

  • 在 Alfred 中搜索帐户
  • 在终端中执行 pass <account>,然后解析 Pass 返回的结果
  • 自动输入用户名和密码

利用 Alfred 强大的 Workflow 功能,这些操作自然不是什么很复杂的事情。我们一个个拆开来说。

搜索 Pass 中的帐户

默认情况下,Pass 保险库中的帐户描述是不会加密、且保存为文件名,存储在 ~/.password-store 中。利用这一点,我们可以通过「查找文件」的方式来利用 Alfred 搜索保险库。

我利用 Node.js 包 alfy 配合 fs 来完成文件搜索工作。使用 Node.js 环境的原因主要有两个:一是我对 Node.js 比较熟悉、编写起来比较快;二是 Alfred 本身没有为 JavaScript 暴露文件搜索接口。

知道该怎么做,接下来就该动手开始做。打开 Alfred 偏好设置,进入「Workflows」界面,点击「Create a new workflow」按钮。

在弹出框中,可以只填写 Workflow 的名称。点击「Create」即可创建空白 Workflow。

点击右上角的「汉堡」菜单按钮呼出「组件库」(我就这么叫它了),找到「Script Filter」,然后把它拖出来。

拖出来之后,会弹出节点配置的对话框。Keyword 填写「pass」(或者其他你喜欢的呼出关键词)。在下面的脚本编辑框中,填入 ./node_modules/.bin/run-node searchquery.js "$1"

接下来,右键点击左侧 Workflow 列表中你刚才创建的 Workflow,选择「Open in Finder」。接下来打开终端,输入 cd (注意后面有个空格),然后将 Alfred 打开的文件夹拖进终端,回车。

2022-11-21 10.56.57

然后在终端中输入 npm init -y(啊,当然你的电脑得装一下 Node.js 环境,这里不展开了)。等屏幕滚完之后,输入 npm i alfy 来安装 alfy 包。

接着,在文件夹中新建 searchquery.js 文件,输入以下代码。

import alfy from "alfy"
import fs from "fs"

let { input } = alfy

// Search file in ~/.password-store
let files = fs.readdirSync(process.env.HOME + "/.password-store")

// filter file with input keyword
let inputLowercase = input.toLowerCase()
let filteredFiles = files.filter(file => file.toLowerCase().includes(inputLowercase))
for (let i in filteredFiles) {
	filteredFiles[i] = filteredFiles[i].replace(".gpg", "")
}

// output to alfred
alfy.output(filteredFiles.map(file => {
	return {
		title: file,
		subtitle: "Autofill username and password.",
		arg: file,
	}
}))

这段代码先将搜索目录设为你的主目录下的 .password-store 文件夹,然后将搜索词的小写版本和文件名的小写版本比较,如果文件名包括搜索关键词,那么就将它列出来,以 Alfred 可读取的列表形式输出。

注意,输出的列表中每一项 arg 变量,指的是当用户选中这个搜索结果之时,向下一个行为传递什么数据。此处我们选择将帐户名称完整输出至 Workflow 中的下一个节点。

保存文件,然后启动 Alfred Workflow。输入 pass <account>,如果能正常出现结果,那么证明你的配置和代码是正确无误的。

执行终端指令并解析结果

在终端中输入 pass <account>,你可以看到 Pass 会将密码写入到第一行,之后会用 key: value 的形式保存其他的账户信息。(至少大部分兼容 Pass 的密码管理器都会约定俗成地使用这样的格式。)

我们需要做的,就是在终端中执行这个指令,然后解析 Pass 返回的输出,接着传输到下一个操作中去。

在 Workflow 中,拖出一个 Run Script 节点。先关闭自动弹出的设置窗口,然后从上一个节点右侧牵出一条线,连接到这个节点的左侧。

还记得我们上一节提到的 arg 变量吗?这条连线意思就是,将用户点击的项目的 arg(在这里是帐户完整名称)传递给我们刚才新建的 Run Script 节点。

双击刚才新建的 Run Script 节点,在编辑框中输入以下代码。然后保存。

PATH=/opt/homebrew/bin:$PATH
pass "$1"

由于在一些程序中的终端环境与我们平常使用的终端环境有所差异,因此我们最好先将 Homebrew 的二进制包目录添加到环境变量中。接着,我们只需要平常地让 Pass 输出账户数据即可。

注意这里的 $1,它代表的是从上一个节点传入的变量。通过这样的操作,我们就可以让 Pass 输出账户详情,然后传递到下一个操作中去。

接着,我们再拖出一个 Run Script,将上一个 Run Script 节点连接到新建的 Run Script 节点。双击新建的 Run Script 节点,修改 Language 为「/usr/bin/osascript (JavaScript)」,然后写以下代码:

function run(argv) {
  let props = argv[0].split("\n")
  let password = props[0]
  let username = ""
  for (let i in props) {
    if (props[i].startsWith("login: ")) {
      username = props[i].split("login: ")[1]
    }
  }
  
  return `${username}\n${password}`
}

这段代码的意思就是,将 Pass 返回的数据以行折断,从第一行读取密码、从 login 字段读取用户名,然后将它们拼合,以换行符分割,再传递给下一个节点。

理论上,这两个段脚本可以合并成为一个 shell 脚本,但是 shell 写起来实在是不太直观…… 如果你感兴趣,你可以尝试一下。

自动填充

我们的最终目的,是将用户名和密码自动填充进输入框中。因此,我们需要利用 AppleScript 来完成这种高级自动化操作。

同样,拖出一个 Run Script 节点,把线连好。在新的 Run Script 设置中,将 Language 设为「/usr/bin/osascript (AppleScript)」,然后填写以下代码:

on run argv
  set theQuery to item 1 of argv
  set splitedArgv to paragraphs of theQuery
  tell application "System Events"
    repeat with theItem in splitedArgv
      keystroke theItem
      key code 48
    end repeat
  end tell
end run

这段代码的意思是,从 Alfred 提供的变量中拿到我们传递过去的用户名与密码组合,将它们按行折断,形成数组。之后告诉「System Events」,对于这个数组中的每一项都进行键盘输入、然后按下 Tab 按键(48 号按键)以跳转到下一个输入框继续输入。

至此,我们的 Alfred Workflow 就这样编写完成了。你可以尝试在浏览器或者其他 app 中,呼出 Alfred 并尝试使用这个 Workflow。

抛砖引玉

很明显,单纯使用这个 Alfred Workflow 自然有其限制:例如,遇到 Grammarly 或者 Apple 官网那种用户名密码分步骤填写的网站,这个 Workflow 自然也就无能为力。以及,OTP 密码的复制方法并没有在本文中列出。

不过,这篇文章姑且为你提供了编写类似的 Workflow 的方法,你可以尝试自己创建、自己改造这个 Workflow。

当然,如果你希望使用开箱即用的版本,你可以尝试我制作好的文件,你可以 前往 GitHub 项目主页 下载到。(下载完别忘记点一个 star!)

另外,如果你没有购买 Alfred Powerpack,也可以使用快捷指令作为「平替」。快捷指令 app 中,包括字符串处理和 AppleScript 都有对应的执行方式,感兴趣的也可以尝试自己制作。

> 少数派请你做地图:城市声音收藏夹火热征集中,期待你创作的城市之声 🎧 

> 下载少数派 2.0 客户端 、关注少数派公众号,解锁全新阅读体验 📰 

> 实用、好用的正版软件,少数派为你呈现 🚀