Files
duplicati/Tools/Commandline/ReEncrypt/ReEncrypt.py
T

205 lines
8.9 KiB
Python
Raw Normal View History

2018-01-26 20:56:19 +01:00
#!/usr/bin/env python3
2024-02-28 15:45:30 +01:00
## Permission is hereby granted, free of charge, to any person obtaining a
## copy of this software and associated documentation files (the "Software"),
## to deal in the Software without restriction, including without limitation
## the rights to use, copy, modify, merge, publish, distribute, sublicense,
## and/or sell copies of the Software, and to permit persons to whom the
## Software is furnished to do so, subject to the following conditions:
##
## The above copyright notice and this permission notice shall be included in
## all copies or substantial portions of the Software.
##
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
## OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
## FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
## DEALINGS IN THE SOFTWARE.
2018-01-26 20:56:19 +01:00
import os
import sys, getopt
2018-01-26 20:56:19 +01:00
import io
import json
import zipfile
import base64
import hashlib
import pyAesCrypt
from collections import OrderedDict
from tempfile import mkstemp, mkdtemp, TemporaryFile, TemporaryDirectory, NamedTemporaryFile
import gnupg
import shutil
2018-03-08 17:55:34 +01:00
from joblib import Parallel, delayed
import multiprocessing
2018-01-26 20:56:19 +01:00
def mainReEncrypt(options):
2018-01-26 20:56:19 +01:00
# locate dlist
dlists = [name for name in os.listdir(options['orig']['path']) if name.endswith(".dlist.%s" %(options['orig']['extension']))]
# loop over all dlists; they only need to be enencrypted, and encrypted. They have no relation to the dindex and dblock files.
with NamedTemporaryFile() as temp_file:
for dlist_enc in dlists:
dlist_enc_fullpath = os.path.join(options['orig']['path'],dlist_enc)
dlist_reenc_fullpath = os.path.join(options['new']['path'],change_ext(dlist_enc,options['orig']['extension'],options['new']['extension']))
decrypt(options['orig'],dlist_enc_fullpath,options['orig']['passwd'],temp_file.name)
encrypt(options['new'],temp_file.name,options['new']['passwd'], dlist_reenc_fullpath)
2018-01-26 20:56:19 +01:00
# locate dlist
dindex = [name for name in os.listdir(options['orig']['path']) if name.endswith(".dindex.%s" %(options['orig']['extension']))]
2018-03-08 17:55:34 +01:00
num_cores = multiprocessing.cpu_count()
Parallel(n_jobs=num_cores*2)(delayed(handleIndex)(options, dindex_enc) for dindex_enc in dindex)
def handleIndex(options, dindex_enc):
2018-01-26 20:56:19 +01:00
with NamedTemporaryFile() as temp_dindex, NamedTemporaryFile() as temp_dindex_reenc, TemporaryDirectory() as temp_path_zip:
2018-03-08 17:55:34 +01:00
dindex_enc_fullpath = os.path.join(options['orig']['path'],dindex_enc)
dindex_reenc_fullpath = os.path.join(options['new']['path'],change_ext(dindex_enc,options['orig']['extension'],options['new']['extension']))
decrypt(options['orig'],dindex_enc_fullpath,options['orig']['passwd'],temp_dindex.name)
2018-01-26 20:56:19 +01:00
2018-03-08 17:55:34 +01:00
unzip(temp_dindex,temp_path_zip)
2018-01-26 20:56:19 +01:00
2018-03-08 17:55:34 +01:00
vol_path = os.path.join(temp_path_zip,'vol')
2020-04-06 14:39:45 +03:00
if os.path.exists(vol_path):
for dblock in os.listdir(vol_path):
data = []
with open(os.path.join(vol_path, dblock)) as data_file:
data = json.load(data_file, object_pairs_hook=OrderedDict)
2018-01-26 20:56:19 +01:00
2020-04-06 14:39:45 +03:00
expected_hash = data['volumehash'].encode('utf8')
expected_volumesize = data['volumesize']
2018-01-26 20:56:19 +01:00
2020-04-06 14:39:45 +03:00
if (options['verify_hash']):
actual_hash = computeHash(os.path.join(options['orig']['path'],dblock))
actual_volumesize=os.stat(os.path.join(options['orig']['path'],dblock)).st_size
print('dblock: %s expected_hash: %s calc_hash: %s exact: %s' % (dblock,expected_hash.decode('utf8'),actual_hash.decode('utf8'),expected_hash==actual_hash))
2018-01-26 20:56:19 +01:00
2020-04-06 14:39:45 +03:00
with NamedTemporaryFile() as temp_dblock:
dblock_enc_fullpath = os.path.join(options['orig']['path'],dblock)
dblock_reenc_fullpath = os.path.join(options['new']['path'],change_ext(dblock,options['orig']['extension'],options['new']['extension']))
decrypt(options['orig'],dblock_enc_fullpath,options['orig']['passwd'],temp_dblock.name)
encrypt(options['new'],temp_dblock.name,options['new']['passwd'], dblock_reenc_fullpath)
new_hash = computeHash(dblock_reenc_fullpath)
data['volumehash'] = new_hash.decode('utf8')
data['volumesize'] = os.stat(os.path.join(options['new']['path'],change_ext(dblock,options['orig']['extension'],options['new']['extension']))).st_size
print('dblock: %s old_hash: %s new_hash: %s' % (dblock,expected_hash.decode('utf8'),data['volumehash']))
2018-01-26 20:56:19 +01:00
2020-04-06 14:39:45 +03:00
with open(os.path.join(vol_path,dblock),'w') as data_file:
json.dump(data, data_file)
2018-01-26 20:56:19 +01:00
2020-04-06 14:39:45 +03:00
os.rename(os.path.join(vol_path, dblock), os.path.join(vol_path, change_ext(dblock,options['orig']['extension'],options['new']['extension'])))
2018-03-08 17:55:34 +01:00
make_zipfile(temp_dindex_reenc.name,temp_path_zip)
encrypt(options['new'],temp_dindex_reenc.name, options['new']['passwd'],dindex_reenc_fullpath)
2018-01-26 20:56:19 +01:00
def change_ext(filename, ext_old, ext_new):
return filename.replace(ext_old, ext_new)
def decrypt(options, encrypted, passw, decrypted):
print('decrypting: %s to %s' % (encrypted, decrypted))
2018-01-26 20:56:19 +01:00
if options['encryption']=='aes':
bufferSize = 64 * 1024
pyAesCrypt.decryptFile(encrypted, decrypted, passw, bufferSize)
if options['encryption']=='gpg':
2018-01-26 20:56:19 +01:00
gpg = gnupg.GPG()
with open(encrypted, 'rb') as f:
status = gpg.decrypt_file(f, output=decrypted,passphrase=passw)
if options['encryption']=='none':
2018-01-26 20:56:19 +01:00
shutil.copy(encrypted,decrypted)
def encrypt(options,decrypted, passw, encrypted):
print('encrypting: %s %s' % (decrypted, encrypted))
if options['encryption']=='aes':
2018-01-26 20:56:19 +01:00
bufferSize = 64 * 1024
pyAesCrypt.encryptFile(decrypted, encrypted, passw, bufferSize)
if options['encryption']=='gpg':
2018-01-26 20:56:19 +01:00
gpg = gnupg.GPG()
with open(decrypted, 'rb') as f:
status = gpg.encrypt_file(f, recipients=options['recipients'], output=encrypted, armor=False)
if options['encryption']=='none':
2018-01-26 20:56:19 +01:00
shutil.copy(decrypted,encrypted)
def emptydir(top):
if(top == '/' or top == "\\"): return
else:
for root, dirs, files in os.walk(top, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
2018-01-26 20:56:19 +01:00
def unzip(archive, path):
emptydir(path)
2018-01-26 20:56:19 +01:00
with zipfile.ZipFile(archive.name) as zf:
zf.extractall(path)
def make_zipfile(output_filename, source_dir):
print('zipping: %s to %s' % (source_dir, output_filename))
emptydir(output_filename)
2018-01-26 20:56:19 +01:00
relroot=source_dir
with zipfile.ZipFile(output_filename, "w", zipfile.ZIP_DEFLATED) as zip:
for root, dirs, files in os.walk(source_dir):
# add directory (needed for empty dirs)
zip.write(root, os.path.relpath(root, relroot))
for file in files:
filename = os.path.join(root, file)
if os.path.isfile(filename): # regular files only
arcname = os.path.join(os.path.relpath(root, relroot), file)
zip.write(filename, arcname)
def rezip(temp_path_z, path):
zf = zipfile.ZipFile(path, "w")
for dirname, subdirs, files in os.walk(temp_path_z):
zf.write(dirname)
for filename in files:
zf.write(os.path.join(dirname, filename))
zf.close()
def zipdir(path,ziph):
for root, dirs, files in os.walk(path):
for file in files:
ziph.write(os.path.join(root, file))
def computeHash(path):
print('hashing: %s ' % (path))
2018-01-26 20:56:19 +01:00
buffersize=64 * 1024
hasher = hashlib.sha256()
with open(path, 'rb') as f:
while True:
buffer = f.read(buffersize)
if not buffer:
break
hasher.update(buffer)
return base64.b64encode(hasher.digest())
def main(argv):
configfile = ''
try:
opts, args = getopt.getopt(argv,"hc:")
except getopt.GetoptError:
print('ReEncrypt.py -c <configfile>')
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print('ReEncrypt.py -c <configfile>')
sys.exit(2)
elif opt == '-c':
configfile = arg
else:
print( "unhandled option")
if (configfile == ''):
print('ReEncrypt.py -c <configfile>')
sys.exit(2)
with open(configfile) as infile:
options = json.load(infile)
mainReEncrypt(options)
2018-01-26 20:56:19 +01:00
print('Complete.')
if __name__ == "__main__":
main(sys.argv[1:])