More and more frequently, when I ask friends and family (people with a mainly non-computing background) how they manage their passwords their eyes cloud over, and I then feel the need to tell them that they ought to apply good password hygiene. (I tend to mensplain a bit.) As such I’ve been looking much more deeply into KeePassXC as a multi-platform, Open Source, and very decent password manager.

I ran away from 1Password many years ago when, IIRC, forced cloud upon their users and also converted to a subscription model and settled for EnPass at the time. Aside from a number of UI quirks in EnPass I’ve been happy enough with it, and I got it at the time when they had a purchase model; I believe that has meanwhile also changed to a subscription model. I want to be able to recommend a program which has a fixed price (Open Source is fine) and a UI which will hopefully remain somewhat consistent. I think KeePassXC matches the requirement.

These notes are intended as a reminder to myself of the features and possibilities I discovered in KeePassXC. (Start with some screenshots if you like.)

the database

KeePassXC databases (*.kdbx; file format explained) are protected with either a password or a key file or both. The desktop app and the CLI program can optionally create these key files, and they contain 128 byte of random data which is used to augment the password. These key files can also be an image, a love letter — any file which doesn’t change. Think of it as a really complicated password that is read from a file, so you don’t have to remember or type it into your master password field.

I would likely suggest a key file created with random data and have a backup of the key file printed on paper (using a font with which I can easily differentiate zero and oh and one and ell …):

$ keyfile=kp.key
$ dd if=/dev/urandom bs=128 count=1 status=none of=$keyfile

$ openssl dgst -sha1 $keyfile
SHA1(kp.key)= f4e8b1dca0f2833d0596ba60664999fc0ca99a09

$ openssl enc -base64 -in $keyfile

$ openssl enc -A -base64 -in $keyfile |
       qrencode -l Q -o $keyfile.png

QR-code of base64 of the key file

In order to recover the binary key file I could scan the QR code, copy the resulting text (or even enter it manually from the base64 representation if necessary), and decode the base64 back into the key file’s data with

$ openssl enc -d -A -base64 -in /tmp/paper -out kp-new.key

$ openssl dgst -sha1 kp-new.key
SHA1(kp-new.key)= f4e8b1dca0f2833d0596ba60664999fc0ca99a09

KeePassXC databases can be synchronized via, say, syncthing, Dropbox, or a file share, etc., but the key files ought to be kept separately. Key files are also supported by KeePassium on iOS and possibly also by other apps such KeePassDX and KeePass2Android on Android, and Strongbox on iOS.

Hardware key chooser when opening database

In addition to password and/or key file, the desktop apps can use a supported Yubikey (I chose a 5c nano) with HMAC-SHA1 to add additional entropy to the chosen password. While this works very well it has the disadvantage of not being supported by the mobile apps I looked at, meaning it would be a desktop-only feature. Also, it’s important to have a backup Yubikey (5c NFC here) for the feature; I wouldn’t want a lost/broken Yubikey to lock me out of the database!

Sadly, KeePassXC relies on external file synchronization, which might not be trivial to set up. As Alexander notes, the original Keepass2 (and Keepass2Android) are able to open database files directly from a WebDAV URL, and merge changes that have been made from a different device, but KeePassXC is likely the more modern choice with more features.

Python module

The Python pykeepass module interacts with KeePass databases (supports KDBX3 and KDBX4), and as such also works with KeePassXC. I can create a database (this is how I created the jane.kdbx database for the examples on this page), create and find entries, add entries, change / add passwords and entry details, etc.

#!/usr/bin/env python3

from pykeepass import PyKeePass, create_database
import secrets
from xkcdpass import xkcd_password as xp

kp = create_database("jane.kdbx", password="tt", keyfile="kp.key", transformed_key=None)

g_work = kp.add_group(kp.root_group, "Work")
g_play = kp.add_group(kp.root_group, "Play")
g_social = kp.add_group(g_play, "socialmedia")

wordfile = xp.locate_wordfile()
mywords = xp.generate_wordlist(wordfile=wordfile, min_length=5, max_length=8)

password = xp.generate_xkcdpassword(mywords, acrostic="tonic", delimiter="-")

entry = kp.add_entry(g_work, "gmail", "myusername", password)
print(entry)  # Entry: "email/gmail (myusername)"

e = kp.add_entry(g_social, "Mastodon", "janej", secrets.token_urlsafe(32))

e.url = ""
e.tags = [ "fediverse", "mastodon" ]
e.notes = "account created in 2018 with 2FA"

emails = [ "", "" ]
e.set_custom_property("mail", "\n".join(emails))    # custom property expects newline-separated
e.set_custom_property("uid", "12345678")            # pykeepass > 4.0.3 will have: protect=True

# there doesn't appear to be a way of exiting cleanly without the .save()


