花密(https://flowerpassword.com)是一种轻量级的密码管理工具,通过仅记忆一个私人密码,利用不同关键字实时生成相应的密码,来避免不同网站使用同样的密码,以及避免记忆多个网站的多个密码。
花密目前仅提供网页页面,每次需要密码的时候,还要联网打开页面,有时也不太方便。因此希望将花密迁移到本地使用,比如直接在命令行里面输入 password ss
就可以获得最终密码。用 flower password
做关键字在 github 上搜索发现,有人发布了疑似花密的算法(github.com/xlsdg/flowerpassword.js),经验证发现算法正确。也有人已经发布了疑似花密算法的命令行工具 (github.com/xlsdg/flower-password-cli),不过代码略复杂,一时没怎么看懂,因此决定选择前者。
新建一个目录 flowerpassword,进入其中后运行 npm install --save flowerpassword.js
,即将所需模块下载到本地。然后新建测试代码 test.js
如下:
import fpCode from 'flowerpassword.js';
console.log(fpCode('mypassword', 'key', 16));
虽然就两句话,但是用 node test.js
本地运行依旧出错,提示 Cannot use import statement outside a module
。查询一番后才得知,nodejs 默认使用 CommonJS
语法,而 import
属于 ES6
语法,最简单的解决办法就是将文件名后缀改为 .mjs
,然后成功运行。
通过 nodejs 的 readline
函数,可以获取用户输入。因此,代码修改如下:
import password from 'flowerpassword.js';
import readline from 'readline';
const r = readline.createInterface({
input: process.stdin,
output:process.stdout
})
r.question('?', user_input => {
console.log(password('mypassword', user_input, 16));
r.close();
})
这里有个小细节,flowerpassword.js
源代码中使用 export default function fpCode(...){...}
这样的语句,因此我们使用的时候,可以任意命名咱 import
进来的变量(示例代码导入 fpcode
,俺们自己的代码使用 password
)。如果对方使用 export
而不带 default
的话,就只能用 import { fpCode as password } from ...
这样的语法了。
考虑到私人密码(就是前面那个 mypassword
)不宜使用明文,为避免视觉泄露,建议将明文加密。最简单的加密就是使用各种编码样式给编码一次即可,比如 base64
编码。我们首先使用 Buffer.from('mypassword').toString('base64')
获得私人密码编码后的字符串(即'bXlwYXNzd29yZA=='
),然后在代码中直接使用 Buffer.from('xxx', 'base64').toString()
语句来解密之即可。于是最终代码如下:
import password from 'flowerpassword.js';
import readline from 'readline';
const common = 'bXlwYXNzd29yZA==';
const r = readline.createInterface({
input: process.stdin,
output:process.stdout
})
r.question('?', user_input => {
console.log(password(
Buffer.from(common, 'base64').toString(),
user_input,
16
));
r.close();
})
既然密码出来了,还要用鼠标选中、复制,然后找到合适的地方粘贴,感觉还可以偷懒一点,信息化的指导思想之一就是让代码多跑路,让用户少操作嘛。因此想再次修改代码,让最终的密码直接到系统 clipboard 上,用户只需找地方Ctrl-V
即可。
经查询,nodejs 里面的 child_process
模块里的 exec
函数可以干这个事情。这是个通用函数,用于调用系统功能,实现一些系统级的能力。代码的最后一个语句改为如下:
r.question('?', user_input => {
const new_password = password(
Buffer.from(common, 'base64').toString(),
user_input,
16
);
console.log(new_password);
exec('clip').stdin.end(new_password)
r.close();
})
这里需要吐槽一下,中文互联网的内容比较差,比如我刚开始搜索 js 加密
的时候,大部分给的答案都是 btoa()
和 atob()
两个函数,但后来认真查看了 nodejs 的官方文档后才知道,这两个函数早就被 nodejs 建议不再使用了。通过阅读 nodejs 的源代码及官方文档,可以得知当前(nodejs v20)官方实现的 encode
总共是以下几种
utf8
:这个是默认编码,别名 utf-8
latin1
:别名binary
utf16le
:别名 usc2
、ucs-2
、utf-16le
ascii
:向下兼容用base64
:转码规范见 rfc4648base64url
:转码规范见 rfc4648(其实与前者几乎一样,除了没有 =
补齐、将 +/
用 -_
代替之外)hex
本轮学习 javascript,通过将“花密”算法本地化过程,学习了如下知识点:
readline
函数的基础使用Buffer
编解码的基础使用与常见编码类型child_process.exec()
如何实现对系统 clipboard 的访问base64
编码与 base64url
编码的大致规范与区别