尴尬的浏览器密码
2022-09-28

你有没有遇到这样的情况:浏览器帮你记住了密码,能自动填写密码登录,但你想知道密码内容是什么,对不起,它没法告诉你。

密码框中的一串小黑点,无法查看也无法复制。这样做有一定的安全考虑,因为大多数人不会给每个网站分别设置不同的密码,而是同一个密码到处用。倘若某一个网站的登录密码被居心不良的人拿到,其他账号的安全也会受到威胁。

尴尬的是,这一招能把老实人关在门外,却给坏人留了后门。想拿到小黑点所代表的密码明文,方法很简单,只要打开浏览器的调试功能,选中密码框,把 HTML 标签属性由 type="password" 改成 type="text",密码明文就显示出来了。

这就好像把门钥匙藏在门框上面,不知道的人一筹莫展,知道的人伸手一摸就有。因此我觉得,浏览器不允许获取密码内容,对普通小白用户算是一种误导。会让用户误以为浏览器记住的密码很安全,于是放心大胆的在不同网站上使用相同密码,长年处于危险中却不自知。

Chrome 和 Edge 浏览器都提供了批量查看甚至导出密码的方法。在浏览器地址栏打开这个网址:

chrome://settings/passwords?search=password

稍微找一下就能看到曾经在浏览器上记住过的所有密码,一览无余,有没有一丝害怕?不过查看密码和导出密码都需要输入当前电脑用户的密码,比上面介绍的用网页调试器获取密码多了一道防范。

有些比较先进的网站把查看密码的按钮(一只小眼睛)放在密码框右侧,这对用户是个提醒 —— 密码并不是保密的,点一下就能看到。遗憾的是,大部分网站还没有提供这个功能,有意或无意的,让密码框继续保持着自欺欺人的神秘感。

通常来讲,安全和方便是一对矛盾,方便了就不安全,安全了就不方便。产品设计往往是在矛盾的两者之间找一个平衡,比如银行软件就宁要安全不要方便,而一些无关紧要的软件则尽量降低使用门槛,优先保证方便,安全方面就管不了太多了。

然而,我们常见的输入账号密码登录,无论是安全性还是便捷性,表现都不好。用户经常会输错密码,说明它不方便。浏览器体谅到用户的痛苦,发明了记住密码功能,又失掉了安全性,两头不讨好。

近年来生物识别技术例如 Face ID 和 Touch ID 在苹果的推动下迅速普及,在安全性和便捷性上双双有了提升。相比之下,登录密码就显得尴尬了。但密码作为历史遗留,必然还会长期存在,只不过它的作用会越来越弱。现在很多网站都力推 2FA(2-Factor Authentication),即两步验证。第一步验证仍然是传统的密码登录,第二步验证在比较慎重的动作之前,比如删除数据等不可逆的操作。

有了 2FA 护航,对密码安全性的依赖其实是降低了,因为重要的操作会要求使用第二步验证。密码作为一个尴尬的存在,不如就专心的负责好方便这一条,让用户不再需要为密码的事而耗费脑细胞,甚至用啥内容作为密码,也都帮用户决定得了。想必你一定见过,在设置密码的时候,一旁有个显示密码强弱的指示条,一般的密码达到中等安全就不错了,很难达到强安全级别。现代浏览器建议采用自动生成的随机密码,又长又没规律,当然也甭打算用脑子记住它,就连抄下来都费劲。

用浏览器生成的密码,必须同时让浏览器记住它,而这样的密码长得奇形怪状,念也不好念,抄也不好抄,想拿到密码串的内容,唯有拷贝是可行的。可是呢,浏览器又不允许拷贝密码,是不是陷入了尴尬?

为此我做了一个浏览器插件,可以按快捷键拷贝密码,像拷贝普通文字一样。过程中并不显示密码的明文,保持小黑点的样子,这样在共享屏幕的时候,也不用担心密码泄露。

代码呈上:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
(function () {
  function isMac() {
    return /(Mac|iPhone|iPod|iPad)/i.test(navigator.userAgent);
  }

  function writeTextToClipboard(text) {
    // navigator clipboard api needs a secure context (https)
    if (navigator.clipboard && window.isSecureContext) {
      return navigator.clipboard.writeText(text);
    } else {
      let currentFocusElement = document.activeElement;
      let currentSelectionText = window.getSelection().toString();

      // make temporary textarea element
      let textArea = document.createElement("textarea");
      textArea.value = text;
      // the textarea have to be on page and technically visible
      textArea.style.position = "fixed";
      textArea.style.width = "0";
      textArea.style.height = "0";
      textArea.style.padding = "0";
      textArea.style.border = "none";
      document.body.appendChild(textArea);
      textArea.select(); // select text, get ready to copy

      return new Promise((resolve, reject) => {
        // execute copy command, though this api is deprecated
        document.execCommand("copy") ? resolve() : reject();
        // do the cleanup
        textArea.remove();
        if (currentSelectionText.length > 0) {
          currentFocusElement.select();
        }
      });
    }
  }

  let copyModifierKey = isMac() ? "Meta" : "Control";
  let isCopyModifierKeyDown = false;

  window.addEventListener("keydown", (event) => {
    if (event.key === copyModifierKey) {
      isCopyModifierKeyDown = true;
    } else if (isCopyModifierKeyDown && event.key === "c") {
      if (document.activeElement.type === "password" && document.activeElement.value !== "") {
        writeTextToClipboard(document.activeElement.value);
      }
    }
  });
  window.addEventListener("keyup", (event) => {
    if (event.key === copyModifierKey) {
      isCopyModifierKeyDown = false;
    }
  });
})();

这段代码本可以更短,大部分篇幅占用是为了兼容 http 而写的 tricky code。在安全的 https 协议下,把密码串写入剪切板,用很短的代码就搞定了。但当协议是不安全的 http,浏览器实在不放心,禁用了 navigator.clipboard.writeText 这个 API。http 也不能不管啊,想想那些少有人维护的内部系统,甚至都没有域名而用 IP 地址直接打开,怎能指望它再搞一个 SSL 证书。

最终的解决方法可以说十分曲折,在页面上创建一个看不见的 textArea 把密码字符串写入这个多行文本框,然后悄悄用程序选中它上面的文本,再调用已经过时的document.execCommand("copy")来完成复制,可以说是以老制老,用上古魔法来打败上古魔神。

插件名字就叫 Copy Password,已发布到 Chrome 和 Edge 浏览器的商店:

插件的完整源代码见 GitHub 仓库

END