keepassxc-cli is a command-line tool for KeePassXC from which I can manipulate its databases.

$ kpc open -k tt.key jane.kdbx
Enter password to unlock jane.kdbx:
Passwords> help

Available commands:
add                 Add a new entry to a database.
analyze             Analyze passwords for weaknesses and problems.
attachment-export   Export an attachment of an entry.
attachment-import   Imports an attachment to an entry.
attachment-rm       Remove an attachment of an entry.
clip                Copy an entry's attribute to the clipboard.
close               Close the currently opened database.
db-create           Create a new database.
db-edit             Edit a database.
db-info             Show a database's information.
diceware            Generate a new random diceware passphrase.
edit                Edit an entry.
estimate            Estimate the entropy of a password.
exit                Exit interactive mode.
generate            Generate a new random password.
help                Display command help.
ls                  List database entries.
merge               Merge two databases.
mkdir               Adds a new group to a database.
mv                  Moves an entry to a new group.
open                Open a database.
quit                Exit interactive mode.
rm                  Remove an entry from the database.
rmdir               Removes a group from a database.
search              Find entries quickly.
show                Show an entry's information.
Passwords> generate

Using the --yubikey option, I can also unlock a Yubikey-protected database from the command-line:

$ kpc ls other.kdbx -y 2
Enter password to unlock other.kdbx:
Please present or touch your YubiKey to continue.

In the example which follows, I attach an image to the database and then display all details (also the protected fields) of an entry.

$ alias kpc=/Applications/
$ kpc attachment-import -k kp.key jane.kdbx Mastodon mascot mastodon-mascot.jpg
Enter password to unlock jane.kdbx:
Successfully imported attachment mastodon-mascot.jpg as mascot to entry Mastodon.

$ kpc show jane.kdbx -k kp.key --show-protected --show-attachments Mastodon
Enter password to unlock jane.kdbx:
Title: Mastodon
UserName: janej
Password: REH1I0xz_iEM2VYvhiwfah5Rt1RROxqErmejlaoKY6A
Notes: account created in 2018 with 2FA
Uuid: {95275776-9a50-11ed-add7-f01898ef9fe7}
Tags: fediverse,mastodon

  mascot (3.0 KiB)

It’s not actually documented anywhere that I could find, but keepassxc-cli actually reads passwords from stdin. (tt is the database password and the dice ware subcommand creates four words as in ”subpar amusement crayfish footrest”.)

$ (echo tt; kpc diceware -W 4) | kpc edit -k tt.key jane.kdbx -p gmail
Enter password to unlock jane.kdbx:
Enter new password for entry:
Successfully edited entry gmail.

I could add -q to the command to completely silence prompts for database and entry’s new password.

the UI

screenshot of KeePassXC with the programmatically-created entry shown

  1. favicon downloaded from within the entry (add URL, hit download), but there is a menu for downloading URLs automatically, not possible here b/c it’s a fake address
  2. I didn’t understand the color square, but it’s a password-quality indicator
  3. Additional attributes. In future the Python module will be able to add protection (such as shown in the comment for 4.)
  4. Attachment names
  5. Group folders as created within Python

SSH agent

KeePassXC implements support for an SSH-agent, and I find it works very well. What I particularly appreciate is the possibility to override the agent socket path as I have a bit of a convoluted setup here which sets a specific path on login.

KeePassXC’s implementation can add SSH keys when unlocking a database, it can automatically remove keys from the agent after a selectable time, and it can optionally remove all keys it’s added when the database is locked (i.e. closed). Note there’s a setting with KeePassXC which can optionally ask for confirmation before using a key, but that requires the separate SSH ask-pass utility — this is not something that KeePassXC can implement as there’s no feedback from the agent itself.

There’s a very good writeup of how to manage SSH keys with KeePassXC.


KeePassXC has built-in support for Time-based One-Time Passwords (TOTP). These are passwords which use the current time as a source of uniqueness. I prefer to use an app which does TOTP than to use SMS for two-factor authentication (2FA).

I have some doubts about the security of having TOTP within the password manager (I use Authy and not the support built-in to EnPass), so I asked on Mastodon:

Is there a consensus on whether it is better/safer to have TOTP generation done within the password safe (e.g. KeePassXC) or rather externally using a separate program (e.g. Authy)?

I think I’m summarizing correctly when I quote Thomas, who responded:

It’s better to have it on a separate device. But it’s also better to have it in any place than to not have it at all.

That’s probably very good advice.

Further reading

passwords :: 22 Jan 2023 :: e-mail

My original plan for 2022 was to work a bit less, but I failed for the simple reason that I forgot to mark “free” time in the calendar. Stupid. Be that as it may, it was mostly quite a good year for me, with a definite non-work-highlight being a three-week holiday with my offspring in the Spring.

