转载

动手打造跨设备、无需同步的密码管理器

动手打造跨设备、无需同步的密码管理器

*本文原创作者:mscb,本文属FreeBuf原创奖励计划,未经许可禁止转载

密码管理器大家都有用过吧?但你相信有一种密码管理器不仅可以跨设备使用,还无需同步!无论你相不相信,今天就让我来教你如何自己动手制作一个。

密码管理器,实在太多。简直能让人挑花眼。有各种各样类型的,但主要是有2种:网络同步的、存在自己本地数据库的。从理论上讲,存在本地的会比自己存在网络上的会相对安全一点。因为最近经常能听到某些网络型密码管理器被爆出漏洞什么的,虽然说你保存的密码是加密保存在服务器上的,但还是有点安全隐患。

相对于网络型密码管理器,本地型密码管理器确实会安全一点(取决与你的安全意识)。不过,与此同时,也带来一些缺点。比如没有网络型的方便,需自己对保存密码的数据库进行同步等等。

原理说明

跨平台?离线?不同步?

如果要实现这3个要求,我们需要重新审视一下自己对应密码管理器的需求。首先,我们使用密码管理器的目的不仅仅是为了保存密码,还为了安全。保证每个网站注册的密码都不同,而且是随机的,不存在与本人有挂钩的信息,比如手机号、生日、幸运数字等等,防止他人根据你的个人信息生成字典跑包。

​既然要保证密码中没有个人信息,而且需要无序,随机数显然是最好的选择。但如果使用随机数就无法实现每个平台都相同。所以必须使用一个统一的算法,这样各个设备使用同一套算法就能保证生成的密码都是相同的。

最终,想到如下算法,来实现密码同步:

用户需指定一个通用密码

需要指定一个网站的域名(不包含具体文件地址)

使用Hash算法生成密码。

这样,我们就能保证各个网站的密码都不一样,而且还不需要同步!

开始动手

密码管理器最终还是为浏览器服务的,不如我们直接将它做成Chrome浏览器扩展,这样使用方便,还能跨平台。

chrome扩展开发

开发一个chrome扩展其实很简单,只需要一点js的知识就行了,接下来让我们一起来看看如何开发!

要开发,首先需要 开发者文档 ,这是Google官方的文档地址,请自备梯子。如果需要中文文档的话可以自己找翻译版本的~

第一,在本地新建一个文件夹,这里取名为“ImPassword”

动手打造跨设备、无需同步的密码管理器

第二,新建一个名称为 manifest.json 的文件,这是用于保存整个扩展的描述信息。

{
  "manifest_version": 2,

  "name": "扩展名称",
  "description": "扩展描述文件",
  "version": "版本号",

  "browser_action": {
    "default_icon": "默认图标",
    "default_popup": "点击弹出的HTML网页"
  },

  "permissions": [
    "权限信息"
  ]
}

第三,我们再来分析一下。我们这个扩展的需求。

1.扩展要能提取到浏览器地址栏的网址。

​2.能储存必要的信息到本地中。

3.对所有的网站都必须要有读取的权限。

第四,根据需求,我们查查开发者文档,需要加入一些权限,比如: "permissions": ["tabs","storage","<all_urls>"]

​第五,把最终的 manifest.json 文件写出来!

{ 
   "name": "密码管理器", 
   "manifest_version":2,
   "version": "1.0", 
   "description": "密码管理器Chrome扩展", 

   "permissions": [
         "tabs",
         "storage",
         "<all_urls>"
   ],

   "options_page": "options.html",

   "browser_action": { 
      "default_icon": "icon.png", 
      "default_popup": "popup.html" 
   },

    "background" : {
      "scripts" : [
          "bg.js" 
    ]
  } 

}

这里简单的对上面的信息解析说明。 permissions 表示你需要申请的权限。 options_page 表示“选项”界面和html网页,也就是说,有了这个字段则当扩展安装到浏览器后,能实现 右键->选项 操作。 background 里的内容我这里是添加一个空js文件,因为如果没有该参数,每次点开扩展的时候鼠标会一直转转转,强迫症患者表示无法接受。

