keyfish

module
v0.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 25, 2024 License: BSD-3-Clause

README

keyfish

Keyfish is a site-specific password generator and manager. It is very much a personal project, and if you are looking for such a tool for your own use, I strongly recommend instead using one of the many more mature open source and commercial products designed for this purpose. Keyfish is idiosyncratic and unpolished, and does not aspire to greatness.

Nonetheless, if, having been so cautioned you remain curious, feel free to explore.

History

Around 1996 or 1997 as the web was starting to really take off, I ran into a problem that everyone has now, which is that it's hard to keep track of all the passwords you need to sign in to your various accounts. Today there are lots of good tools to help with this, but at that time -- assuming you didn't just reuse the same password for everything like your parents -- the state of the art was a post-it note or a sheet of paper.

Somewhere on Usenet I stumbled across the idea of a "password generator", the idea that you'd take the name of each site you wanted to sign into and combine it with one really good-quality secret passphrase to get a password for that specific site. The idea appealed to me, because it meant you could keep all the non-secret details like the site address, your username, etc., in a plain text file, and even if someone stole that file they wouldn't get your passwords.

I wrote a simple version of this in Perl (boringly named pwgen), which at first just consisted of concatenating the site address with the secret passphrase and hashing it. Schematically:

SHA1("www.example.com" || "my-excellent-secret-passphrase")

To turn that into a password, I'd take some bytes off the hash and use them to index into the English alphabet. SHA1 has twenty bytes to work with, which is plenty given that a lot of sites wouldn't even accept passwords longer than 8 characters.

The configuration file was a plain CSV text file with one line per site, having a short label and the site name, e.g.,

bank,www.bank.com
blog,myblog.wordpress.com

A year or two later, I rewrote pwgen in Python as a learning project. By that point I'd already run into some complications:

  1. You need a way to change your password. Sites were forever making you do this, supposedly as a security measure.

  2. Sites have different rules about password length and composition. Some sites required at least one digit, others required a combination of digits and punctuation, etc.

And of course, many sites only accepted certain punctuation, thanks to poor string escaping hygiene in popular web frameworks.

