Export password store passwords with babashka (clojure shell scripting)

- 26 Dec, 2020

Password management using pass

For a long time I’ve used pass for password management. I still believe it’s a good tool however my workflow changed and I needed suited for it. pass helped me keep unique passwords for a lot of services and tools. I loved the clipboard integration and it’s use from the command line (I use the terminal). However I did find it difficult to use in the browser and on other systems - like my mobile phone.

Another thing that bothered me was lack of One Time Password and TOTP integration.

That is why I searched for a replacement tool. And that is why now I’m migrating away from pass.

The replacement: KeePassXC

I’m using KeePassXC for some time now. While it does not fit perfectly into my workflow it does look more promising:

  • It has OTP/TOTP support

  • A single file database that you can copy around (that might be a good or a bad thing)

  • A way to share passwords with multiple devices including the browser and the phone

  • Ability to use hardware tokens to unlock the database

What I do miss right now is:

  • An easier way to use passwords in CLI apps. I like the way pass works: pass path/to/pw-entry, it would be nice to have this flow in keepassxc-cli.

  • An easy way to store files - to be able to share a secret database inside a team via git projects.

The migration

To make the migration I needed a way to export password data from pass and import it into KeePassXC. KeePassXC supports Importing CSV files so this was the way to go. All I needed was a way to convert the gpg encrypted files into a CSV and import that.

The migration process looks like this:

  • Export passwords from pass into a CSV file

  • Import them into KeePassXC

Exporting passwords from pass

To export the passwords I chose to write a script in babashka. For those of you who don’t know, babashka is a tool to write using scripts in Clojure. It also has a very cool logo .

For me it is a way to avoid writing bash scripts, because I find bash to be less fun to write and also because I wanted to test out babashka.

The full source code is bellow and you can copy and paste it locally to use it.

Full source code for pass export script using babashka
#!/usr/bin/env bb
(ns script
  (:require [clojure.java.io :as io]
            [clojure.string :as str]
            [clojure.pprint :as pp]
            [clojure.edn :as edn]
            [cheshire.core :as json]
            [clojure.data.csv :as csv]
            [clojure.java.shell :refer [sh]]))
;; Iterez peste directoare / fișiere
;; pass pe fiecare
;; Creez un csv pentru KeePass
;; https://github.com/borkdude/babashka
;; https://book.babashka.org/#style
;; https://book.babashka.org/#child_processes
;; http://clojure-doc.org/articles/cookbooks/files_and_directories.html
;; https://clojuredocs.org/clojure.core/file-seq
;; https://github.com/borkdude/babashka/blob/master/doc/projects.md
;; https://keepassxc.org/docs/KeePassXC_UserGuide.html#_importing_csv_file

(def home (System/getProperty "user.home"))
(def pstore-dir (str home "/.password-store/"))

(defn process-password-store [pstore-dir]
  (let [files (file-seq (io/file pstore-dir))
        gpg-filter (fn gpg-filter [f] (.contains (.toString f) ".gpg"))
        gpg-files (filter gpg-filter files)]
    ;; process all files - read the contents and produce a list of entries
    (for [f gpg-files
          :let [f-str (.toString f)
                pass-arg (-> f-str
                             (.substring (.length pstore-dir))
                             (.replace ".gpg" ""))
                data (sh "/usr/bin/pass" pass-arg)
                lines (str/split-lines (:out data))
                pw (first lines)
                other (rest lines)]]
      {:file f-str
       :data data
       :out (:out data)
       :pass-arg pass-arg
       :password pw
       :other other})))

(defn pwstore-edn->csv-table [elements]
  (let [convert (fn convert [e] (let [{:keys [password pass-arg other]} e
                                      notes (str/join "||" other)]
                                  ["pass" password pass-arg notes]))]
    (map convert elements)))

(println "Processing files in " pstore-dir)

(def processed (process-password-store pstore-dir))

(spit "password-store-export.edn" (with-out-str (pp/pprint processed)))
(spit "password-store-export.json" (with-out-str (json/generate-string processed)))

(println "Done extracting files. Convert edn file to csv.")

(def csv-data (pwstore-edn->csv-table (edn/read-string (slurp "password-store-export.edn"))))

(with-open [writer (io/writer "password-store-export.csv")]
  (csv/write-csv writer csv-data :quote? true))

Save it into a file passwordstore-export.sh, make it executable and run it.

Running the export
  chmod +x passwordstore-export.sh

The file contains two functions and produces 3 files: an edn, a csv and a json file with mostly the same data.

The function process-password-store iterates over all the files in the user’s .password-store/ directory calling pass for each file. This function produces a list of maps, one for each file with all the data.

The function pwstore-edn→csv-table takes the data produced by the first function and produces a list of lists, suitable for CSV export.

The data is written in each file format at the end.

Importing the data in KeePassXC was pretty trivial - I just followed the online guide.

Developing the script was pleasant and I will use babashka for more scripts.

I recommend you give it a try yourself.

Tags: keepassxc clojure pass password management export csv babashka

Tag cloud

plain text accounting timeclock documentation parser sysadmin cryogen grammar keepassxc gnome authentication clojure tutorial pass oauth2 password management export hledger oauth2-proxy free software csv website static website kubernetes lexer open-source snippets dex gnome extension babashka k3s contribution time logging FreeIPA Raspberry PI LDAP deps.edn giving back OpenFaaS Antlr self-hosting serverless cryogen-web live-reload open source