Esempio n. 1
0
def decrypt(f=None, s=None, pwd=None, out=None):
    if out is None:
        out = io.BytesIO()
    if f is None:
        f = io.BytesIO(s)
    salt = f.read(16)
    nonce = f.read(16)
    tag = f.read(16)
    if salt not in _KEYCACHE:
        _KEYCACHE[salt] = KDF(pwd, salt)[0]
    cipher = Crypto.Cipher.AES.new(_KEYCACHE[salt],
                                   Crypto.Cipher.AES.MODE_GCM,
                                   nonce=nonce)
    while True:
        block = f.read(BLOCKSIZE)
        if not block:
            break
        out.write(cipher.decrypt(block))
    try:
        cipher.verify(tag)
    except ValueError:
        print('Incorrect key or file corrupted.')
    out.seek(0)
    return out
Esempio n. 2
0
def backup(src=None, dest=None, sftppwd=None, encryptionpwd=None):
    """Do a backup of `src` (local path) to `dest` (SFTP). The files are encrypted locally and are *never* decrypted on `dest`. Also, `dest` never gets the `encryptionpwd`."""
    if os.path.isdir(src):
        os.chdir(src)
    else:
        print('Source directory does not exist.')
        return
    remote, user, host, remotepath = parseaddress(dest)
    if not remote or not user or not host or not remotepath:  # either not remote (local), or remote with empty user, host or remotepath
        print(
            'dest should use the following format: [email protected]:/path/to/backup/'
        )
        return
    print(
        'Starting backup...\nSource path: %s\nDestination host: %s\nDestination path: %s'
        % (src, host, remotepath))
    if sftppwd is None:
        sftppwd = getpass.getpass(
            'Please enter the SFTP password for user %s: ' % user)
    if encryptionpwd is None:
        encryptionpwd = getpass.getpass(
            'Please enter the encryption password: '******'Destination directory does not exist.')
                return
            ######## GET DISTANT FILES INFO
            print('Distant files list: getting...')
            DELS = b''
            DISTANTFILES = dict()
            DISTANTHASHES = dict()
            distantfilenames = set(sftp.listdir())
            DISTANTCHUNKS = {
                bytes.fromhex(f)
                for f in distantfilenames if '.' not in f
            }  # discard .files and .tmp files
            for f in distantfilenames:  # remove old distant temp files
                if f.endswith('.tmp'):
                    sftp.remove(f)
            flist = io.BytesIO()
            if sftp.isfile('.files'):
                sftp.getfo('.files', flist)
                flist.seek(0)
                while True:
                    l = flist.read(4)
                    if not l:
                        break
                    length = int.from_bytes(l, byteorder='little')
                    s = flist.read(length)
                    if len(s) != length:
                        print(
                            'Item of .files is corrupt. Last sync interrupted?'
                        )
                        break
                    chunkid, mtime, fsize, h, fn = readdistantfileblock(
                        s, encryptionpwd)
                    DISTANTFILES[fn] = [chunkid, mtime, fsize, h]
                    if DISTANTFILES[fn][0] == NULL16BYTES:  # deleted
                        del DISTANTFILES[fn]
                    if chunkid in DISTANTCHUNKS:
                        DISTANTHASHES[
                            h] = chunkid  # DISTANTHASHES[sha256_noencryption] = chunkid ; even if deleted file keep the sha256, it might be useful for moved/renamed files
            for fn, distantfile in DISTANTFILES.items():
                if not os.path.exists(fn):
                    print('  %s no longer exists (deleted or moved/renamed).' %
                          fn)
                    DELS += newdistantfileblock(chunkid=NULL16BYTES,
                                                mtime=0,
                                                fsize=0,
                                                h=NULL32BYTES,
                                                fn=fn,
                                                key=key,
                                                salt=salt)
            if len(DELS) > 0:
                with sftp.open('.files', 'a+') as flist:
                    flist.write(DELS)
            print('Distant files list: done.')
            ####### SEND FILES
            REQUIREDCHUNKS = set()
            with sftp.open('.files', 'a+') as flist:
                for fn in glob.glob('**/*', recursive=True):
                    if os.path.isdir(fn):
                        continue
                    mtime = os.stat(fn).st_mtime_ns
                    fsize = os.path.getsize(fn)
                    if fn in DISTANTFILES and DISTANTFILES[fn][
                            1] >= mtime and DISTANTFILES[fn][2] == fsize:
                        print(
                            'Already on distant: unmodified (mtime + fsize). Skipping: %s'
                            % fn)
                        REQUIREDCHUNKS.add(DISTANTFILES[fn][0])
                    else:
                        h = getsha256(fn)
                        if h in DISTANTHASHES:  # ex : chunk already there with same SHA256, but other filename  (case 1 : duplicate file, case 2 : renamed/moved file)
                            print(
                                'New, but already on distant (same sha256). Skipping: %s'
                                % fn)
                            chunkid = DISTANTHASHES[h]
                            REQUIREDCHUNKS.add(chunkid)
                        else:
                            print('New, sending file: %s' % fn)
                            chunkid = uuid.uuid4().bytes
                            with sftp.open(chunkid.hex() + '.tmp',
                                           'wb') as f_enc, open(fn, 'rb') as f:
                                encrypt(f, key=key, salt=salt, out=f_enc)
                            sftp.rename(chunkid.hex() + '.tmp', chunkid.hex())
                            REQUIREDCHUNKS.add(chunkid)
                            DISTANTHASHES[h] = chunkid
                        flist.write(
                            newdistantfileblock(chunkid=chunkid,
                                                mtime=mtime,
                                                fsize=fsize,
                                                h=h,
                                                fn=fn,
                                                key=key,
                                                salt=salt)
                        )  # todo: accumulate in a buffer and do this every 10 seconds instead
            delchunks = DISTANTCHUNKS - REQUIREDCHUNKS
            if len(delchunks) > 0:
                print('Deleting %s no-longer-used distant chunks... ' %
                      len(delchunks),
                      end='')
                for chunkid in delchunks:
                    sftp.remove(chunkid.hex())
                print('done.')
        print('Backup finished.')
    except paramiko.ssh_exception.AuthenticationException:
        print('Authentication failed.')
    except paramiko.ssh_exception.SSHException as e:
        print(
            e,
            '\nPlease ssh your remote host at least once before, or add your remote to your known_hosts file.\n\n'
        )  # todo: avoid ugly error messages after