I did quite a bit of DNS work and trainings, and gave a few Ansible trainings, and in between I wrote the odd blogticle:

This year marked my tenth Ansible anniversary, and we created an Ansible reference sheet. I learned about using a lookup plugin for Ansible module_defaults, and jotted down some notes on Ansible local facts on Windows nodes. As you might know, I’m a fan of local facts, and we began collecting ideas for using local facts.

On the DNS side, the pièce de résistance was writing about DNSSEC signing with an offline KSK, and because I get the question occasionally, about DNSSEC with NLnetLabs’ LDNS and NSD.

We also looked at a bit of history in DNSSEC “key tag” or “key ID”?, and for good measure I made a fool of myself in Red means Kaputt: when DNSSEC turns into a treasure hunt.

I’m looking forward to what 2023 has to offer. We’ll see. Wir werden sehen. Veremos. On verra bien.

dns, dnssec, ansible, and blog :: 23 Dec 2022 :: e-mail

One of the most useful DNS and DNSSEC debugging utilities I am aware of is DNSViz – a tool for visualizing the status of a DNS zone:

it provides a visual analysis of the DNSSEC authentication chain for a domain name and its resolution path in the DNS namespace, and it lists configuration errors detected by the tool

It has enabled me to to “see” issues with domains which are otherwise very difficult to determine, and it is a site I strongly recommend in DNS/DNSSEC courses.

a small portion of a domain visualization

DNSViz presents a domain on a Web page and I can hover over individual elements to see details about them, as the example above demonstrates. (See the full output here.) Domains are typically visualized from the root down to the domain I wish to test. DNSViz keeps a history (which was unavailable for a long time) so I can “walk back” in time looking at previous analyses.

DNSViz is also exciting to use on your own DNSSEC-signed domains, and note that this excitement extends to a possible requirement for smelling salts: will the page display portions in red (i.e. bogus or kaputt)?

That was not a joke. :-)

I wanted to try DNSViz in a self-hosted environment, as the software is open source, and opted for the easiest mechanism: a docker image.

I also cloned the dnsviz/dnsviz repository as I later discovered the program can produce the “HTML format”, i.e. exactly the view we see on at DNSViz, and I copied the required CSS and JS files into their own directory:

$ ls -1 web/

Then I assembled the commands (probe, graph) needed to produce the PNG, and HTML (SVG is also possible), and massaged the HTML to use the assets from the web/ directory:

#!/usr/bin/env bash

docker run --network host -v "$PWD:/data:rw" dnsviz/dnsviz \
	probe -A -a . --nsid --pretty-output -o $z.json $z

docker run -v "$PWD:/data:rw" dnsviz/dnsviz \
	graph -r $z.json -T png -O

docker run -v "$PWD:/data:rw" dnsviz/dnsviz \
	graph -r $z.json -T html -O --rr-types SOA,NS

sed -I "" -e 's,file:///usr/share/dnsviz/css,web,' \
	  -e 's,file:///usr/share/dnsviz/js,web,' $z.html

And I have the program generate the visualization:

$ ./
Analyzing .
Analyzing net

The resulting PNG and HTML differ here, because for the HTML I’ve limited the RR types to SOA and NS, but otherwise they contain the same information. The probe phase produces a JSON file containing serialized responses to queries for the specified domain.

I can specify my own resolvers, ask the program to query authoritative servers only, and use the “print” subcommand to assess specified domain names based on the content in the JSON file. If need be, I can also use an alternate trust anchor, permitting the tool to be used in private roots as well.

I thought interesting what Guillaume-Jean Herbiet mentioned to me: they use a self-hosted version of DNSViz to test the semantic validity of signed zones before publication.

I then install the program so that I no longer need docker.

$ brew install dnsviz graphviz

Peter DeVries points out that the tool also has a “query” command which gives textual results which are relatively easy to understand. In the following example I query for a domain and we see then chain of trust from the root (.) down through (net) to the zone I’m interested in:

$ dnsviz query
. [.]
  [.]  DNSKEY: 8/951/256 [.], 8/18733/256 [.], 8/20326/257 [.]
  [.]    RRSIG: ./Algorithm.RSASHA256/20326 (2022-12-20 - 2023-01-10) [.]
net [.] [.]
  [.]  DS: 8/35886/2 [.]
  [.]    RRSIG: ./Algorithm.RSASHA256/18733 (2022-12-22 - 2023-01-04) [.]
  [.]  DNSKEY: 8/57635/256 [.], 8/35886/257 [.]
  [.]    RRSIG: net/Algorithm.RSASHA256/35886 (2022-12-20 - 2023-01-04) [.] [.] [.]
  [.]  DS: 13/37440/2 [.]
  [.]    RRSIG: net/Algorithm.RSASHA256/57635 (2022-12-19 - 2022-12-26) [.]
  [.]  DNSKEY: 13/17125/256 [.], 13/37440/257 [.]
  [.]    RRSIG: (2022-12-15 - 2023-01-14) [.]
  [.]  A:
  [.]    RRSIG: (2022-12-10 - 2023-01-09) [.]

