Building a Password Manager with Electron and WebCrypto


Electron (formerly known as Atom Shell) is a desktop application API/executable which combines Node and Chromium. It's been used to build cross-platform apps like Atom, Slack, and Visual Studio Code. I took it for a spin to build a little password manager application and wanted to share my experience with the rest of the class.

Motivation

I've been a happy user of KeyPass for many years. Unfortunately, it's really difficult to move between my PC and Mac because KeyPass just doesn't work well on the Mac. I'm not a fan of any password service which transmits data to remote servers, so there haven't been many great options for going between machines besides CLI tools.

Most of my non-geek friends and family don't use password managers, mainly because they are too complicated. I figured this might be a good opportunity to build a very simple one.

Why Electron

Electron is cross-platform (Windows/Mac/Linux) but there's much more to it. Since it's powered by Node, you get to get to leverage thousands upon thousands of NPM modules. Chromium is the rendering engine, so you can write your UI in (cutting-edge) HTML/CSS/JS using whatever other tools you want, but with added benefit of not needing to worry about supporting old browsers or over-the-wire performance optimizations because everything is local. How refreshing!

It may sound strange, but it's a lot like writing a web app on your local machine, but without the need to run your own local web server, and wrapped with a thin native shell. Electron exposes all sorts of native APIs as Node modules like:

Let's say you want to use the clipboard, here's how easy it is:

    
var clipboard = require("clipboard");
clipboard.writeText("omg");

File I/O can be done just by using standard Node modules. Here's how to combine it with a native Save File dialog:


dialog.showSaveDialog(function (fileName) {
    if (!fileName) return;
    fs.writeFile(filePath, "some data", function (err) {   
        if(err){
            dialog.showErrorBox("Could not save the file", err.message);
        } else {
            console.log("saved file!");
        }
    });
}); 

Getting Started

My advice is to install electron-prebuilt and play around a bit to get your feet wet. Then once you're ready to get serious, use electron-boilerplate as your starting point. Electron-Boilerplate brings a LOT to the party like Jasmine, Gulp, Less, Babel, JS modules and more in order to organize your code.

Here are some good resources to get you started:

Developer Experience

Debugging is easy. The views are Chromium processes, so you just open Dev Tools just like you would when working on any web page. The Node process can be debugged with anything that supports the V8 Debugger Protocol like node-inspector, WebStorm or Visual Studio Code.

What I really like is the tight feedback loop. Whenever you make a code change you can just hit Ctrl + R and reload the app. You can even wire up something like a Gulp task to reload automatically as your files change. This is a natural for web development, but it's relatively uncommon when developing desktop apps.

When it comes to packaging, keep in mind that you need to build for Windows on Windows and likewise on a Mac. Here's an example command to package for OSX using the electron-packager module:


electron-packager . your-app --platform=darwin --arch=all --version=0.25.3 --out=./dist --overwrite --icon=icons/your-icon.icns

WebCrypto

There were many options for encryption. Some standalone JS libraries exist like crypto.js and Node ships with some crypto libraries too. However, all that cryptography is happening in JavaScript, which isn't optimal. There's a new W3C candidate spec called WebCrypto which adds some standard support for cryptography into the web platform (e.g. native code baked into the browser, not interpreted JS libs). This makes it possible, in theory, to have high-performance crypto support by offloading calcs to the GPU.

I wanted PBKDF2 for generating keys from passwords and 128-bit AES symmetric encryption. None of this was a problem, at least on Chromium. I did notice that any failed attempt to encrypt or decrypt resulted in a DOMException with no other information.

Here's an example of how to encrypt something. You'll notice the API is all Promise-based.


function generateKey() {
    var cryptoKey;
    return crypto.subtle.generateKey(
        {
            name: "AES-CBC",
            length: 128
        },
        false,
        ["encrypt"]
    );
}

function encryptContent(cryptoKey) {
    var encoder = new TextEncoder("utf-8");
    var arrayBuf = encoder.encode("super secret");
    return crypto.subtle.encrypt(
        {
            name: "AES-CBC", 

            // this is the initialization vector
            // you create a new one each time you encrypt
            iv: crypto.getRandomValues(new Uint8Array(16))
        }, 
        cryptoKey,
        arrayBuf);
}

function logEncryptedResult(encryptedBuffer) {
    console.log("look ma, it's encrypted",  new Uint8Array(encryptedBuffer));
}

function ohNoes(err) {
    console.error(err);
}

generateKey()
    .then(encryptContent)
    .then(logEncryptedResult)
    .catch(ohNoes);

The one tricky thing with WebCrypto is you are working with lower level things like Uint8Array instead of plain old Arrays and Strings, so you will probably run into issues with unicode. Checkout this article for more info.

End Result

I'm pretty happy with my pw-puppy app. Here's a screen shot:

You can download the installers from here.

Conclusion

The lines between web and native applications continue to blur. There's a lot of value in leveraging existing web skills and assets when trying to build things for multiple platforms.

Building my app with Electron was an enjoyable experience and I'm eager to build more. It has an interesting set of tradeoffs compared to other desktop app platforms. If you have a need to build cross-platform desktop apps, and know your way around HTML/CSS/JS/Node and the wild world of modern web tools, then definitely give it a try. You'll be impressed.

[ Archive · Home ]