Skip to content

Instantly share code, notes, and snippets.

@Grissess
Created July 30, 2026 20:28
Show Gist options
  • Select an option

  • Save Grissess/5efe88496364b8de43bac28691b2b281 to your computer and use it in GitHub Desktop.

Select an option

Save Grissess/5efe88496364b8de43bac28691b2b281 to your computer and use it in GitHub Desktop.
Pass and Syncthing: the Unix Way

I'd been hesitant to adopt a password manager for a number of years, due to a few reasons:

  1. I kept moving between computers. I still do. I have two or three laptops and a desktop in the active rotation; the desktop's been through about three generations of hard disks. People know me as someone to whom they can give their "outdated" hardware to get some more use out of it. So keeping a password database up to date between all of them is not really trivial.

  2. I have an inherent distrust of "cloud" solutions. These are my passwords; they anchor trust for all my accounts. "Trust me bro" is not a good security policy for something like that. Yes, I know, most of them have reasonable thought and effort put into cryptosystem design, but the point still stands: for starters, you're trusting your vendor to implement that design right, even if it is correct, and--after that--you're investing all your effort and trust into one vendor. I'm sure that's fine for you KeePassXC users out there who can audit the source, but it's more concerning for things like 1Password where you can't. And, besides, the golden rule of auditability: Keep It Simple, Stupid. Anything requiring network is already dubious at best, since "easy reachability" doesn't mean just your ability in particular; anything that neeeeeeds a graphical interface will, if it doesn't detonate straightaway on Sway or work incorrectly with XWayland, still expose itself to a huge surface of code dependencies that can be exploited. Having a "nice REST API" or a CLI bolted on after the fact doesn't "fix" this; simply, less is more when you're dealing with secrets.

  3. I remember those passwords anyway, right? That's the whole point!

