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