GnuPG - How to edit the file without decrypt and save to local disk first?

Viewed 19179

I'm using GNUPG to encrypt my ascii files.

I learnt to generate a key, also how to use the it to encrypt and decrypt a file.

There are two ways I used:

gpg -d foo.txt.gpg

and

gpg --output foo.txt --decrypt
foo.txt.gpg

I realized the first method will display the decrypted file on the screen, for example when I executed the command over SSH.

With regard to the second method, I concerned if it will leave a trace on the local pc - the foo.txt file.

Most importantly, I don't know how to edit the contents of the foo file on the fly. Ideally, I would like to open the file over SSH use nano/pico, type my passphrase to decrypt, then edit the file, save it and encrypt it. I very much like to avoid save any files to the local disk.

Any comments are welcome.

Thank you in advance.

15 Answers

Here is a slight improvement to @Luke's answer. It makes two small improvements:

  • It avoids the stack trace if the file is unmodified during the edit session.

  • It restores the original gpg file if re-encryption back to the original gpg file was attempted, which is a little safer than checking the modification dates of the edit file.

#!/usr/bin/python

# Downloaded from https://stackoverflow.com/questions/1510105/gnupg-how-to-edit-the-file-without-decrypt-and-save-to-local-disk-first/12289967#12289967
# and then slightly improved.

import os, sys, subprocess, getpass, stat, shutil

editor = 'nano'
dataFile = sys.argv[1]

## make a backup of the encrypted file
bakFile = dataFile+'-gpgedit_backup'
shutil.copy(dataFile, bakFile)
dstat = os.stat(dataFile)

##  create temporary directory in tmpfs to work from
tmpDir = '/dev/shm/gpgedit'
n = 0
while True:
    try:
        os.mkdir(tmpDir+str(n))
        break
    except OSError as err:
        if err.errno != 17:  ## file already exists
            raise
    n += 1
tmpDir += str(n)

os.chmod(tmpDir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)


reEncrypted = False
try:
    ## Get password
    passwd = getpass.getpass()

    ## decrypt file
    tmpFile = os.path.join(tmpDir, 'data')
    cmd = "gpg -d --cipher-algo AES256 --passphrase-fd 0 --output %s %s" % (tmpFile, dataFile)
    proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE)
    proc.stdin.write(passwd)
    proc.stdin.close()
    if proc.wait() != 0:
        raise Exception("Error decrypting file.")

    ## record stats of tmp file
    stat = os.stat(tmpFile)

    ## invoke editor
    os.system('%s %s' % (editor, tmpFile))

    ## see whether data has changed
    stat2 = os.stat(tmpFile)
    if stat.st_mtime == stat2.st_mtime and stat.st_size == stat2.st_size:
        print "Data unchanged; not re-writing encrypted file."
    else:
        ## re-encrypt, write back to original file
    reEncrypted = True
        cmd = "gpg --yes --symmetric --cipher-algo AES256 --passphrase-fd 0 --output %s %s" % (dataFile, tmpFile)
        proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE)
        proc.stdin.write(passwd)
        proc.stdin.close()
        if proc.wait() != 0:
            raise Exception("Error encrypting file.")
except:
    ## If there was an error AND re-encryption was attempted, restore the backup.
    if reEncrypted:
        print "Error occurred; restoring encrypted file from backup."
        shutil.copy(bakFile, dataFile)
    raise
finally:
    shutil.rmtree(tmpDir)
    os.remove(bakFile)

I would have posted these suggested improvements as comments to @Luke's answer -- which I like a lot -- but did not have enough reputation points to do so. :(

I wrote a shell script to edit files encrypted with gpg. Call it like ./editgpg.sh path/to/.secrets.gpg, edit with vim then close with ':q!'. Vim flag '-n' opens file only in memory. No temp files.

#!/usr/bin/env bash


# decrypt CRYPT_FILE, pipe to vim, and encrypt againg whent type ':q!'
edit_crypt_file() {
        echo "Enter your gpg encrypted file passphrase,
edit it with vim, then close the editor with ':q!'."

        # first argument is a file encrypted with gpg
        CRYPT_FILE=$1
        # get password user input
        local pass
        read -sp "Password:" pass

        gpg_flags='--batch --yes'
        vim_flags='- -n -u NONE --not-a-term'

        # vim command to run before exit with ':q!'
        vim_autocmd=":autocmd VimLeave * :%! tee | gpg $gpg_flags --passphrase $pass -o $CRYPT_FILE -c"

        gpg $gpg_flags --passphrase $pass -d $CRYPT_FILE | vim $vim_flags -c "$vim_autocmd"

        # restart agent in order to lose kept password
        gpgconf --kill gpg-agent
        unset pass
}

edit_crypt_file $1

Related