... well, until recently, at least. I do have about a dozen base passwords with a few derivations apiece, and all of them are pretty long but fairly memorable. But the number of accounts I have has far outpaced how many passwords I can remember, which leads me down the ugly rabbit hole of password reuse (also known as: how a phishing attack can pivot into emptying your bank accounts). There are some tactics you can use to combat this--I recall someone who insisted on inserting the domain name into their password in a regular and memorable way--but this can run afoul of very obtuse password requirements, including the ill-advised maximum length options. (I'm looking at you, Progressive and FCC CORES.) Plus it won't save you if a reasonably attentive attacker can guess your derivation scheme from an example; not all of them will invest the effort, of course, but it doesn't hurt to have good posture.

So I guess I have to revisit (3). Enter pass.

pass

In the effort of keeping it simple, pass is just that. No cloud sync, no home-rolled database formats, no extra software agents; pass does exactly two things:

  1. It owns a directory tree in '~/.password-store`, where the files inside are (encrypted) passwords; and
  2. It encrypts everything at rest with a GPG key (or keys).

The exact GPG key(s) it uses is determined by the ~/.password-store/.gpg-id file, which is a line-based plain text file of key IDs. Aside from that, most everything else is just wrappers around directory operations; pass rm, pass cp, pass mv, pass ls, etc., all do what you expect they might inside the tree. The more interesting commands are pass show, which is a gpg --decrypt, pass insert, which is a gpg --encrypt, and pass generate, which is a decent little password generator which automatically does pass store for you. pass show is more or less the default, so you can just pass Path/To/password and expect it to be written out to your terminal.

If you're following along, note that, unless you know what you're doing, I recommend a separate GPG key for this. That's easy enough to do:

gpg --gen-key

will walk you through the steps. Just name it appropriately, and give it a memorable email address (that doesn't need to be authentic!)--it's one of the easiest ways to identify the key. (For example, mine is pass@hygiea.) When you're done, give that email address to pass init, or, if you've already made the directory, just put it on a line by itself in ~/.password-store/.gpg-id.

Once that's all done, you should be able to just pass insert any of your current passwords. If you're coming from elsewhere, exporting passwords from your old tool is left as an exercise to the reader.

syncthing

So that's fine and dandy, but now I've run afoul of (1). Granted, I can (and have) SSH'd into my devices, but the default configuration of pinentry and gpg-agent means you have to do a few calisthenics to unlock the key password. Plus it depends on your box being SSH-able; if you've memorized your IPv4 gateway's address and are fortunate enough to have a NAT you can control at home, then, by all means, keep doing that.

For the rest of us, including when laziness wins over insanity, there's a lovely Go project called syncthing that tries to make file synchronization between devices easy again. Basically, it's a little network daemon with an HTTP interface that sets up a device keypair; you can take the public parts to other devices and, through the magic of ICE and some freely-available signalling servers (or signal-less protocols like mDNS), the two peers find each other. Somewhat like Wireguard, the key is the identity, so possession of the key is sufficient to validate your peers--although you can still give them reasonable names, if you'd like.

Friends and I have already used this for years; it started out with a book club, but we quickly learned that sync'd directories are well suited to hold "bare" Git repositories (decentralized Git, the way it was meant to be!). Perhaps more surprisingly, it's the perfect fit for pass: all you have to do is add your ~/.password-store as a "shared folder" and share it with your other devices, and syncthing will silently but eagerly keep all changes synchronized between them.

That glosses over one important point, though; the other devices, naturally, need your GPG key, the one you've been using to protect those precious passwords. Fortunately, it's not too hard to get that:

gpg --export-secret-keys -a $ID > $file
gpg --import < $file

Replace $ID with your key ID, and put $file wherever. If your key was encrypted with a password (as it should be!), this exported key is still protected by the same password, so--as long as that's pretty strong--you could even email it to yourself. Personally, I just use SSH and the clipboard; it works, right?

And here's the nice thing: since it's the same key, it'll have the same ID you already have in ~/.password-store/.gpg-id, which syncthing also dutifully synchronized. Zero reconfiguration required. Now you can pass insert on one host and, as long as your network is amicable, pass show it on another in seconds!

Bonus: TOTP

Everyone and their mother wants to set up your Google Authenticator codes, don't they? Fortunately, TOTP (and the lesser-used HOTP) are open standards; oath-toolkit is a decent POSIX CLI implementation thereof. Purists might complain that it's not the wisest to keep your OTP secrets and passwords together--probably why the Authenticator App was conceived, back before people used smartphones for everything--but any password manager worth its salt ought to give you OTP codes on demand, it seems.

You can do it differently, of course, but I personally store TOTP secrets--the string of letters and numbers you sometimes have to ask explicitly for--just like I would passwords, under the TOTP directory. For example:

pass store TOTP/example.org

And then just paste in the secret. If you do it this way verbatim, the following script will just work for you:

#!/bin/sh
pass show TOTP/"$1" | oathtool --totp -b -

Just pop that in any $PATH directory as pass-otp, set it executable, and then you can just pass-otp example.org and expect to get 123456 back. Shell completion is also left as an exercise to the reader.

(This assumes that all the OTP secrets you get are base32 encoded (-b); in practice, I've yet to see one that isn't, because they're shorter and easier to transfer that way. Whitespace isn't significant.)

You could set this up as an extension, too, but the paths are distribution- and package-specific, so I won't recommend them here. Use the Source, Luke; pass is just a shell script, after all :)

Further Directions

pass has a small but fairly replete set of plugins for things you might want, like, say, browser plugins. In doing so, you might wonder how to "autofill" other things, like usernames and email addresses--all of this and more is covered on their site. (Spoiler: subsequent lines in your password files!) And, again, these all transparently work with syncthing if you've set them up as above.

This is one of the reasons I prefer small, robust tools rather than a kitchen-sink-included, optional-cloud-sync password manager with built in social media platform: a small, well-designed interface is infinitely composable--and that includes unexpected, serendipitous composability, like "passwords are a file tree" with "this tool synchronizes file trees, no questions asked". That's true of extensions, too; if OTP 2 comes out tomorrow, all I need is a replacement for oathtool, and I'll be ready for it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment