GPG in Emacs - Functions to Decrypt and Delete All
06 Jan 2024Table of Contents
Motivation
Continuing from my last post, the EPA provides a seamless interface when working with GPG files in Emacs. But there are situations where I have to work with GPG files using other programs (mostly Python) which EPA cannot help.
For those cases, I have to decrypt the GPG files first before using
them (for example, calling pandas.read_csv).
Obviously, there’s no point in encrypting a file if there is a decrypted version next to it. So I also need a function to delete all the decrypted files.
Emacs Lisp Implementation
Of course, I run Python inside of Emacs, I wrote the Lisp functions to decrypt GPG files and delete all the decrypted files.
(defun yt/gpg--decrypt-recursively (root-dir)
"It decrypts all the files ends .gpg under the root-dir. The decrypted files have the same filename but without the .gpg extension.
It stops if the decryption fails.
"
(interactive)
(dolist (file (directory-files-recursively root-dir "\\.gpg"))
;; the 2nd argument for epa-decrypt-file can only be the base filename without the directory.
(let ((default-directory (file-name-directory file)))
(epa-decrypt-file file (file-name-base file))
))
)
(defun yt/gpg--delete-decrypted-files (root-dir)
"It deletes the decrypted files under the root-dir directory.
e.g. if there's a file foo.tar.gz.gpg, it attempts to remove the foo.tar.gz file.
"
(interactive)
(dolist (file (directory-files-recursively root-dir "\\.gpg"))
(delete-file (file-name-sans-extension file))
)
)A bit of explanation:
- directory-files-recursively: searches for files with a pattern. Here, it returns all the files ending with .gpg under the given root-dir,
- dolist: loops over the GPG files to process them one by one,
- epa-decrypt-file: decrypts a GPG file into a new file.
- delete-file: deletes a given filename.
It seems the epa-decrypt-file function does not like the new
filename with the directory in its path, so I have to set the default
directory (working directory) and use the base filename after removing
the directory as a workaround.
Bash Implementation
It would be useful to have those functionalities outside of the Emacs, so I implemented their counterpart in Bash.
function decrypt_recursively() {
# PS: this function is equivalent to `gpg --decrypt-files $1/**/*.gpg`
for fn in $(find $1 -iname "*.gpg")
do
echo decrypt ${fn} to "${fn%.*}"
gpg -o "${fn%.*}" -d "${fn}"
done
}
function remove_decrypted_files() {
for fn in $(find $1 -iname "*.gpg")
do
echo removing "${fn%.*}"
rm "${fn%.*}"
done
}The interface is the same: given a root directory, it decrypts all the GPG files or deletes the decrypted files.
A little bit of Bash:
-
$1: refers to the first function argument,
$2refers to the second function argument and so on. This is the Bash way. When the function is called,$1will be replaced with the actual argument, here it means the root directory. -
$(find …): is a list of files returned by the
findprogram. In this context, it stands for all the files whose filename ends with .gpg.It can be achieved using
lsprogram but it will be a lot slower 1 and requires some configuration in MacOS 2. -
${fn%.*}: removes the last file extension of the variable $fn$, for example, foo.tar.gz.gpg becomes foo.tar.gz.
Another approach is using
$(basename $fn .gpg)to remove the .gpg extension explicitly. -
for, do, done: loops through each file.
The Bash functions have the advantage of being easily incorporated
into the system, for example, call the remove_decrypted_files
function automatically prior to shutting down or after login.