added symmetric encryption. changed options in to groups
This commit is contained in:
23
Bakefile
23
Bakefile
@@ -19,25 +19,30 @@ install_useve() {
|
||||
py-format .
|
||||
useve qgpg
|
||||
pip install ./
|
||||
qgpg --help
|
||||
}
|
||||
|
||||
test_encrypt() {
|
||||
# Prepare
|
||||
cd ~/tmp/
|
||||
. useve-runner
|
||||
useve qgpg
|
||||
rm -fr key2* datadir sha1sum.txt
|
||||
GPGPASS=secret @ qgpg --key key2 k
|
||||
rm -fr key2* datadir datadir.encrypted sha1sum.txt
|
||||
@ mkdir -p datadir/folder{1,2}
|
||||
@ dd if=/dev/random of=datadir/testfile bs=3024 count=102400
|
||||
for i in {1..10}; do
|
||||
dd if=/dev/random of=datadir/folder1/${i}testfile bs=1024 count=10240 &> /dev/null
|
||||
dd if=/dev/random of=datadir/folder2/${i}testfile bs=1024 count=10240 &> /dev/null
|
||||
done
|
||||
@ qgpg --key key2.pub e datadir/folder1/1testfile
|
||||
@ qgpg --key key2.pub e datadir/folder1/1testfile datadir/1testfile.encrypted.gpg
|
||||
@ qgpg --key key2.pub -r e datadir
|
||||
@ qgpg --key key2.pub -r e datadir
|
||||
@ qgpg --key key2.pub -r e $(pwd)/datadir/ datadir.encrypted/
|
||||
# Run encryption code
|
||||
set -e
|
||||
GPGPASS=secret @ qgpg -k --key key2
|
||||
@ qgpg -e --key key2.pub datadir/folder1/1testfile
|
||||
@ qgpg -e --key key2.pub datadir/folder1/1testfile datadir/1testfile.encrypted.gpg
|
||||
@ qgpg -e --key key2.pub -r datadir
|
||||
@ qgpg -e --key key2.pub -r datadir
|
||||
@ qgpg -e --key key2.pub -r $(pwd)/datadir/ datadir.encrypted/
|
||||
GPGPASS=symmetric_password @ qgpg -e datadir/folder1/1testfile datadir/1testfile.encrypted.symmetric.gpg
|
||||
@ hash-update -t sha1 -f sha1sum.txt -r datadir
|
||||
}
|
||||
|
||||
@@ -45,8 +50,10 @@ test_decrypt() {
|
||||
cd ~/tmp/
|
||||
. useve-runner
|
||||
useve qgpg
|
||||
set -e
|
||||
@ find datadir -type f -name '*testfile' | xargs -II rm -v I
|
||||
GPGPASS=secret @ qgpg --key key2 -r d datadir
|
||||
GPGPASS=secret @ qgpg -d --key key2 -r datadir
|
||||
GPGPASS=symmetric_password @ qgpg -d --force datadir/1testfile.encrypted.symmetric.gpg datadir/folder1/1testfile
|
||||
@ hash-update -t sha1 -f sha1sum.txt -c
|
||||
}
|
||||
|
||||
|
||||
140
qgpg/__init__.py
140
qgpg/__init__.py
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
__version__ = "1.1.1"
|
||||
__version__ = "20240627.1"
|
||||
|
||||
import argparse
|
||||
import os
|
||||
@@ -101,52 +101,106 @@ def get_opts():
|
||||
"""Returns command line arguments"""
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
add_help=False,
|
||||
description="Wrapper to GPG, allowing recursive encryption.",
|
||||
epilog="""Private key passphrase can be set to env variable GPGPASS, otherwise will prompt
|
||||
epilog="""Private key passphrase can be set to env variable GPGPASS, otherwise program will prompt it.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
- Generate keys:
|
||||
qgpg --key ./path/mykey --name "User Name" keygen
|
||||
qgpg --keygen --key ./path/mykey --name "User Name"
|
||||
- This will create private and public keys, mykey and mykey.pub
|
||||
- Share the public key to someone who will encrypt data to you
|
||||
- Encrypt file:
|
||||
qgpg --key ./mykey.pub encrypt ./plain_file.txt ./encrypted_file.txt.gpg
|
||||
- Encrypt all files in a folder:
|
||||
qgpg --key ./mykey.pub -r encrypt ./folder/
|
||||
qgpg --encrypt --key ./mykey.pub ./plain_file.txt ./encrypted_file.txt.gpg
|
||||
- Encrypt all files recursively in a folder:
|
||||
qgpg --encrypt --key ./mykey.pub --recursive ./in_folder/
|
||||
qgpg --encrypt --key ./mykey.pub --recursive ./from_folder/ ./to_another_folder/
|
||||
- Decrypt all files with the private key:
|
||||
qgpg --key ./mykey -r decrypt ./encrypted_folder/
|
||||
qgpg --decrypt --key ./mykey --recursive ./encrypted_folder/
|
||||
- Using passhprase in a variable:
|
||||
GPGPASS=mysecretpassword qgpg --key ./mykey decrypt file.gpg file.txt
|
||||
GPGPASS=mysecretpassword qgpg --key ./mykey --decrypt file.gpg file.txt
|
||||
- Symmetric encryption (Just leave out the --key):
|
||||
qgpg --encrypt file.txt file.gpg
|
||||
|
||||
""",
|
||||
)
|
||||
parser.add_argument("--width", help="Console width in characters. Defaults to auto detect.")
|
||||
parser.add_argument("--no-progress", help="Disable progress meter.", default=False, action="store_true")
|
||||
parser.add_argument(
|
||||
"--key", help="path to recipient public key for encryption or private key for decryption", required=True
|
||||
commands = parser.add_argument_group("Command", "Main command for the program")
|
||||
commands_grp = commands.add_mutually_exclusive_group(required=True)
|
||||
commands_grp.add_argument(
|
||||
"--keygen",
|
||||
"-k",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Create new key pair",
|
||||
)
|
||||
parser.add_argument("--name", help="Name of the owner of the key", required=False, default="recipient@address")
|
||||
parser.add_argument(
|
||||
commands_grp.add_argument(
|
||||
"--encrypt",
|
||||
"-e",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Encrypt files",
|
||||
)
|
||||
commands_grp.add_argument(
|
||||
"--decrypt",
|
||||
"-d",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Decrypt files",
|
||||
)
|
||||
crypt_grp = parser.add_argument_group("Encryption", "Encryption and Decryption options")
|
||||
|
||||
crypt_grp.add_argument(
|
||||
"--key",
|
||||
help="Path to key file. For keygen: private key to write; encrypt: public key file, decrypt: private key file. If not defined, symmetric password encryption is used.",
|
||||
)
|
||||
keygen_grp = parser.add_argument_group(
|
||||
"Keygen", "Options for keygen. Note: --key is required for keygen, see above."
|
||||
)
|
||||
keygen_grp.add_argument(
|
||||
"--name",
|
||||
help="Name of the owner of the key (usually email) Default: %(default)s",
|
||||
required=False,
|
||||
default="recipient@address",
|
||||
)
|
||||
crypt_grp.add_argument(
|
||||
"--recursive",
|
||||
"-r",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Recursive encryption or decryption. Processes full folder structures",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"command",
|
||||
help="keygen/encrypt/decrypt. you can also use just the first letter.",
|
||||
choices=["k", "e", "d", "keygen", "encrypt", "decrypt"],
|
||||
crypt_grp.add_argument(
|
||||
"--force",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Overwrite existing files",
|
||||
)
|
||||
parser.add_argument("path", help="path to file to encrypt/decrypt, or folder if recursive", nargs="?")
|
||||
parser.add_argument("out_path", help="output file or folder when using recursive.", nargs="?")
|
||||
misc_grp = parser.add_argument_group("Misc", "Other options")
|
||||
misc_grp.add_argument("--width", help="Console width in characters. Defaults to auto detect.")
|
||||
misc_grp.add_argument("--no-progress", help="Disable progress meter.", default=False, action="store_true")
|
||||
misc_grp.add_argument("--version", help="Display version and exit", default=False, action="store_true")
|
||||
misc_grp.add_argument("--help", "-h", action="help", help="Show this help message and exit")
|
||||
|
||||
crypt_grp.add_argument("path", help="Path to source file to encrypt/decrypt, or folder if recursive", nargs="?")
|
||||
crypt_grp.add_argument(
|
||||
"out_path",
|
||||
help="Path to target file or folder when using recursive. If not specified, use of .gpg extension is automatically used.",
|
||||
nargs="?",
|
||||
)
|
||||
|
||||
parsed = parser.parse_args()
|
||||
for cmd in ("keygen", "encrypt", "decrypt"):
|
||||
if parsed.command == cmd[0]:
|
||||
parsed.command = cmd
|
||||
if parsed.version:
|
||||
print(__version__)
|
||||
sys.exit(0)
|
||||
for cmd in zip(("keygen", "encrypt", "decrypt"), (parsed.keygen, parsed.encrypt, parsed.decrypt)):
|
||||
if cmd[1]:
|
||||
parsed.command = cmd[0]
|
||||
if parsed.command == "keygen":
|
||||
if parsed.key is None:
|
||||
parser.error("--key required for keygen")
|
||||
|
||||
return parsed
|
||||
|
||||
|
||||
@@ -171,18 +225,24 @@ class Processor:
|
||||
self.homedir = tempfile.TemporaryDirectory()
|
||||
self.gpg = gnupg.GPG(gnupghome=self.homedir.name)
|
||||
self.gpg.encoding = "utf-8"
|
||||
self.symmetric = self.opts.key is None
|
||||
self.suffix = ".gpg"
|
||||
self.phrase = None
|
||||
|
||||
if self.opts.command == "keygen":
|
||||
self.keygen()
|
||||
filelist = []
|
||||
|
||||
if self.opts.command in ("encrypt", "decrypt"):
|
||||
if not self.symmetric:
|
||||
import_result = self.gpg.import_keys_file(self.opts.key)
|
||||
filelist = self.get_filelist(self.opts.path, self.opts.recursive, self.opts.command)
|
||||
|
||||
if self.opts.command == "encrypt":
|
||||
|
||||
if self.symmetric:
|
||||
self.set_phrase(twice=True)
|
||||
else:
|
||||
keyid = import_result.fingerprints[0]
|
||||
public_keys = self.gpg.list_keys()
|
||||
for key in public_keys:
|
||||
@@ -191,6 +251,7 @@ class Processor:
|
||||
|
||||
if self.opts.command == "decrypt":
|
||||
self.set_phrase()
|
||||
|
||||
n_f = len(filelist)
|
||||
for i, f in enumerate(filelist):
|
||||
print(f"{i+1}/{n_f} {self.opts.command}ing {f} ", file=sys.stderr)
|
||||
@@ -200,16 +261,27 @@ class Processor:
|
||||
|
||||
def set_phrase(self, twice=False):
|
||||
|
||||
if not self.phrase is None:
|
||||
# phrase already set
|
||||
return
|
||||
|
||||
self.phrase = os.environ.get("GPGPASS", None)
|
||||
if self.phrase is None:
|
||||
print("Type private key passphrase [Empty if no passphrase]")
|
||||
print(
|
||||
"Type symmetric passphrase"
|
||||
if self.symmetric
|
||||
else "Type private key passphrase [Empty if no passphrase]"
|
||||
)
|
||||
self.phrase = getpass()
|
||||
if twice:
|
||||
print("Type private key passphrase again to confirm")
|
||||
if twice and len(self.phrase) > 0:
|
||||
print("Type passphrase again to confirm")
|
||||
phrase2 = getpass()
|
||||
if phrase2 != self.phrase:
|
||||
print("Passphrases do not match!")
|
||||
sys.exit(1)
|
||||
if self.phrase == "" and self.symmetric:
|
||||
print("Symmetric passphrase required!")
|
||||
sys.exit(1)
|
||||
|
||||
def get_filelist(self, root, recurse, direction):
|
||||
if not recurse:
|
||||
@@ -229,9 +301,12 @@ class Processor:
|
||||
return filelist
|
||||
|
||||
def keygen(self):
|
||||
if os.path.exists(self.opts.key):
|
||||
print(f"File {self.opts.key} already exists")
|
||||
if not self.opts.force:
|
||||
for f in (self.opts.key, self.opts.key + ".pub"):
|
||||
if os.path.exists(f):
|
||||
print(f"File {f} already exists")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Generating keys for {self.opts.name}...", file=sys.stderr)
|
||||
self.set_phrase(twice=True)
|
||||
input_data = self.gpg.gen_key_input(
|
||||
@@ -264,6 +339,8 @@ class Processor:
|
||||
os.makedirs(os.path.dirname(out_file), exist_ok=True)
|
||||
else:
|
||||
out_file = self.opts.out_path if self.opts.out_path else auto_path
|
||||
|
||||
if not self.opts.force:
|
||||
if os.path.exists(out_file):
|
||||
print(f"{out_file} already exists", file=sys.stderr)
|
||||
return
|
||||
@@ -274,6 +351,11 @@ class Processor:
|
||||
try:
|
||||
with open(in_file, "rb") as fp:
|
||||
if self.opts.command == "encrypt":
|
||||
if self.symmetric:
|
||||
d = self.gpg.encrypt_file(
|
||||
fp, None, symmetric="AES256", passphrase=self.phrase, always_trust=True, armor=False
|
||||
)
|
||||
else:
|
||||
d = self.gpg.encrypt_file(fp, self.uid, always_trust=True, armor=False)
|
||||
if self.opts.command == "decrypt":
|
||||
d = self.gpg.decrypt_file(fp, passphrase=self.phrase, always_trust=True)
|
||||
|
||||
Reference in New Issue
Block a user