​第六,根据信息,建立相关的文件。

动手打造跨设备、无需同步的密码管理器

书写代码

从用户的角度看,用户点击一下我们的扩展图标会弹出一个小小的框框,而这个框实际上就是我们上面配置的 popup.html 。使用这个html文件里的内容就是用户能看到的东西,可以称它为显示界面。

options 代码

对于 options.html 内的内容则是当用户 右键->选项 时显示的内容。我们现在的目的就是,通过选项界面需要用户配置一个独立密钥,并保存。而当用户点击扩展图标的时候,扩展会自动获取网站的域名,并与设置的密钥进行Hash操作,产生一个重复率比较低的网站密码。

首先,打开 popup.html 文件,写入以下内容

<html>
 <body>
    <p id="status">
        hello
    </p>
 </body>
<script type="text/javascript" src="/md5.js"></script>
<script type="text/javascript" charset="utf-8" src="/popup.js"></script>
</html>

这里的代码实在太简单了,相信大家都能看懂。id为status的标签主要用来显示网站的密码,下面引用了2个JS文件,一个是我们等一下要写的 popup.js 文件,还有一个是用于哈希的库。

由于考虑到很多网站的密码位数不能大于20位,大部分的hash函数都无法用,这里只能用一个16 BIT MD5的hash算法。

现在,先来写一下选项界面的代码。

打开options.html,写入以下内容。

<!DOCTYPE html>
<html>
<head><title>选项</title></head>
    <body>

    <div>输入密码:
         <input type="text" id="text"></input>
         <button id="set">保存</button>
    </div>

    <script src="options.js"></script>

    </body>
</html>

​其实也就是一个简单的html,一个文本框一个按钮而且。接下来打开options.js,开始真正写代码了。

document.body.onload = function() {
  chrome.storage.sync.get("data", function(items) {
    if (items.data == undefined){
      return;
    }
    if (!chrome.runtime.error) {
      console.log(items);
      document.getElementById("text").value = items.data;
    }
  });
}

document.getElementById("set").onclick = function() {
  var d = document.getElementById("text").value;
  chrome.storage.sync.set({ "data" : d }, function() {
    if (chrome.runtime.error) {
      console.log("Runtime error.");
    }
  });
  window.close();
}

​下面来和分析一下,上面代码的具体意思。代码由2个块组成,这2个块实际上都是事件。第一个为载入事件(即当你打开选项界面的时候会触发的内容),第二个为ID为set的按钮的点击事件。

chrome.storage.sync.get 是chrome的一个api,用于获取储存的内容。这里的判断逻辑是,如果获取有获取到内容(即说明之前有保存过密码),则显示原先的密码。如果没有获取到,就不显示。

chrome.storage.sync.set 就很好理解了,当然是把密码保存起来起来。

popup 代码

既然选项界面的代码写好了,接下来看看popup.js里的内容,这个是这个扩展的主要逻辑部分。但也不要担心,这个扩展只是做了一点微小的工作,不会很难~

看看popup.js的全部代码,等一下我们再来分析具体的原理。

var receiver_data = new Array(), callFun;

chrome.tabs.query({'active': true, 'windowId': chrome.windows.WINDOW_ID_CURRENT},
    function(tabs){
        var urlLink = tabs[0].url.replace("http://", '').replace("https://", '').split('/')[0];

        chrome.storage.sync.get("data", function(items) {
            if(items.data == undefined){
                document.getElementById('status').textContent = "未配置独立密钥!";
                return;
            }
            if (!chrome.runtime.error) {
                pass = items.data;
                receiver(["Password",pass]); //发送信号
            }
        });
        receiver(["Url",urlLink]);
        receiver(["RUN",run]);//发送函数信号

    }
);

