Category Archives: Programs

Encrypt files recursively with openssl

I wrote this program because I had a great idea to offload encrypted versions of my data, but conserving the full directory structure, keeping the permissions and timestamps. This way you can do incremental encrypted snapshots to an untrusted remote server. Always rsync the directory you wish to encrypt someplace else locally, and then run this script.

$ pycrypto.py -e 

This encrypts everything in the "." directory, recursively, and removes the original files. Pass the -n switch to not remove the files.

$ pyrcypto.py -d

This will decrypt everything within "." directory, with the .enc extension. Please be aware that because of the nature of the encryption used, there is no sanity check. If you enter the wrong password while decrypting, the files will be “decrypted” with the wrong password. You’ll get the files, but it won’t be the files you have encrypted in the first place. I’ve considered using a hashing method, but this comprimises security and slows down the process considerably and this was designed to be fast. You can download pycrypto here.

#!/usr/bin/env python2

"""
This script encrypts files in the current (.) directory, including 
hidden files using the AES encryption. The original timestamps are 
preserved and the original files are deleted. A .enc suffix is added 
at the end of each encrypted file. The purpose of the program is to 
encrypt the data, while preserving the original directory structure 
and timestamps so you can safely rsync it to an unsecure location. 
The passphrase needs to be either 16, 24 or 32 bytes long.

You will need to have python-crypto installed on your system, most
distributions have it in their repositories.
"""

from Crypto.Cipher import AES
from stat import *

import os, random, struct, optparse, sys, getpass

# Define options
parser = optparse.OptionParser(usage="%prog --encrypt | --decrypt")
parser.add_option('-e', '--encrypt', dest='enc', action="store_true",
       help='Encrypt the entire current directory including files in'
       ' subtrees and hidden files')
parser.add_option('-d', '--decrypt', dest='dec', action="store_true",
       help='Decrypt the entire current directory including files in'
       ' subtrees and hidden files')
parser.add_option('-v', '--verbose', dest='verb', action="store_true",
       help='Verbose mode')
parser.add_option('-n', '--no-remove', dest='delete', action="store_true",
       help='Do not remove input files once encrypted or decrypted')       
options, files = parser.parse_args()

FILES=[]

def pad_password(pwd):
    "Pad the password to lengths 16, 24, or 32, as needed for AES encryption."
    for size in 16, 24, 32:
        if len(pwd) <= size:
            return pwd + 'x' * (size - len(pwd))
    raise ValueError("password must be 32 characters, or shorter")



def encrypt_file(key, in_filename, out_filename, atime, mtime, perm, chunksize=64*1024):
    if options.verb:
        sys.stdout.write("Encrypting %s\n" % in_filename[2:])
    iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16))
    encryptor = AES.new(key, AES.MODE_CBC, iv)
    filesize = os.path.getsize(in_filename)

    with open(in_filename, 'rb') as infile:
        with open(out_filename, 'wb') as outfile:
            outfile.write(struct.pack('<Q', filesize))
            outfile.write(iv)

            while True:
                chunk = infile.read(chunksize)
                if len(chunk) == 0:
                    break
                elif len(chunk) % 16 != 0:
                    chunk += ' ' * (16 - len(chunk) % 16)

                outfile.write(encryptor.encrypt(chunk))
    os.utime(out_filename, (atime, mtime))
    os.chmod(out_filename, perm)
    if options.delete == None:
       os.remove(in_filename)


def decrypt_file(key, in_filename, out_filename, atime, mtime, perm, chunksize=24*1024):
    if options.verb:
        sys.stdout.write("Decrypting %s\n" % in_filename[2:])

    with open(in_filename, 'rb') as infile:
        origsize = struct.unpack('<Q', infile.read(struct.calcsize('Q')))[0]
        iv = infile.read(16)
        decryptor = AES.new(key, AES.MODE_CBC, iv)

        with open(out_filename, 'wb') as outfile:
            while True:
                chunk = infile.read(chunksize)
                if len(chunk) == 0:
                    break
                outfile.write(decryptor.decrypt(chunk))

            outfile.truncate(origsize)
    os.utime(out_filename, (atime, mtime))
    os.chmod(out_filename, perm)
    if options.delete == None:
       os.remove(in_filename)

if options.enc and options.dec:
    sys.stderr.write("Please use -e or -d")
    sys.exit(1)

if options.enc == None and options.dec == None:
    sys.stderr.write("Please use -e or -d\n")
    sys.exit(1)

for dirname, dirnames, filenames in os.walk('.'):
    for filename in filenames:
        FILES.append(os.path.join(dirname, filename))

if options.enc:
    pwd = pad_password(getpass.getpass())
    for i in FILES:
       if os.path.islink(i):
           continue 
       encrypt_file(pwd, i, i+".enc",
       os.path.getatime(i), os.path.getmtime(i), os.stat(i)[ST_MODE])

if options.dec:
   pwd = pad_password(getpass.getpass())       
   for i in FILES:
       if os.path.islink(i):
           continue 
       decrypt_file(pwd, i, i[:-4], 
       os.path.getatime(i), os.path.getmtime(i), os.stat(i)[ST_MODE])

Match file timestamp with EXIF data

Over the years I’ve collected a lot of pictures, coming close to 20000. Most of these pictures have the exif metadata embedded in the JPEG files. Alas, I was careless with some of the photographs, and when copying over from filesystem to filesystem, creating backups etc., the timestamps got overwritten. So now I had loads of files that had a timestamp of 22nd January 2010 for example. They were most definitely not taken at that date, but rather they were copied then and no preserve timestamp flag was enabled at the time of the cp issued. I googled for a quick solution to my problems, but I could not find anything that would be simple to use, and not clogged up with bullshit. So, I cracked my knuckles and delved into the world of Python:

#!/usr/bin/python2

# You need the exifread module installed
import exifread, time, datetime, os, sys

def collect_data(file):
    tags = exifread.process_file(open(file), 'rb')
    for tag in tags.keys():
        if tag in ('EXIF DateTimeOriginal'):
            return "%s" % (tags[tag])

for file in sys.argv[1:]:
    try:
        phototaken = int(datetime.datetime.strptime(collect_data(file), '%Y:%m:%d %H:%M:%S').strftime("%s"))
        os.utime(file,(phototaken,phototaken))
    except Exception, e:
        sys.stderr.write(str(e))
        sys.stderr.write('Failed on ' + str(file) + '\n')

Basically it takes each file, reads the exif metadata for photo taken and invokes the os.utime() function to set the timestamp to that date. You’ll need the exifread module for Python, this is the simplest one I could find that can do what I needed it to do. I hope someone will find this script useful. You can start it simply with $ exify *.JPG. You can download it here.