in #code ~ read.

Migrating from Enpass to KeePass

TL;DR; Here's the link to a python script that converts an Enpass CSV export into a format that can be imported by KeePassXC: https://github.com/jsphpl/enpass-to-keepass


Enpass is actually great software. I've been recommending it to lots of people for the following reasons:

  • clean and simple user interface
  • fast search in large databases
  • decent (though sometimes slightly laggy) browser extensions
  • great support for different operating systems
  • great features (storing different types of data; TOTP)
  • fair pricing model

Today, i'm banning google from my Android phone and thus had to realize that Enpass' Pro version strictly requires Google Play Services. Bummer!

Sadly, i have to say goodbye to a good piece of software. But it's not even that simple…

Export Format

Enpass offers two export formats: csv and plain text. The latter format seems to be intended for humans, while the former can be halfheartedly read by machines. Why halfhearted? Because it's not typical tabular data with fixed columns, but rather stores an arbitrary number of columns with rows that don't necessarily correspond to each other. This is understandable, as an single record in Enpass can contain an arbitrary number of fields, which the user can give any name they like. Thus, CSV is not really the best format to represent that data, but it still works.

So how do they do it? The first column always contains the title of the entry, while the last column may contain the notes field (if the entry contained notes). All other fields of the original entry are represented by two csv columns, where columns with even numbers contain a field's name and the subsequent odd-numbered column contains the field's value. So an export can look something like this:

"Title","Field","Value","Field","Value",.........,"Note"
"google.com","username","some.user@gmail.com","password","super_secret","url","https://accounts.google.com","Note: Never use this – it's eeeeevil ;)"
"facebook.com","password","unguessable","username","some.user@gmail.com","url","https://facebook.com/login","Note: Also evil"
"twitter.com","password","secret123","username","some.user@gmail.com","url","https://twitter.com","Note: Almost as evil"

Or, rendered as a table:

Tabular display of an Enpass sample export

As you can see, the username of one record can be in the same column as the password (or any other field) of another record. Furthermore, each row can have a different number of columns. Thus, we cannot interpret this file as typical CSV data. The header row of the above table, which stem from an actual Enpass csv export file, demonstrates this: The titles do not necessarily correspond to the values below them, and they do not contain the names of the data fields available in Enpass.

Translating it for KeePass

KeePassXC allows us to import rather typical CSV files, making the assumption that each column represents the same field across all rows. So we have to fit our Enpass export into that scheme. I'm going to write a script to automate that process. That will not only make the task manageable for me in the first place (having 662 entries in my Enpass db), but also help other people who want to migrate from Enpass to KeePass, too.

These are the settings we can adjust, when importing a CSV file through KeePassXC:

Screenshot of KeePass CSV import view

We're offered the following fields:

  • Group
  • Title
  • Username
  • Password
  • URL
  • Notes
  • Last Modified
  • Created

Group, Last Modified, and Created are not contained in the Enpass export file at all. Also, there is no way to have multiple URLs, Usernames, or Passwords in KeePass. While that's unlikely in Enpass, too, there is no guarantee that it won't happen.

So we have to map the fields from Enpass to KeePass in a way that cannot be reversed unabmiguously:

Table that maps Enpass fields to KeePass

Everything else should not just be dumped into the notes field, but rather stick to the following format: {field name}: {field value}\n. While this format still won't allow KeePass to understand the contents of the notes field, it still makes it easier for us to find the data we want.

The strategy for the script is roughly going to look like the following:

  1. Parse the Enpass export file as a regular CSV file (UTF-8)
  2. Ignore the first row (headings)
  3. Loop over the data rows
  4. For each row, create an object
  5. Loop over the row's columns
  6. First column will be the object's title
  7. If the total number of columns is even, store the last column's value to the notes field on the object
  8. Group the remaining columns into 2-tuples
  9. For each tuple: the first element will be the key of the field, the second its value. If the key already exists on the object, append the tuple to the notes field according to the template described above. Else, set object[key] = value.
  10. Dump all objects out as a regular csv file

Looks like we're ready to write our translator script.

Translation script

Is hosted on github: GitHub - jsphpl/enpass-to-keepass: Convert an Enpass csv export so it can be imported to a KeePass database using KeePassXC

Binary Blobs

Enpass can store files in its database. Unfortunately those are not included in the text-only exports. There is no way to export all files stored in Enpass. What's the solution? I don't see any, apart from checking each single entry whether it contains a file attachment. Tedious task in a database containing more than 600 entries…

Final Note: Backup!

As a final note, i would recommend you to back up and keep a copy of the original Enpass database (.walletx file). Just in case you forgot something and realize it only a year later. Then you could still reinstall Enpass and open the database backup. Keep in mind that you will need your master password in order to do so!