How to securely save your secrets for free

This solution is based on Google Chrome, Gmail, and the Mailvelope extension for Chrome, which are all free.

First-time procedure

From the second time on, you only need Step 4 for storing and Step 6 for retrieving.

  1. Register a new Gmail account or use one of yours. Be it `X@gmail.com`.
  2. Install Mailvelope.
  3. Generate a key pair for `X+secrets@gmail.com`.
  4. Compose a message for `X+secrets@gmail.com`.
    1. Use a Subject like `Awesome Service` for your Awesome Service account.
    2. Click the Mailvelope icon that appears when you hover the body.
    3. You’ll see that a dialog appears with `X+secrets@gmail.com` in green.
    4. Enter account details, like your password.
    5. `Encrypt`
    6. You’ll see that the body of the message is now encrypted.
    7. Send the message.
  5. Given that `X+secrets@gmail.com` is just an alias of `X@gmail.com` you got the message in your inbox.
  6. Open it and confirm that it’s still encrypted.
    1. Click the Mailvelope icon that appears when you hover the body.
    2. Enter the password you specified in Step 3.
    3. You’ll see that the message is now decrypted.
    4. Close the message.

Why is this secure?

  • The network connection between your browser and Gmail servers is encrypted by default, so only you and Google can read your messages. With this encryption, not even Google will be able to read them.
  • When you send an email message protected by someone’s public encryption key, such a message can only be read by that someone using their matching private decryption key.
  • Anyone who got access to your browser without your consent and tried to steal one of these secrets would have to enter your password to read them.

Why is this practical?

  • It’s secure and free.
  • You can add a filter to label these messages so they appear collected.
  • You can search the subject or any other metadata you added to the bodies.
  • You can access your secrets from any Gmail device, anywhere in the world.
  • You can also keep all related secrets in a text file and send it as an encrypted attachment to `X+secrets@gmail.com`.

How to ´Promise.all´ the values of a JS hash

For doing something like this:

var hash = { key1: value1, key2: promise2, ... };
Promise.all(hash).then(hash => ...);

I wrote this little function:

  function promiseAll(hash) {
    const keys = Object.keys(hash);
    const values = keys.map(key => hash[key]);
    return Promise.all(values).then(resolved_values => {
      const resolved_hash = {};
      keys.forEach((key, index) => {
        resolved_hash[key] = resolved_values[index];
      });
      return resolved_hash;
    });
  }

which allows you to do this:

var hash = { key1: value1, key2: promise2, ... };
promiseAll(hash).then(hash => ...);

How to promise in Chrome extensions

The following setup of Message Passing for communicating JSON data from a content script to a popup script is pretty usual:

  chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    if (sender.tab) {
      return;
    }
    switch (request) {
    case 'selected_count':
      const count = selected_count();
      console.log('selected_count:', count);
      sendResponse({
        data: count
      });
      break;
    case 'selected_items':
      const items = selected_items();
      console.log('selected_items:', items);
      sendResponse({
        data: items
      });
      break;
    case 'unselect_all':
      unselect_all();
      break;
    }
  });

However, if some ´items´ contain data that you get asynchronously (like an image), then the setup above will fail if those data are not loaded before the handler returns.

Unfortunately, ´Promise´s are the right tool for the job in this case, but you can’t use them right away, because you can’t send a promise back to the popup and let it handle that, because a message can’t contain a promise, because

A message can contain any valid JSON object (null, boolean, number, string, array, or [my annotation: POJO] object).

Fortunately, according to the documentation,

If you want to asynchronously use ´sendResponse´, add ´return true;´ to the ´onMessage´ event handler.

which you can do like I show in the rewrite below, where I use a ´Promise´ object to take care of the asynchronous bits:

  chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    const KEEP_CHANNEL_OPEN = true;
    const CLOSE_CHANNEL = false;
    var result = CLOSE_CHANNEL;
    if (sender.tab) {
      return result;
    }
    switch (request) {
    case 'selected_count':
      const count = selected_count();
      console.log('selected_count:', count);
      sendResponse({
        data: count
      });
      break;
    case 'selected_items':
      selected_items().then(items => {
        console.log('selected_items:', items);
        sendResponse({
          data: items
        });
      });
      result = KEEP_CHANNEL_OPEN;
      break;
    case 'unselect_all':
      unselect_all();
      break;
    }
    return result;
  });