Moving from password-store to Bitwarden

Password-store, the one true unix way of password management has served me well for over 6 years. However, as I'm starting to use other devices in tandem such as my phone and laptop, I've become jealous of those who can easily fill in their credentials with a click of a button.

Right now, my workflow on my desktop and laptop computers (which run windows) are:

  1. open windows terminal, ssh into my server that holds my passwords
  2. run pass
  3. copy the output
  4. paste it where I need it

It's been very cumbersome, but the friction was low enough that I haven't done anything about it. However, it's time to make things a bit easier as this workflow has been feeling more cumbersome recently.

I had tried to make a nicer workflow for pass on windows, but getting gpg running properly has been a nightmare, and I eventually gave up. Further, passff extension can't connect to the local instance of pass properly and keeps complaining. I'm sure I could get it to work easily on linux or *BSD, but here we are!

Exporting from password-store

As password-store deals with basic tools, exporting passwords is superbly easy. In fact, this is one of the main reasons I've stuck to it for so long, because I always knew my passwords were safe and in my control.

To do all this, I'm using linux under WSL on windows as I don't want to deal with Windows's idiosyncrasies with gpg and pass.

To export passwords from pass, gpg can be used directly after importing your key and decrypting individual files. The below snippet assumes you have your passwords in passwd directory and want the decrypted files in passwd-decrypted directory with the same directory structure. First, we copy the directory as is, deleting all the gpg files inside to preserve the directory structure.

$ cp -r passwd passwd-decrypted
$ find passwd-decrypted -type f -name '*.gpg' -delete

And decrypt.

$ find passwd -type f -name '*.gpg' -exec gpg --output 'passwd-decrypted/{}.txt' --decrypt '{}' \;

You can remove the extension too with any bash-compatible shell.

$ shopt -s globstar # enables recursive glob for bash 4+
$ for f in **/*.gpg.txt; do mv "$f" "${f/%.gpg.txt/}"; done

Now we have a directory full of decrypted passwords.

Importing into Bitwarden

Bitwarden expects either csv or json with a specific format. The issue at this point is converting the free form text format that pass allows, to a more structured format that Bitwarden expects. This solely depends on how you have structured your pass passwords. Mine are in the following format:

username: <username>
email: <email>
www: <url>


multiline free-form notes

Unfortunately, even though more recently I've tried to adhere to this format, I haven't always. So there are a lot of files without much structure, which means I have to manually import them. The only reliable part of my password collection is the first line, and if exists, otpauth://.

set -eux

function add {

    folder=$(dirname "${filename}")

    if [[ "x$folder" = "x." ]]; then
        name=$(basename "${filename}")
        name="${folder}-"$(basename "${filename}")

    password=$(cat "$filename" | head -n 1)
    username=$(cat "$filename" | grep -E '^(email|username):' | head -n 1 | cut -d: -f2- | sed 's/^[ \t]*//')
    totp=$(cat "$filename" | grep otpauth)
    url=$(cat "$filename" | grep -E '^(url|www|website):' | head -n 1 | cut -d: -f2- | sed 's/^[ \t]*//')
    notes=$(cat "$filename")

    echo -n "Adding $name..."

    item_template=$(bw get template item)
    login_template=$(bw get template item.login)

    item_login=$(echo $login_template | jq --arg u "${username}" '.username=$u' | jq --arg p "${password}" '.password=$p' | jq ".totp=\"${totp}\"" | jq ".uris=[\"${url}\"]")
    item=$(echo $item_template | jq ".name=\"${name}\"" | jq ".login=${item_login}" | jq --arg n "${notes}" '.notes=$n')

    echo "$item" | bw encode | bw create item >/dev/null
    echo "done"

# find * ... to remove the preceeding ./
find /path/to/passwd/decrypted/* -type f | while read f; do add "$f"; done

Note that the above script is extremely hacky and fails at the mere sight of anything being imperfect. Use with caution. It does handle quotes in passwords though, which is nice.

And finally sync.

$ bw sync

At last I can click to autofill everywhere I go!