function receiver(value){ //信号接受函数
    if(value[0] == "RUN"){
        callFun = value[1];
        return 0;
    }
    if(receiver_data.length >= 2){//清空
        receiver_data.length = 0;
        receiver_data.push(value);
        return 0;
    }
    if(receiver_data.length <= 2){//继续添加
        receiver_data.push(value);
    }

    if(typeof(callFun)=="function" && receiver_data.length == 2){
        callFun(receiver_data);
    }
}
function run(buf){
    if(buf.length == 2){
        var pass,urlad;
        if(buf[0][0] == "Url"){
            urlad = buf[0][1];
            pass = buf[1][1];
        }else{
            pass = buf[0][1];
            urlad = buf[1][1];
        }
        document.getElementById('status').textContent = (urlad.MD5()+pass.MD5()).MD5();
    }
}

开头定义了2个全局变量,一个是接收器数组,一个是储存函数指针的变量。

由于Chrome是以回调的方式返回具体的url的,这样会给我们处理带来很大不便。所以 receiver 是我自己定义的一个接收器,用于处理传入的信号,并将信号数据保存到接收器数组里。接收器数组就像一个容器,接受到的数据只要不超过数组下标,均保存起来。接收器会对传入的信号进行处理,如果没出现Run标志,则把数据保存到数组中,如果出现指定标志,则通过函数指针执行指定函数,同时带上接收器数组的内容。

介绍完接收器的具体实现方式,我们再来看看,这个代码的具体逻辑。

由于我们需要得到tab的URL地址,并对其进行分解。所以要使用Chrome的一个api去实现(前面我们已经申请了权限)。

chrome.tabs.query({'active': true, 'windowId': chrome.windows.WINDOW_ID_CURRENT},function(tabs){})

现在只需要在 function(tabs){} 内写入需要执行的操作即可!而该匿名函数内的处理的是,对tabs得到的url地址进行分解,得到一个域名地址,并保存到urllink中。接着从chrome.storage取到之前保存的密钥,如果没有则显示“未配置密钥”。然后就是把密码、地址、run函数指针,通通发送给receiver.

receiver 判断传入的标签,如果是RUN则将函数指针保存到callFun变量中,如果是其他的则保存到receiver_data数组里。

run 函数主要是处理由receiver传的参数,并对参数解析。最终进行Hash计算并输出到ID为status的标签中。

总结

虽然说,这里使用的密码是用md5 16bit 计算出来的,但也确实是无奈之举,同时也不算太安全。但相对与N个网站用同一个密码来说,这套不需要同步的且N个网站对应N个密码的方案,确实能有效的提高安全性。不过,这套方案真的有那么完美吗?显然不是。举个例子,你无法很高效更改某个网站的密码。假设有一天你注册过的某个网站被脱裤,你的密码被爆出来了,虽然不会影响其他网站账号,但你在该网站的密码肯定要修改吧?但你没有一个很有效的方案修改密码,因为该密码是根据你的初始密钥计算出来的,如果要修改就要修改初始密钥,那就得修改所有网站的密码……

没办法,一个方案有它好的方面,必然有它不好的方面。所以我本人的解决方案是,用2种密码管理器,一种是本地型的,一种是本篇文章写的。我对于一些很重要的网站,比如支付宝、百度、Google、Youtube、Twitter、Freebuf、Github…这些网站均保存到本地密码管理器中,而在网络中其他乱七八糟的网站、或者是各种只有注册才能看的网站,都用本篇文章的密码管理器生成密码。对于重点网站重点保护,对于不重要的网站用普通的方式保存(但安全性也蛮高的)。

根据开发者文档的描述,用chrome.storage保存密码的方式貌似并不安全,欢迎大家提供更好的方式给我~

最后,我把该扩展的所有代码,打包上传到Github上了,欢迎大家提交issues和pull request哦!

NoSync-PasswordManager : https://github.com/mscb402/NoSync-PasswordManager

*本文原创作者:mscb,本文属FreeBuf原创奖励计划,未经许可禁止转载

原文  http://www.freebuf.com/articles/database/123055.html
正文到此结束
Loading...