Did I mention DNSViz is a brilliant tool?

dnssec :: 22 Dec 2022 :: e-mail

I toyed with a version of GoToSocial a few weeks ago, but as a much more stable version 0.6.0 was released a few days ago, I decided to install GoToSocial and attempt to use it productively for posting DNS tidbits as Who knows: I might integrate it with

Why GoToSocial and not Mastodon? The latter scares me: it has too many moving parts for my taste, and the former is one of these lovely single Go binaries – sufficient reason to chose it in spite of the project’s warning:

GoToSocial is still ALPHA SOFTWARE. It is already deployable and useable, and it federates cleanly with many other Fediverse servers (not yet all). However, many things are not yet implemented, and there are plenty of bugs!

System requirements and installation are described well. This time around I selected to use a PostgreSQL back-end instead of SQLite3 thinking the combination might have more testing.

I am also not yet fronting the installation with a reverse proxy: as can be seen by my configuration, GTS listens on port 443 (thanks to CAP_NET_BIND_SERVICE) and has automatically obtained a certificate via Let’s Encrypt – so far so good.

GTS post as seen on Mastodon

GTS doesn’t provide its own Web client, but it does implement the Mastodon client API, so I use the Web client called Pinafore which is simple but sufficient. On mobile, Toot! works well with GTS, and I got the Python toot client logged in so I can toot from the command-line.

$ toot post "Do any of you run DNSviz locally, for monitoring, say? ..."

GoToSocial implements a rudimentary settings page for the user (at /settings/user/profile) where I can upload an avatar, set a name and a bio, and toggle follow requests approval and whether or not I want to enable an RSS feed of public posts (

All the bits and pieces I think I want for this “application” are there.

After 24 hours and a dozen followers I see this resource consumption:

$ free
               total        used        free      shared  buff/cache   available
Mem:         2030444      394272      239944       23616     1396228     1447012
Swap:        1048572         780     1047792

$ du -sh storage/
154M    storage/

The latter is a directory which contains full-sized and small avatars of people who have seen posts by this instance:

gts=# SELECT COUNT(*) FROM accounts;

Color me surprised; I still have a lot to learn about federation.

The server / admin utility can also export data from my GTS instance into a file for backup / storage, but it does not export statuses, media, bookmarks, and tokens (documentation).

$ ./gotosocial --config-path config.yaml admin export --path p
$ jq -r .type < p | sort -u


This is a list of things I found “missing” in GoToSocial, but I could imagine they’re still on the “toto” list:

  • Bookmarks. Upon setting a bookmark the client received a 404:
    Dec 13 23:32:25 gotosocial[4650]: timestamp="13/12/2022 23:32:25.045" func=router.loggingMiddleware.func1 level=INFO latency=3.435733ms clientIP=xxxx userAgent="Toot!/139 CFNetwork/1335.0.3 Darwin/21.6.0" method=POST statusCode=404 path=/api/v1/statuses/01GM3RE3WGSW0RYSF21GF2TF9F/bookmark msg="Not Found: wrote 883B"
fediverse :: 10 Dec 2022 :: e-mail

RFC 4034 defines key tag as an identifier with which a DNSKEY RR containing the public key that a validator can use to verify the signature, but over time I have used the terms key tag and key ID interchangeably, without really knowing where they came from.

small poll on the Fediverse

In a small poll on the Fediverse, done mostly jokingly, 35% of those who understood the question answered key ID, and the next morning a question arose:

How come “key tag” didn’t win? Isn’t that the terminology from the RFCs?

It is, but it’s complicated, as section 5.4 shows:

section 5.4 uses both keytag and keyid

I didn’t know the answer. The BIND source code contains roughly 150 mentions of “key tag” and 500 references to “key ID” (with and without a space, and case-insensitive).

I ask Evan, who knows the answer (of course):

the text formatting of DNSKEY records in BIND is based on earlier code that was written for KEY records, five years before 4034. The “key id” string came from there. (Commit 0e93f65e103c, if you’re interested.)

I suspect it was a shortening of “key identifier”, which is used in RFC 2535 in the example SIG records - though, interestingly, that also uses “key tag” in the text.

I don’t know if it was deliberate or an oversight to leave it unchanged for DNSKEY.

Yet another bit of history clarified. Like the semicolon in zone master files.

dnssec :: 18 Nov 2022 :: e-mail

Other recent entries