5

I'm trying to do symmetric encryption with python and gnupg.

This code snippet works on my windows vista machine, on which the python gnupg module is version 0.3.2:

import gnupg
gpg = gnupg.GPG()
data = 'the quick brown fow jumps over the laxy dog.'
passphrase='12345'
crypt = gpg.encrypt(data, recipients=None,
                     symmetric='AES256',
                     passphrase=passphrase,
                     armor=False)

When I try to run it on my linux machine with the version 1.2.5 python gnupg module I get this error:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "/usr/local/lib/python2.7/dist-packages/gnupg/gnupg.py", line 961, in encrypt
    result = self._encrypt(stream, recipients, **kwargs)
TypeError: _encrypt() got multiple values for keyword argument 'recipients'

I have done a number of searches and can't find anything on this.

user3601780
  • 51
  • 1
  • 3

6 Answers6

9

This is an old ask, but I came upon this in a Google search and was unhappy with the provided answers. I found the real answer in python-gnupg's GitHub issues:

gpg.encrypt(data, symmetric='AES256', passphrase=passphrase, armor=False, encrypt=False)

So, drop the recipients=None and add an encrypt=False. Your crypt.data will then contain the encrypted data. Unintuitive, but it works.

(src: https://github.com/isislovecruft/python-gnupg/issues/110)

Victoria
  • 91
  • 2
  • 3
  • 1
    Encrypt seems to work, any idea as to how to decrypt? gpg.decrypt(crypt.data, passphrase=passphrase) doesn't seem to work ... – Dan Jan 04 '18 at 03:08
  • gpg.encrypt(data, recipients=None, symmetric='AES256', passphrase=passphrase, armor=False) works on the last version – raphaeldavidf Nov 21 '19 at 23:22
2

I think it's just broken (correct me if I'm wrong). It appears to be fixed in gnupg 0.3.6-1 for Python 3.x. You're probably better off using that or trying a workaround, such as one similar to what I'm about to describe.

This workaround uses the command-line gpg in Python instead of the gnupg module. You could do a similar workaround with gpg.encrypt_file, of course (which wouldn't have the temporarily stored passphrase I talk about in the next paragraph; that would most likely be a better choice).

If you're concerned about the passphrase showing up in the task manager, I took care of that by putting the password in a temporary file and using cat to pipe it into gpg. However, you might have to worry about malware snatching the password from the temporary file, if it can do it in time.

Note that this code is in Python 3.x, since that's what I use the most (to recreate it in 2.x you'll have to learn how temporary files work in it, and maybe a few other things).

import subprocess, tempfile, os, shutil

def gpg_encrypt(data, passphrase, alg="AES256", hash="SHA512", compress_alg="BZIP2", compress_lvl="9", iterations="1000000"):
    #This is for symmetric encryption.
    with tempfile.TemporaryDirectory() as directory:
        filepath=os.path.join(directory, "tmp")
        with open(filepath, "w") as FILE:
            FILE.write(data)
        iterations=str(iterations)
        compress_level="--compress-level "+compress_lvl
        if compress_alg.upper()=="BZIP2":
            compress_level="--bzip2-compress-level "+compress_lvl
        tmp_filename="tmp"
        with tempfile.TemporaryDirectory() as DIR:
            tmpfilepath=os.path.join(DIR, tmp_filename)
            with open(tmpfilepath, "w") as FILE:
                FILE.write(passphrase)
            subprocess.Popen("cat "+tmpfilepath+"|gpg --batch --yes --passphrase-fd 0 --force-mdc --s2k-mode 3 --s2k-count "+iterations+" --s2k-cipher-algo "+alg+" --s2k-digest-algo "+hash+" --compress-algo='"+compress_alg+"' --compress-level "+compress_lvl+" -ac '"+filepath+"'", stdin=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True).communicate()[0]
        result=None
        with open(filepath+".asc", "r") as FILE:
            result=FILE.read()
        return result

