Editing GPG encrypted files symmetrically in NeoVIM
Copy plugin/gpg.lua file to your ~/.config/nvim/lua/plugins/ directory
-- ~/.config/nvim/lua/plugins/gpg.lua
{
"benoror/gpg.nvim",
ft = { "gpg", "asc", "pgp" },
}-- ~/.config/nvim/init.lua
vim.pack.add({
{ "https://github.com/benoror/gpg.nvim" },
})vim.filetype.add({
extension = {
gpg = "gpg",
asc = "asc",
},
})
return {
"benoror/gpg.nvim",
ft = { "gpg", "asc", "pgp" },
}Vía @Frestein Frestein/dotfiles/dot_config/nvim/lua/plugins/extras/utils/gpg.lua
gpg(GnuPG 2.1+ recommended for loopback pinentry)- Optional: GUI pinentry (only needed if you disable loopback)
All *.gpg files will be decrypted/encrypted transparently using gpg tools
(--default-recipient-self / asymmetric encrypt on write).
This plugin intentionally stays focused on transparent whole-file editing for
*.gpg (and similar) buffers. Inline encrypt/decrypt of a visual selection —
including picking recipients from your keyring — is left to Neovim's built-in
external filter (:!) and gpg, so the plugin remains small and agnostic.
Select text in visual mode, then:
" Encrypt to yourself (ASCII-armored)
:'<,'>!gpg -ae --default-recipient-self
" Encrypt to a specific recipient
:'<,'>!gpg -ae -r alice@example.com
" Decrypt an armored PGP block
:'<,'>!gpg -qdOptional keymaps:
vim.keymap.set("v", "<leader>ge", ":!gpg -ae --default-recipient-self<CR>", {
desc = "GPG encrypt selection",
})
vim.keymap.set("v", "<leader>gd", ":!gpg -qd<CR>", {
desc = "GPG decrypt selection",
})For a recipient picker, list keys with gpg --list-keys, choose with
vim.ui.select / Telescope / fzf-lua, then run the same filter with one or more
-r flags. That UI is config-local and out of scope for this plugin.
By default, decrypt uses --pinentry-mode loopback so Neovim owns the
passphrase prompt via inputsecret() when the agent does not already have the
key cached. This avoids pinentry-curses TTY contention (dropped key presses)
inside Neovim — see issue #8
and docs/pinentry.md for the assessment, exact probe argv,
pros/cons, and alternatives.
The first decrypt is a probe (ciphertext on stdin). It is not supposed to
prompt. If the agent lacks a passphrase, GnuPG should print
gpg: Sorry, we are in batchmode - can't get input and exit; the plugin then
prompts inside Neovim and retries with --passphrase-fd. A hang after that
line means gpg did not exit (often GnuPG use-keyboxd / agent IPC —
issue #20), not a missing
plugin prompt.
The probe wait has a safety timeout so a wedged gpg cannot freeze Neovim
forever (vim.g.gpg_probe_timeout, default 30000 ms; false or 0 waits
indefinitely).
Loopback is on by default. To restore the legacy external pinentry path:
vim.g.gpg_pinentry_loopback = falseThese opt-in knobs are legacy mitigations for the external pinentry path. Prefer the default loopback behavior above.
Update the GPG agent startup TTY (equivalent to
gpg-connect-agent updatestartuptty /bye):
vim.g.gpg_update_tty = trueOptional "priming" step that runs gpg --list-packets before decrypting so
the passphrase may be cached by the agent:
vim.g.gpg_prime_agent = trueLocal smoke tests (headless Neovim, temp keyring):
make test-bash
make test-zsh
make test-nuPlugin manager compatibility checks:
make test-lazy
make test-packerNotes:
- The tests create a temporary
GNUPGHOMEand a throwaway key, so your user keyring is not touched. - The tests also set isolated
XDG_*paths andNVIM_APPNAMEto a temp directory to avoid writing artifacts into your normal Neovim runtime. tests/init_lazy.luaandtests/init_packer.luawill clone their managers if missing (network required).
- From @nickali https://gist.github.com/nickali/89f3743e305db015d0f3ad4ffd325ccb
- Proposed first by @traut https://gist.github.com/traut/cd19ae2817ab13e0bade1f8a9995029f
https://github.com/jamessan/vim-gnupg
- Pinentry / passphrase notes (issue #8 assessment, exact probe argv, issue #20 hangs)
- Setup GPG on macOS
- vim-gnupg#32 (related Neovim + pinentry TTY contention)
