We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Don't armor encrypted submissions
SecureDrop currently armors encrypted submissions. This bloats the size of stored submissions significantly due to the encoding. For example, a 93 MB upload results in a 125.7 MB submission for the journalist to download.

Downloading anything over Tor is very slow (the aforementioned download took me, on average, 9 minutes to download). Therefore, unnecessarily increasing the size of submissions severely impacts usability. There is no reason that I can think of to ascii armor submissions - they are uploaded and downloaded over HTTP, which automatically handles encoding and de-encoding binary data.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `securedrop/crypto_util.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 import os
3 import subprocess
4 from base64 import b32encode
5 
6 from Crypto.Random import random
7 import gnupg
8 import scrypt
9 
10 import config
11 import store
12 
13 # to fix gpg error #78 on production
14 os.environ['USERNAME'] = 'www-data'
15 
16 GPG_KEY_TYPE = "RSA"
17 if os.environ.get('SECUREDROP_ENV') == 'test':
18     # Optiimize crypto to speed up tests (at the expense of security - DO NOT
19     # use these settings in production)
20     GPG_KEY_LENGTH = 1024
21     SCRYPT_PARAMS = dict(N=2**1, r=1, p=1)
22 else:
23     GPG_KEY_LENGTH = 4096
24     SCRYPT_PARAMS = config.SCRYPT_PARAMS
25 
26 SCRYPT_ID_PEPPER = config.SCRYPT_ID_PEPPER
27 SCRYPT_GPG_PEPPER = config.SCRYPT_GPG_PEPPER
28 
29 DEFAULT_WORDS_IN_RANDOM_ID = 8
30 
31 # Make sure these pass before the app can run
32 # TODO: Add more tests
33 def do_runtime_tests():
34     assert(config.SCRYPT_ID_PEPPER != config.SCRYPT_GPG_PEPPER)
35     # crash if we don't have srm:
36     try:
37         subprocess.check_call(['srm'], stdout=subprocess.PIPE)
38     except subprocess.CalledProcessError:
39         pass
40 
41 do_runtime_tests()
42 
43 GPG_BINARY = 'gpg2'
44 try:
45     p = subprocess.Popen([GPG_BINARY, '--version'], stdout=subprocess.PIPE)
46 except OSError:
47     GPG_BINARY = 'gpg'
48     p = subprocess.Popen([GPG_BINARY, '--version'], stdout=subprocess.PIPE)
49 
50 assert p.stdout.readline().split()[
51     -1].split('.')[0] == '2', "upgrade GPG to 2.0"
52 del p
53 
54 gpg = gnupg.GPG(binary=GPG_BINARY, homedir=config.GPG_KEY_DIR)
55 
56 words = file(config.WORD_LIST).read().split('\n')
57 nouns = file(config.NOUNS).read().split('\n')
58 adjectives = file(config.ADJECTIVES).read().split('\n')
59 
60 
61 class CryptoException(Exception):
62     pass
63 
64 
65 def clean(s, also=''):
66     """
67     >>> clean("Hello, world!")
68     Traceback (most recent call last):
69       ...
70     CryptoException: invalid input
71     >>> clean("Helloworld")
72     'Helloworld'
73     """
74     # safe characters for every possible word in the wordlist includes capital
75     # letters because codename hashes are base32-encoded with capital letters
76     ok = ' !#%$&)(+*-1032547698;:=?@acbedgfihkjmlonqpsrutwvyxzABCDEFGHIJKLMNOPQRSTUVWXYZ'
77     for c in s:
78         if c not in ok and c not in also:
79             raise CryptoException("invalid input: %s" % s)
80     # scrypt.hash requires input of type str. Since the wordlist is all ASCII
81     # characters, this conversion is not problematic
82     return str(s)
83 
84 
85 def genrandomid(words_in_random_id=DEFAULT_WORDS_IN_RANDOM_ID):
86     return ' '.join(random.choice(words) for x in range(words_in_random_id))
87 
88 
89 def display_id():
90     return ' '.join([random.choice(adjectives), random.choice(nouns)])
91 
92 
93 def hash_codename(codename, salt=SCRYPT_ID_PEPPER):
94     """
95     >>> hash_codename('Hello, world!')
96     'EQZGCJBRGISGOTC2NZVWG6LILJBHEV3CINNEWSCLLFTUWZLFHBTS6WLCHFHTOLRSGQXUQLRQHFMXKOKKOQ4WQ6SXGZXDAS3Z'
97     """
98     return b32encode(scrypt.hash(clean(codename), salt, **SCRYPT_PARAMS))
99 
100 
101 def genkeypair(name, secret):
102     """
103     >>> if not gpg.list_keys(hash_codename('randomid')):
104     ...     genkeypair(hash_codename('randomid'), 'randomid').type
105     ... else:
106     ...     u'P'
107     u'P'
108     """
109     name = clean(name)
110     secret = hash_codename(secret, salt=SCRYPT_GPG_PEPPER)
111     return gpg.gen_key(gpg.gen_key_input(
112         key_type=GPG_KEY_TYPE, key_length=GPG_KEY_LENGTH,
113         passphrase=secret,
114         name_email=name
115     ))
116 
117 
118 def delete_reply_keypair(source_id):
119     key = getkey(source_id)
120     # If this source was never flagged for reivew, they won't have a reply keypair
121     if not key: return
122     # The private key needs to be deleted before the public key can be deleted
123     # http://pythonhosted.org/python-gnupg/#deleting-keys
124     gpg.delete_keys(key, True) # private key
125     gpg.delete_keys(key)       # public key
126     # TODO: srm?
127 
128 
129 def getkey(name):
130     for key in gpg.list_keys():
131         for uid in key['uids']:
132             if name in uid:
133                 return key['fingerprint']
134     return None
135 
136 
137 def get_key_by_fingerprint(fingerprint):
138     matches = filter(lambda k: k['fingerprint'] == fingerprint, gpg.list_keys())
139     return matches[0] if matches else None
140 
141 
142 def encrypt(plaintext, fingerprints, output=None):
143     # Verify the output path
144     if output:
145         store.verify(output)
146 
147     # Remove any spaces from provided fingerpints
148     # GPG outputs fingerprints with spaces for readability, but requires the
149     # spaces to be removed when using fingerprints to specify recipients.
150     if not isinstance(fingerprints, (list, tuple)):
151         fingerprints = [fingerprints,]
152     fingerprints = [ fpr.replace(' ', '') for fpr in fingerprints ]
153 
154     if isinstance(plaintext, unicode):
155         plaintext = plaintext.encode('utf8')
156 
157     encrypt_fn = gpg.encrypt if isinstance(plaintext, str) else gpg.encrypt_file
158     out = encrypt_fn(plaintext,
159                      *fingerprints,
160                      output=output,
161                      always_trust=True)
162     if out.ok:
163         return out.data
164     else:
165         raise CryptoException(out.stderr)
166 
167 
168 def decrypt(secret, plain_text):
169     """
170     >>> key = genkeypair('randomid', 'randomid')
171     >>> decrypt('randomid', 'randomid',
172     ...   encrypt('randomid', 'Goodbye, cruel world!')
173     ... )
174     'Goodbye, cruel world!'
175     """
176     hashed_codename = hash_codename(secret, salt=SCRYPT_GPG_PEPPER)
177     return gpg.decrypt(plain_text, passphrase=hashed_codename).data
178 
179 
180 if __name__ == "__main__":
181     import doctest
182     doctest.testmod()
183
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/securedrop/crypto_util.py b/securedrop/crypto_util.py
--- a/securedrop/crypto_util.py
+++ b/securedrop/crypto_util.py
@@ -158,7 +158,8 @@
     out = encrypt_fn(plaintext,
                      *fingerprints,
                      output=output,
-                     always_trust=True)
+                     always_trust=True,
+                     armor=False)
     if out.ok:
         return out.data
     else:

{"golden_diff": "diff --git a/securedrop/crypto_util.py b/securedrop/crypto_util.py\n--- a/securedrop/crypto_util.py\n+++ b/securedrop/crypto_util.py\n@@ -158,7 +158,8 @@\n     out = encrypt_fn(plaintext,\n                      *fingerprints,\n                      output=output,\n-                     always_trust=True)\n+                     always_trust=True,\n+                     armor=False)\n     if out.ok:\n         return out.data\n     else:\n", "issue": "Don't armor encrypted submissions\nSecureDrop currently armors encrypted submissions. This bloats the size of stored submissions significantly due to the encoding. For example, a 93 MB upload results in a 125.7 MB submission for the journalist to download.\n\nDownloading anything over Tor is very slow (the aforementioned download took me, on average, 9 minutes to download). Therefore, unnecessarily increasing the size of submissions severely impacts usability. There is no reason that I can think of to ascii armor submissions - they are uploaded and downloaded over HTTP, which automatically handles encoding and de-encoding binary data.\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\nimport os\nimport subprocess\nfrom base64 import b32encode\n\nfrom Crypto.Random import random\nimport gnupg\nimport scrypt\n\nimport config\nimport store\n\n# to fix gpg error #78 on production\nos.environ['USERNAME'] = 'www-data'\n\nGPG_KEY_TYPE = \"RSA\"\nif os.environ.get('SECUREDROP_ENV') == 'test':\n    # Optiimize crypto to speed up tests (at the expense of security - DO NOT\n    # use these settings in production)\n    GPG_KEY_LENGTH = 1024\n    SCRYPT_PARAMS = dict(N=2**1, r=1, p=1)\nelse:\n    GPG_KEY_LENGTH = 4096\n    SCRYPT_PARAMS = config.SCRYPT_PARAMS\n\nSCRYPT_ID_PEPPER = config.SCRYPT_ID_PEPPER\nSCRYPT_GPG_PEPPER = config.SCRYPT_GPG_PEPPER\n\nDEFAULT_WORDS_IN_RANDOM_ID = 8\n\n# Make sure these pass before the app can run\n# TODO: Add more tests\ndef do_runtime_tests():\n    assert(config.SCRYPT_ID_PEPPER != config.SCRYPT_GPG_PEPPER)\n    # crash if we don't have srm:\n    try:\n        subprocess.check_call(['srm'], stdout=subprocess.PIPE)\n    except subprocess.CalledProcessError:\n        pass\n\ndo_runtime_tests()\n\nGPG_BINARY = 'gpg2'\ntry:\n    p = subprocess.Popen([GPG_BINARY, '--version'], stdout=subprocess.PIPE)\nexcept OSError:\n    GPG_BINARY = 'gpg'\n    p = subprocess.Popen([GPG_BINARY, '--version'], stdout=subprocess.PIPE)\n\nassert p.stdout.readline().split()[\n    -1].split('.')[0] == '2', \"upgrade GPG to 2.0\"\ndel p\n\ngpg = gnupg.GPG(binary=GPG_BINARY, homedir=config.GPG_KEY_DIR)\n\nwords = file(config.WORD_LIST).read().split('\\n')\nnouns = file(config.NOUNS).read().split('\\n')\nadjectives = file(config.ADJECTIVES).read().split('\\n')\n\n\nclass CryptoException(Exception):\n    pass\n\n\ndef clean(s, also=''):\n    \"\"\"\n    >>> clean(\"Hello, world!\")\n    Traceback (most recent call last):\n      ...\n    CryptoException: invalid input\n    >>> clean(\"Helloworld\")\n    'Helloworld'\n    \"\"\"\n    # safe characters for every possible word in the wordlist includes capital\n    # letters because codename hashes are base32-encoded with capital letters\n    ok = ' !#%$&)(+*-1032547698;:=?@acbedgfihkjmlonqpsrutwvyxzABCDEFGHIJKLMNOPQRSTUVWXYZ'\n    for c in s:\n        if c not in ok and c not in also:\n            raise CryptoException(\"invalid input: %s\" % s)\n    # scrypt.hash requires input of type str. Since the wordlist is all ASCII\n    # characters, this conversion is not problematic\n    return str(s)\n\n\ndef genrandomid(words_in_random_id=DEFAULT_WORDS_IN_RANDOM_ID):\n    return ' '.join(random.choice(words) for x in range(words_in_random_id))\n\n\ndef display_id():\n    return ' '.join([random.choice(adjectives), random.choice(nouns)])\n\n\ndef hash_codename(codename, salt=SCRYPT_ID_PEPPER):\n    \"\"\"\n    >>> hash_codename('Hello, world!')\n    'EQZGCJBRGISGOTC2NZVWG6LILJBHEV3CINNEWSCLLFTUWZLFHBTS6WLCHFHTOLRSGQXUQLRQHFMXKOKKOQ4WQ6SXGZXDAS3Z'\n    \"\"\"\n    return b32encode(scrypt.hash(clean(codename), salt, **SCRYPT_PARAMS))\n\n\ndef genkeypair(name, secret):\n    \"\"\"\n    >>> if not gpg.list_keys(hash_codename('randomid')):\n    ...     genkeypair(hash_codename('randomid'), 'randomid').type\n    ... else:\n    ...     u'P'\n    u'P'\n    \"\"\"\n    name = clean(name)\n    secret = hash_codename(secret, salt=SCRYPT_GPG_PEPPER)\n    return gpg.gen_key(gpg.gen_key_input(\n        key_type=GPG_KEY_TYPE, key_length=GPG_KEY_LENGTH,\n        passphrase=secret,\n        name_email=name\n    ))\n\n\ndef delete_reply_keypair(source_id):\n    key = getkey(source_id)\n    # If this source was never flagged for reivew, they won't have a reply keypair\n    if not key: return\n    # The private key needs to be deleted before the public key can be deleted\n    # http://pythonhosted.org/python-gnupg/#deleting-keys\n    gpg.delete_keys(key, True) # private key\n    gpg.delete_keys(key)       # public key\n    # TODO: srm?\n\n\ndef getkey(name):\n    for key in gpg.list_keys():\n        for uid in key['uids']:\n            if name in uid:\n                return key['fingerprint']\n    return None\n\n\ndef get_key_by_fingerprint(fingerprint):\n    matches = filter(lambda k: k['fingerprint'] == fingerprint, gpg.list_keys())\n    return matches[0] if matches else None\n\n\ndef encrypt(plaintext, fingerprints, output=None):\n    # Verify the output path\n    if output:\n        store.verify(output)\n\n    # Remove any spaces from provided fingerpints\n    # GPG outputs fingerprints with spaces for readability, but requires the\n    # spaces to be removed when using fingerprints to specify recipients.\n    if not isinstance(fingerprints, (list, tuple)):\n        fingerprints = [fingerprints,]\n    fingerprints = [ fpr.replace(' ', '') for fpr in fingerprints ]\n\n    if isinstance(plaintext, unicode):\n        plaintext = plaintext.encode('utf8')\n\n    encrypt_fn = gpg.encrypt if isinstance(plaintext, str) else gpg.encrypt_file\n    out = encrypt_fn(plaintext,\n                     *fingerprints,\n                     output=output,\n                     always_trust=True)\n    if out.ok:\n        return out.data\n    else:\n        raise CryptoException(out.stderr)\n\n\ndef decrypt(secret, plain_text):\n    \"\"\"\n    >>> key = genkeypair('randomid', 'randomid')\n    >>> decrypt('randomid', 'randomid',\n    ...   encrypt('randomid', 'Goodbye, cruel world!')\n    ... )\n    'Goodbye, cruel world!'\n    \"\"\"\n    hashed_codename = hash_codename(secret, salt=SCRYPT_GPG_PEPPER)\n    return gpg.decrypt(plain_text, passphrase=hashed_codename).data\n\n\nif __name__ == \"__main__\":\n    import doctest\n    doctest.testmod()\n", "path": "securedrop/crypto_util.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\nimport os\nimport subprocess\nfrom base64 import b32encode\n\nfrom Crypto.Random import random\nimport gnupg\nimport scrypt\n\nimport config\nimport store\n\n# to fix gpg error #78 on production\nos.environ['USERNAME'] = 'www-data'\n\nGPG_KEY_TYPE = \"RSA\"\nif os.environ.get('SECUREDROP_ENV') == 'test':\n    # Optiimize crypto to speed up tests (at the expense of security - DO NOT\n    # use these settings in production)\n    GPG_KEY_LENGTH = 1024\n    SCRYPT_PARAMS = dict(N=2**1, r=1, p=1)\nelse:\n    GPG_KEY_LENGTH = 4096\n    SCRYPT_PARAMS = config.SCRYPT_PARAMS\n\nSCRYPT_ID_PEPPER = config.SCRYPT_ID_PEPPER\nSCRYPT_GPG_PEPPER = config.SCRYPT_GPG_PEPPER\n\nDEFAULT_WORDS_IN_RANDOM_ID = 8\n\n# Make sure these pass before the app can run\n# TODO: Add more tests\ndef do_runtime_tests():\n    assert(config.SCRYPT_ID_PEPPER != config.SCRYPT_GPG_PEPPER)\n    # crash if we don't have srm:\n    try:\n        subprocess.check_call(['srm'], stdout=subprocess.PIPE)\n    except subprocess.CalledProcessError:\n        pass\n\ndo_runtime_tests()\n\nGPG_BINARY = 'gpg2'\ntry:\n    p = subprocess.Popen([GPG_BINARY, '--version'], stdout=subprocess.PIPE)\nexcept OSError:\n    GPG_BINARY = 'gpg'\n    p = subprocess.Popen([GPG_BINARY, '--version'], stdout=subprocess.PIPE)\n\nassert p.stdout.readline().split()[\n    -1].split('.')[0] == '2', \"upgrade GPG to 2.0\"\ndel p\n\ngpg = gnupg.GPG(binary=GPG_BINARY, homedir=config.GPG_KEY_DIR)\n\nwords = file(config.WORD_LIST).read().split('\\n')\nnouns = file(config.NOUNS).read().split('\\n')\nadjectives = file(config.ADJECTIVES).read().split('\\n')\n\n\nclass CryptoException(Exception):\n    pass\n\n\ndef clean(s, also=''):\n    \"\"\"\n    >>> clean(\"Hello, world!\")\n    Traceback (most recent call last):\n      ...\n    CryptoException: invalid input\n    >>> clean(\"Helloworld\")\n    'Helloworld'\n    \"\"\"\n    # safe characters for every possible word in the wordlist includes capital\n    # letters because codename hashes are base32-encoded with capital letters\n    ok = ' !#%$&)(+*-1032547698;:=?@acbedgfihkjmlonqpsrutwvyxzABCDEFGHIJKLMNOPQRSTUVWXYZ'\n    for c in s:\n        if c not in ok and c not in also:\n            raise CryptoException(\"invalid input: %s\" % s)\n    # scrypt.hash requires input of type str. Since the wordlist is all ASCII\n    # characters, this conversion is not problematic\n    return str(s)\n\n\ndef genrandomid(words_in_random_id=DEFAULT_WORDS_IN_RANDOM_ID):\n    return ' '.join(random.choice(words) for x in range(words_in_random_id))\n\n\ndef display_id():\n    return ' '.join([random.choice(adjectives), random.choice(nouns)])\n\n\ndef hash_codename(codename, salt=SCRYPT_ID_PEPPER):\n    \"\"\"\n    >>> hash_codename('Hello, world!')\n    'EQZGCJBRGISGOTC2NZVWG6LILJBHEV3CINNEWSCLLFTUWZLFHBTS6WLCHFHTOLRSGQXUQLRQHFMXKOKKOQ4WQ6SXGZXDAS3Z'\n    \"\"\"\n    return b32encode(scrypt.hash(clean(codename), salt, **SCRYPT_PARAMS))\n\n\ndef genkeypair(name, secret):\n    \"\"\"\n    >>> if not gpg.list_keys(hash_codename('randomid')):\n    ...     genkeypair(hash_codename('randomid'), 'randomid').type\n    ... else:\n    ...     u'P'\n    u'P'\n    \"\"\"\n    name = clean(name)\n    secret = hash_codename(secret, salt=SCRYPT_GPG_PEPPER)\n    return gpg.gen_key(gpg.gen_key_input(\n        key_type=GPG_KEY_TYPE, key_length=GPG_KEY_LENGTH,\n        passphrase=secret,\n        name_email=name\n    ))\n\n\ndef delete_reply_keypair(source_id):\n    key = getkey(source_id)\n    # If this source was never flagged for reivew, they won't have a reply keypair\n    if not key: return\n    # The private key needs to be deleted before the public key can be deleted\n    # http://pythonhosted.org/python-gnupg/#deleting-keys\n    gpg.delete_keys(key, True) # private key\n    gpg.delete_keys(key)       # public key\n    # TODO: srm?\n\n\ndef getkey(name):\n    for key in gpg.list_keys():\n        for uid in key['uids']:\n            if name in uid:\n                return key['fingerprint']\n    return None\n\n\ndef get_key_by_fingerprint(fingerprint):\n    matches = filter(lambda k: k['fingerprint'] == fingerprint, gpg.list_keys())\n    return matches[0] if matches else None\n\n\ndef encrypt(plaintext, fingerprints, output=None):\n    # Verify the output path\n    if output:\n        store.verify(output)\n\n    # Remove any spaces from provided fingerpints\n    # GPG outputs fingerprints with spaces for readability, but requires the\n    # spaces to be removed when using fingerprints to specify recipients.\n    if not isinstance(fingerprints, (list, tuple)):\n        fingerprints = [fingerprints,]\n    fingerprints = [ fpr.replace(' ', '') for fpr in fingerprints ]\n\n    if isinstance(plaintext, unicode):\n        plaintext = plaintext.encode('utf8')\n\n    encrypt_fn = gpg.encrypt if isinstance(plaintext, str) else gpg.encrypt_file\n    out = encrypt_fn(plaintext,\n                     *fingerprints,\n                     output=output,\n                     always_trust=True,\n                     armor=False)\n    if out.ok:\n        return out.data\n    else:\n        raise CryptoException(out.stderr)\n\n\ndef decrypt(secret, plain_text):\n    \"\"\"\n    >>> key = genkeypair('randomid', 'randomid')\n    >>> decrypt('randomid', 'randomid',\n    ...   encrypt('randomid', 'Goodbye, cruel world!')\n    ... )\n    'Goodbye, cruel world!'\n    \"\"\"\n    hashed_codename = hash_codename(secret, salt=SCRYPT_GPG_PEPPER)\n    return gpg.decrypt(plain_text, passphrase=hashed_codename).data\n\n\nif __name__ == \"__main__\":\n    import doctest\n    doctest.testmod()\n", "path": "securedrop/crypto_util.py"}]}