Search code examples
pythonpython-3.xencryptionsubprocessgnupg

Encrypt file with GPG in python 3 with subprocess


How do I encrypt file with subprocess, so the output will be string.

 password = '%030x' % random.randrange(16**30)
 encrypted_file = subprocess.geststatusoutput("echo "+password+
 "|gpg --password-fd 0 < "+ name_of_selected_file)

I want to encrypted _file to be a string so I could use it as encrypted file to upload with post request.
What is the best way to do it with gnupg library?


Solution

  • You can use subprocess.Popen() to execute a gpg command like this:

    import shlex
    import random
    from subprocess import Popen, PIPE
    
    passphrase = '%030x' % random.randrange(16**30)
    source_filename = '/tmp/somefile'
    cmd = 'gpg --batch --symmetric --cipher-algo AES256 --passphrase-fd 0 --output - {}'.format(source_filename)
    
    # error handling omitted
    p = Popen(shlex.split(cmd), stdout=PIPE, stdin=PIPE, stderr=PIPE)
    encrypted_data = p.communicate(passphrase.encode())[0]
    
    # Decryption - should work for Python 2 & 3
    import os
    
    r, w = os.pipe()    # pipe for sending passphrase from parent to child
    try:
        os.set_inheritable(r, True)
    except AttributeError:      # new in version 3.4
        pass
    cmd = 'gpg --batch --decrypt --passphrase-fd {}'.format(r)
    p = Popen(shlex.split(cmd), stdout=PIPE, stdin=PIPE, stderr=PIPE, close_fds=False)
    os.close(r)    # closes fd in parent, child needs it
    f = os.fdopen(w, 'w')
    f.write(passphrase + '\n')    # '\n' seems required for Python 2 ???
    f.close()
    decrypted_data, stderr = p.communicate(encrypted_data)
    
    # check that data was successfully roundtripped
    assert open(source_filename).read() == decrypted_data.decode()
    

    Or, decryption for Python 3 only:

    import os
    r, w = os.pipe()
    cmd = 'gpg --batch --decrypt --passphrase-fd {}'.format(r)
    p = Popen(shlex.split(cmd), stdout=PIPE, stdin=PIPE, stderr=PIPE, pass_fds=(r,))
    os.close(r)    # closes fd in parent, child needs it
    open(w, 'w').write(passphrase)
    decrypted_data, stderr = p.communicate(encrypted_data)
    
    # check that data was successfully roundtripped
    assert open(source_filename).read() == decrypted_data.decode()
    

    Now I am not an expert on the security of this method, however, writing the passphrase directly to the child process' stdin with communicate() is better than echoing the passphrase on the command line - which would be visible to anybody that could run ps or equivalent.

    Also, relying on the shell level IO redirection of your command would require the shell=True argument to Popen which might have other security implications (see the warning in the Popen() documentation).

    I have assumed in my gpg command that you are intending to use a symmetric encryption (your example does not suggest otherwise). If the remote server needs to be able to decrypt the encrypted file content, how are you going to share the generated passphrase?

    You might be better off looking at using public key crypto instead.