Files
duplicati/Tools/Commandline/ReEncrypt/ReEncrypt.py
T
StanislavandGitHub 40dc08db74 small fix for empty "*index*" file
some index file don't contain 'vol' folder, only manifest. this fix check if 'vol' folder is exists
2020-04-06 14:39:45 +03:00

204 lines
8.6 KiB
Python

#!/usr/bin/env python3
## Copyright (C) 2018, The Duplicati Team
## http://www.duplicati.com, info@duplicati.com
##
## This library is free software; you can redistribute it and/or modify
## it under the terms of the GNU Lesser General Public License as
## published by the Free Software Foundation; either version 2.1 of the
## License, or (at your option) any later version.
##
## This library is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
## Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public
## License along with this library; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import os
import sys, getopt
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
from joblib import Parallel, delayed
import multiprocessing
def mainReEncrypt(options):
# 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)
# locate dlist
dindex = [name for name in os.listdir(options['orig']['path']) if name.endswith(".dindex.%s" %(options['orig']['extension']))]
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):
with NamedTemporaryFile() as temp_dindex, NamedTemporaryFile() as temp_dindex_reenc, TemporaryDirectory() as temp_path_zip:
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)
unzip(temp_dindex,temp_path_zip)
vol_path = os.path.join(temp_path_zip,'vol')
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)
expected_hash = data['volumehash'].encode('utf8')
expected_volumesize = data['volumesize']
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))
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']))
with open(os.path.join(vol_path,dblock),'w') as data_file:
json.dump(data, data_file)
os.rename(os.path.join(vol_path, dblock), os.path.join(vol_path, change_ext(dblock,options['orig']['extension'],options['new']['extension'])))
make_zipfile(temp_dindex_reenc.name,temp_path_zip)
encrypt(options['new'],temp_dindex_reenc.name, options['new']['passwd'],dindex_reenc_fullpath)
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))
if options['encryption']=='aes':
bufferSize = 64 * 1024
pyAesCrypt.decryptFile(encrypted, decrypted, passw, bufferSize)
if options['encryption']=='gpg':
gpg = gnupg.GPG()
with open(encrypted, 'rb') as f:
status = gpg.decrypt_file(f, output=decrypted,passphrase=passw)
if options['encryption']=='none':
shutil.copy(encrypted,decrypted)
def encrypt(options,decrypted, passw, encrypted):
print('encrypting: %s %s' % (decrypted, encrypted))
if options['encryption']=='aes':
bufferSize = 64 * 1024
pyAesCrypt.encryptFile(decrypted, encrypted, passw, bufferSize)
if options['encryption']=='gpg':
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':
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))
def unzip(archive, path):
emptydir(path)
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)
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))
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)
print('Complete.')
if __name__ == "__main__":
main(sys.argv[1:])