Cloud Filer Crypto










Cloud Filer Crypto Details

Each _CFCI file contains a base 64 encoded salt and list of entries. Each entry holds the real file or folder name, the obfuscated name, the unencrypted file size and the local modification time for the file. A 256 bit key and the salt are generated using the Python pbkdf2_sha256.encrypt function from the passlib Python library applied to the user's password with 50000 rounds. The _CFCI file and each data file are encrypted using the AES cypher from the pycrypto Python library in Cypher Block mode using the 256 bit key and a random IV generated using Python's urandom function. The IV is stored with each file.

You can check the encryption works as expected by decrypting the files uploaded by Cloud Filer using the following python source code:


import glob
import getpass
from base64 import b64decode
from passlib.hash import pbkdf2_sha256
from Crypto.Cipher import AES
from ast import literal_eval

FOLDER_CONTENTS_HEADER = 'Nuvovis Cloud Filer folder contents:'
CTA_ALTCHARS = './'

def DecryptStr(cypherText, key):
    cypherText = b64decode(cypherText)
    iv = cypherText[0:AES.block_size]
    crypto = AES.new(key, AES.MODE_CBC, iv)
    return UnpadStr(crypto.decrypt(cypherText[AES.block_size:]))

def UnpadStr(cypherText):
    padSize = ord(cypherText[-1])
    return cypherText[0:-padSize]

def DecryptFile(in_file, out_file, key):
    bs = AES.block_size
    iv = in_file.read(bs)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    next_chunk = ''
    finished = False
    while not finished:
        chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs))
        if len(next_chunk) == 0:
            chunk = UnpadStr(chunk)
            finished = True
        out_file.write(chunk)


# Look for the first file ending in "_CFCI" in current dir
fileList = glob.glob('*_CFCI')
if len(fileList) == 0:
    raise Exception('No Cloud Filer contents information file found matching *_CFCI')

fileName = fileList[0]
print 'Decoding ' + fileName + '...'

# Read the whole file into memory
with file(fileName) as f: contentsInfoCypher = f.read()
print 'Got _CFCI contents: ' + contentsInfoCypher

# Get the user's password
passwd = getpass.getpass()

# Split salt from cyphertext
splitPoint = contentsInfoCypher.index('\n')
b64salt = contentsInfoCypher[0:splitPoint]
folderContentsCypher = contentsInfoCypher[splitPoint+1:]
salt = b64decode(b64salt)

# Get the encryption key (hash) from the salt and password
hashAndSalt = pbkdf2_sha256.encrypt(passwd, salt=salt, rounds=50000)
cryptoStuff = hashAndSalt.split('$')
b64key = cryptoStuff[4]
while len(b64key) % 4 != 0:
    b64key = b64key + '='
key = b64decode(b64key, CTA_ALTCHARS)

# Decode the folder contents information
contentsListStr = DecryptStr(folderContentsCypher, key)

# Check the contents info decrypted ok
contentsList = contentsListStr.split('\n')
if contentsList[0] != FOLDER_CONTENTS_HEADER:
    raise Exception('Failed to decrypt')

print 'Decrypted _CFCI contents: ' + contentsListStr

# Decode each directory entry, skipping current folder name
for i in range(2, len(contentsList) - 1):
    obj = literal_eval(contentsList[i])
    (obfuscatedObjName, objName, objSize, objTime) = obj
    # If it's a file
    if obfuscatedObjName.startswith('_CFL'):
        fileList = glob.glob('*' + obfuscatedObjName)
        if len(fileList) == 0:
            raise Exception('No encrypted file found matching *' + obfuscatedObjName)

        fileName = fileList[0]
        print 'Decrypting ' + fileName + '...'
        fp1 = open(fileName, 'rb') # Open the local obfuscated _CFLn file
        fp2 = open(objName, 'wb') # Open the cleartext file to be created alongside the encrypted version
        DecryptFile(fp1, fp2, key)
        fp1.close()
        fp2.close()

Source prettified by http://pygments.org