def gpg_decrypt(data, passphrase):
    #This is for decryption.
    with tempfile.TemporaryDirectory() as directory:
        filepath=os.path.join(directory, "tmp")
        with open(filepath, "w") as FILE:
            FILE.write(data)
        decrypted=None
        tmp_filename="tmp"
        with tempfile.TemporaryDirectory() as DIR:
            tmpfilepath=os.path.join(DIR, tmp_filename)
            with open(tmpfilepath, "w") as FILE:
                FILE.write(passphrase)
            decrypted=subprocess.Popen("cat "+tmpfilepath+"|gpg --batch --yes --passphrase-fd 0 --output='"+filepath+".gpg"+"' '"+filepath+"'", stdin=subprocess.PIPE, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
        result=None
        decrypted=not "decryption failed:" in str(decrypted)
        if decrypted==True:
            with open(filepath+".gpg", "r") as FILE:
                result=FILE.read()
    return decrypted, result #If it worked, return True. If not, return False. (And, return the decrypted data.)

test=gpg_encrypt(data="This is a test!", passphrase="enter")
print("Here is the encrypted message:\n"+test)
decrypted, data=gpg_decrypt(data=test, passphrase="enter")
if decrypted:
    print("Here is the decrypted message:\n"+data)
else:
    print("Incorrect passphrase to decrypt the message.")

Here's the code for encrypting/decrypting symmetrically encrypted files (just for good measure):

import subprocess, tempfile, os, shutil

def gpg_encrypt_file(filepath, passphrase, output=None, alg="AES256", hash="SHA512", compress_alg="BZIP2", compress_lvl="9", iterations="1000000"):
    #This is for symmetric encryption.
    filepath=filepath.replace("'", "'\\''") #This makes it so you can have apostrophes within single quotes.
    iterations=str(iterations)
    compress_level="--compress-level "+compress_lvl
    if compress_alg.upper()=="BZIP2":
        compress_level="--bzip2-compress-level "+compress_lvl
    result=None
    tmp_filename="tmp"
    with tempfile.TemporaryDirectory() as DIR:
        tmpfilepath=os.path.join(DIR, tmp_filename)
        with open(tmpfilepath, "w") as FILE:
            FILE.write(passphrase)
        if output:
            if output[0]!=os.sep and filepath[0]==os.sep:
                output=os.path.join(os.path.dirname(filepath), output)
            result=subprocess.Popen("cat "+tmpfilepath+"|gpg --batch --yes --passphrase-fd 0 --force-mdc --s2k-mode 3 --s2k-count "+iterations+" --s2k-cipher-algo "+alg+" --s2k-digest-algo "+hash+" --compress-algo='"+compress_alg+"' "+compress_level+" --output='"+output+"' -ac '"+filepath+"'", stdin=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True).communicate()[0]
        else:
            result=subprocess.Popen("cat "+tmpfilepath+"|gpg --batch --yes --passphrase-fd 0 --force-mdc --s2k-mode 3 --s2k-count "+iterations+" --s2k-cipher-algo "+alg+" --s2k-digest-algo "+hash+" --compress-algo='"+compress_alg+"' --compress-level "+compress_lvl+" -ac '"+filepath+"'", stdin=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True).communicate()[0]
    return result.strip()

def gpg_decrypt_file(filepath, passphrase, output=None):
    #This is for decryption.
    filepath=filepath.replace("'", "'\\''")
    result=None
    tmp_filename="tmp"
    with tempfile.TemporaryDirectory() as DIR:
        tmpfilepath=os.path.join(DIR, tmp_filename)
        with open(tmpfilepath, "w") as FILE:
            FILE.write(passphrase)
        if output:
            if output[0]!=os.sep and filepath[0]==os.sep:
                output=os.path.join(os.path.dirname(filepath), output)
            result=subprocess.Popen("cat "+tmpfilepath+"|gpg --batch --yes --passphrase-fd 0 --output='"+output+"' '"+filepath+"'", stdin=subprocess.PIPE, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
        else:
            result=subprocess.Popen("cat "+tmpfilepath+"|gpg --batch --yes --passphrase-fd 0 '"+filepath+"'", stdin=subprocess.PIPE, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
    return not "decryption failed:" in str(result) #If it worked, return True. If not, return False.

gpg_encrypt_file(filepath="test.txt", passphrase="myPassphrase", output="test.asc")
if gpg_decrypt_file(filepath="test.asc", passphrase="myPassphrase", output="output.txt"):
    print("Successfully decrypted!")
else:
    print("Incorrect passphrase.")
Brōtsyorfuzthrāx
  • 4,387
  • 4
  • 34
  • 56
1

The "standard" python-gnupg module's (instead of pretty-bad-protocol's module) symmetric-encrypt() call makes much more sense to me:

encrypted_data = gpg.encrypt(data=msg, symmetric=True, passphrase='1234', recipients=None)

Details: https://stackoverflow.com/a/72355824/605356

Johnny Utahh
  • 2,389
  • 3
  • 25
  • 41
0

Double-check the documentation, the argument isn't recipient, is is recipients (note the plural)

Docs (emphasis mine):

symmetric (defaults to False) If specified, symmetric encryption is used. In this case, specify recipients as None. If True is specified, then the default cipher algorithm (CAST5) is used. Starting with version 0.3.5, you can also specify the cipher-algorithm to use (for example, 'AES256'). Check your gpg command line help to see what symmetric cipher algorithms are supported. Note that the default (CAST5) may not be the best available.

Thomas Orozco
  • 53,284
  • 11
  • 113
  • 116
  • 3
    Using `gpg.encrypt(data, recipients=None, symmetric='AES256', passphrase=passphrase, armor=False)` I get `TypeError: _encrypt() got multiple values for keyword argument 'recipients'` – user3601780 May 21 '14 at 14:39
  • It is `recipients` in my full code, but somehow the s got lost when I pulled out the code snippet to ask the question. – user3601780 May 21 '14 at 14:56
0

This should work with python-gnupg 0.3.5 and above (adjust as necessary):

import gnupg

gpg_home = "~/.gnupg"
gpg = gnupg.GPG(gnupghome=gpg_home)

data = raw_input("Enter full path of file to encrypt: ")
phrase = raw_input("Enter the passphrase to decrypt the file: ")
cipher = raw_input("Enter symmetric encryption algorithm to use: ")
savefile = data+".asc"

afile = open(data, "rb")
encrypted_ascii_data = gpg.encrypt_file(afile, None, passphrase=phrase, symmetric=cipher.upper(), output=savefile)
afile.close()

Not sure about versions prior to 0.3.5.

The "None" for recipients does not (with current versions of the module) require "recipients=None" when the gpg.encrypt_file() is called.

Ben
  • 3,981
  • 2
  • 25
  • 34
  • I'm not using `gpg.encrypt_file`. I'm using `gpg.encrypt`, because my data is coming from python code as a string, not from a file. Would it be better to pass it to String.IO and use `gpg.encrypt_file`? – user3601780 May 21 '14 at 14:47
  • 1
    Using 'gpg.encrypt(data, None, symmetric='AES256', passphrase=passphrase, armor=False)' I get `ValueError: Unknown status message: u'NO_RECP'` – user3601780 May 21 '14 at 14:48
0
        gpg = gnupg.GPG()
        data = 'the quick brown fow jumps over the laxy dog.'
        passphrase='12345'
        crypt = gpg.encrypt(
            data,
            recipients=None,
            symmetric='AES256',
            passphrase=passphrase,
            armor=False,
        )
        print(crypt.data)

        clear = gpg.decrypt(
            crypt.data,
            passphrase=passphrase,
        )

        print(clear)

As of July 28, 2022, The above works just fine for me. Python 3.9.2 -- python-gnupg 0.4.9

Mohsen Banan
  • 85
  • 1
  • 5