README.org
February 28, 2025 · View on GitHub
- passage.el: age encryption support for the standard unix password manager
#+html:

passage.el builds on top of [[https://github.com/anticomputer/age.el][age.el]] to provide Emacs support for [[https://github.com/FiloSottile/passage][passage]] which is an Age encryption based port of [[https://www.passwordstore.org/][pass]], the standard unix password manager.
- Usage
Put the passage.el project in your load-path and:
#+begin_src emacs-lisp (require 'passage) #+end_src
You can now interact with your [[https://github.com/FiloSottile/passage][passage]] based local encryption store. Please see the [[https://github.com/FiloSottile/passage/blob/main/README][passage README]] for instructions on how to migrate from the gpg based pass utility to the age based passage utility.
passage.el assumes your passage store is located at =~/.passage/store= but you may customize this through the =auth-source-passage-filename= variable.
- Tips and Tricks
** Using passage store secrets in your elisp code
passage.el includes an =auth-source= back-end for passage, which means you can programmatically fetch passwords out of your passage store with =auth-source-passage-get=, e.g.:
#+begin_src emacs-lisp (auth-source-passage-get 'secret "store/secret") #+end_src
** Pass to passage migration
#+begin_src bash #! /usr/bin/env bash set -eou pipefail cd "{PASSWORD_STORE_DIR:-HOME/.password-store}" while read -r -d "" passfile; do name="{passfile#./}"; name="{name%.gpg}" [[ -f "{PASSAGE_DIR:-HOME/.passage/store}/name.age" ]] && continue pass "name" | passage insert -m "name" || { passage rm "name"; break; } done < <(find . -path '/.git' -prune -o -iname '.gpg' -print0) #+end_src
** Pinentry support
For pinentry support I recommend you use [[https://github.com/str4d/rage/][rage]] as your age encryption utility.
** Specifying alternate passage identities and recipients across systems
If your emacs configuration moves around a variety of systems that all share the same passage store, but you do not want to keep the same key material on all those systems, you can encrypt to a series of recipients via newline seperated public keys at =~/.passage/store/.age-recipients=
If you'd like to specify a specific identity (private key) for passage.el to use on password show operations, you can use the normal environment variables recognized by passage for this in your configuration, e.g.:
#+begin_src emacs-lisp (setenv "PASSAGE_IDENTITIES_FILE" (expand-file-name "~/path/to/identity")) #+end_src
This is useful to deconflict e.g. =age-plugin-yubikey= managed identities contained in the default passage identity file at =~/.passage/identities= for which the yubikeys may not be plugged into the system you are currently working with.
Note that for file open operations, you'll want to use [[https://github.com/anticomputer/age.el][age.el]] in conjuction with passage.el, which has its own identity and recipient configuration settings. For example, on one of my virtual machines, I use an alternate identity, who's recipient is included in the passage.el and age.el configurations on all my system, and specify to only use this identity for age.el and passage.el, respectively, on the virtual machine using the following configuration:
#+begin_src emacs-lisp (setq age-default-identity (expand-file-name "~/.ssh/age_yubikey_vm")) (setenv "PASSAGE_IDENTITIES_FILE" age-default-identity) #+end_src
As of https://github.com/anticomputer/passage.el/pull/4 passage.el will
automagically set age-default-identity and age-default-recipient from
PASSAGE_IDENTITIES_FILE, PASSAGE_RECIPIENTS_FILE or PASSAGE_RECIPIENTS
environment variables for passage.el based find-file operations. When
you open a file from the passage store any existing passage environment
configuration will supercede your Emacs-wide age.el configuration. This
simplifies your use of passage.el without needing to manually deconflict
or align age.el configurations with your passage configuration.
It is important to understand that passage is its own tool with its own configuration. It only, and ONLY, depends on age.el for operating on its associated Age files inside Emacs buffers.
This behavior is backwards compatible with prior passage.el configurations and was added for users who might have entirely different Age identities and recipients for their Emacs-wide Age files vs their passage store Age files. If these environment variables are not set, passage.el will fall back to using the global age.el identity and recipient configuration for its file open and save operations.
For details on proper passage configuration please see its documentation at: https://github.com/FiloSottile/passage
TL;DR: if passage works for you on the normal cli, it should now also work inside passage.el without having to worry about your age.el configuration, by default.
** Other passage use cases
The passage author has a nice walkthrough of their personal, non-emacs based, passage configuration and future plans for the project here:
https://words.filippo.io/dispatches/passage/
- Known Issues
** OTP plugin support is untested
I have not tested passage with the OTP plugins. Ostensibly the regular pass plugins should work relatively unchanged but this is conjecture at this point.
- License
GPLv3
This code was ported from the original password-store Emacs support libraries and its authors are:
- Damien Cassou damien@cassou.me
- Nicolas Petton nicolas@petton.fr
- Keith Amidon camalot@picnicpark.org
- Daniel Barreto
- Svend Sorensen svend@svends.net
Their original copyright assignments apply as this code is mostly a search and replace port of their work.