So as part of the rewrite, I made the algorithm a bit more complicated: Instead of plain SHA1, I used the HMAC construction (mostly because I'd read about it and thought it sounded neat), and added in a "salt" value so you could get a new password without changing the site name. Roughly:

HMAC(SHA1, "my-excellent-secret-passphrase", "www.example.com" || "/" || "salt")

The text file expanded a bit to make room for a password length (so I could change the default for picky sites) and rules about the password shape (letters, digits, punctuation). By this point, some sites had gotten so picky that I included a rudimentary "format" setting, which would specify a template for the password, with placeholders for the various character types. For example, if a site required at least two digits not at the end, you could say:

****#*#*

which means "four letters, a digit, a letter, a digit, and a letter". I soon had to add more placeholders for punctuation, since barely any two sites could agree on which symbols were safe enough for commerce.

That version remained largely unchanged for several years, until late 2011 when I started learning Go for a work project, and decided I would implement pwgen yet again as a learning project. I decided to rename it keyfish, because I had this little fish icon I'd bought from an artist for my blog, and I wanted to do something with it. The Go version came with several more changes:

  • I switched the algorithm to HMAC/SHA256.

  • I converted the config file from plain text to JSON, so it would be easier to add defaults, flags, etc.

  • I built a parallel implementation in JavaScript as a Chrome extension, using the same config file format, so that I could use it from the browser.

This repository is the slow evolution of that initial Go implementation. Over the years since 2012, I've made various small-to-medium changes:

  • After several rounds of fighting with Chrome's increasingly Draconian policies about extensions, I gave up on the extension and implemented a web app as a separate server.

  • I added more and more metadata to the config file, to keep track of all the nonsense you need to log into things: Security questions and their answers, confirmation PINs, which e-mail address I'd used to sign up, and so on.

  • I added the ability to store TOTP keys and to have the tool (and its web app) generate TOTP codes so I wouldn't have to screw around with the authenticator app so much.

Unfortunately, some of these changes meant adding a lot more sensitive data to the configuration file. Whereas the original file was pretty much just a list of website names and some password rules, now it contains things that you really don't want lying around in plaintext.

Over the intervening years, a bunch of tools came out to manage passwords. I'd tried several of them, but kept coming back to my old familiar thing, despite its deficiencies. The ability to bake the whole thing into a single static command-line binary was really useful.

Finally, though, I decided it was past time to switch to a properly-encrypted storage format. To avoid having to change all my passwords all at once, I kept the same HMAC-based password generation scheme as a default option. Now, though, the configuration data are encrypted with an AEAD on ChaCha20-Poly1305 using a storage format inspired by the one my teammates designed for the setec tool we built together.

With an encrypted config file, it's no longer necessary to keep track of key generation salts and such; when I have to change a password I can just generate a new one at random and store it in the file. This also means I can safely keep security questions, access PINs, and so on in there.

The config data are still JSON (prior to encryption), but I cleaned up and simplified the format a bit. I wrote a tool to translate the old format into the new one, and wrote some library code to make it easier to work with.

Moreover, I also reworked the old web app quite a bit. Although I'd made some refinements over the years, it wasn't very well-structured, so I took the opportunity to make it at least a little bit less 90's vintage. It's still not going to win any design awards, but at least it's a little cleaner. I took advantage of the htmx library to make the plumbing a little nicer.

Hopefully this will hold me for a few more years. Come back in another decade, and we'll see what's become of it all.

Usage Outline

  1. Create a new empty database.

    % kf db create example.db
    New database passphrase: ........
    Confirm new database passphrase: ........
    Created database "example.db"
    
  2. (Optional) Set the database location in the environment:

    export KEYFISH_DB=$PWD/example.db
    
  3. Add a record:

    % kf record add -edit email
    Passphrase: ........
    

    The editor will run to edit the record in YAML format. For this example:

    label: email
    title: Personal email account
    hosts: mail.example.com
    username: aloysius
    

    Save and exit the editor, then:

    ▷ Keep changes? (y/n) y
    <saved>
    Created new record "email"
    
  4. Set the password on a record:

    $ kf random -n 20 -set email
    Passphrase: ........
    Setting password on record "email"
    <saved>
    JfYN2JpcVP70Se2VMXxW
    

    Your output will be different, as the password is generated randomly. Use --copy if you want to copy the password to the clipboard instead of printing it. When you do this, it will print a human-readable confirmation nonce instead, e.g.,

    % kf random -n 20 -set email -copy
    Passphrase:
    Setting password on record "email"
    <saved>
    ovary-heath-waist-zebra
    
  5. Copy the password for a record:

    % kf copy email
    Passphrase: .........
    ovary-heath-waist-zebra
    
  6. Run a local web app to access the database from a browser:

    % kf web -addr localhost:8422
    Passphrase: ........
    2024/05/03 12:24:58 Serving at "localhost:8422"
    2024/05/03 12:24:58 Watching for updates at "/home/aloysius/example.db"
    

    Visit http://localhost:8422/ in a browser to use the app.

    If you want to access it from anywhere but localhost you will need to set up access control separately. I use tailscale serve to expose mine to just the computers on my home tailnet, e.g.,

    % tailscale serve --bg --https 8422 localhost:8422
    Available within your tailnet:
    
    https://example.tail1234.ts.net:8422/
    |-- proxy http://127.0.0.1:8422
    
    Serve started and running in the background.
    To disable the proxy, run: tailscale serve --https=8422 off
    

Directories

Path Synopsis
Package clipboard provides basic access to the system clipboard.
Package clipboard provides basic access to the system clipboard.
cmd
kf
Program kf is a keyfish command-line tool.
Program kf is a keyfish command-line tool.
kf/config
Package config contains shared configuration settings for kf subcommands.
Package config contains shared configuration settings for kf subcommands.
kf/internal/cmdweb
Package cmdweb implements the "web" subcommand.
Package cmdweb implements the "web" subcommand.
Package kfdb implements a database of sensitive values maintained by keyfish.
Package kfdb implements a database of sensitive values maintained by keyfish.
Package kflib is a support library for the KeyFish tool.
Package kflib is a support library for the KeyFish tool.
Package kfstore provides a self-contained encrypted data store for sensitive data managed by keyfish.
Package kfstore provides a self-contained encrypted data store for sensitive data managed by keyfish.
Package wordhash generates human-readable non-cryptographic digests to use for visual confirmation.
Package wordhash generates human-readable non-cryptographic digests to use for visual confirmation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL