def decrypt_stream(instream, outstream, password=None, private_key=None, public_key=None): session_key = None decryptor = None decrypt_stream.md5_digestor = None # special kind of local variable... expected_md5_digest = None def outstream_writer_and_md5_digestor(decompressed_chunk): outstream.write(decompressed_chunk) if decrypt_stream.md5_digestor != None: decrypt_stream.md5_digestor.update(decompressed_chunk) # create session key and decryptor header = read_header(instream) if not header: LOGGER.info('failed to parse header; skipping file...') return # TODO: assert version and hash algo decrypt_stream.md5_digestor = hashlib.md5() if password != None: # password actual_password_hash = salted_hash_of(header['key1_hash'][:10], password) if header['key1_hash'] != actual_password_hash: LOGGER.warning('found key1_hash %s but expected %s', actual_password_hash, header['key1_hash']) session_key = decrypted_with_password( base64.b64decode(header['enc_key1'].encode('ascii')), password, header['salt'].encode('ascii')) decryptor = decryptor_with_password(session_key) elif private_key != None and public_key != None: # RSA actual_public_key_hash = salted_hash_of(header['key2_hash'][:10], public_key) if header['key2_hash'] != actual_public_key_hash: LOGGER.warning('found key2_hash %s but expected %s', actual_public_key_hash, header['key2_hash']) session_key = decrypted_with_private_key( base64.b64decode(header['enc_key2'].encode('ascii')), private_key) decryptor = decryptor_with_password(session_key) else: raise Exception("Key material is not found") actual_session_key_hash = salted_hash_of(header['session_key_hash'][:10], session_key) if header['session_key_hash'] != actual_session_key_hash: LOGGER.warning('found session_key_hash %s but expected %s', actual_session_key_hash, header['session_key_hash']) # decrypt chunks data = b'' with util.Lz4Decompressor( decompressed_chunk_handler=outstream_writer_and_md5_digestor ) as decompressor: for chunk in read_chunks(instream): if chunk['type'] == 'metadata': # decrypt file decrypted_data = decryptor_update(decryptor, data) decompressor.write(decrypted_data) # expected_md5_digest = chunk['file_md5'] break elif chunk['type'] == 'data': data += chunk['data'] else: raise Exception("Bad chunk found: " + str(chunk)) # verify md5 if decrypt_stream.md5_digestor != None and expected_md5_digest != None: actual_md5_digest = decrypt_stream.md5_digestor.hexdigest() if actual_md5_digest != expected_md5_digest: raise Exception('expected md5 digest %s but found %s', expected_md5_digest, actual_md5_digest)
def lz4_uncompress(compressed_data): result = io.BytesIO() with util.Lz4Decompressor(decompressed_chunk_handler=result.write) as decompressor: decompressor.write(compressed_data) return result.getvalue()
def decrypt_stream(instream, outstream, password=None, private_key=None): session_key = None decryptor = None decrypt_stream.md5_digestor = None # special kind of local variable... expected_md5_digest = None enc_key1_bytes = None enc_key2_bytes = None salt = b'' session_key_hash = None def outstream_writer_and_md5_digestor(decompressed_chunk): outstream.write(decompressed_chunk) if decrypt_stream.md5_digestor != None: decrypt_stream.md5_digestor.update(decompressed_chunk) with util.Lz4Decompressor(decompressed_chunk_handler=outstream_writer_and_md5_digestor) as decompressor: for (key,value) in decode_csenc_stream(instream): for case in switch(key): if case('digest'): if value != 'md5': LOGGER.warning('found unexpected digest "%s": cannot verify checksum', value) decrypt_stream.md5_digestor = hashlib.md5() break if case('enc_key1'): enc_key1_bytes = base64.b64decode(value.encode('ascii')) break if case('enc_key2'): enc_key2_bytes = base64.b64decode(value.encode('ascii')) break if case('key1_hash'): if password != None: actual_password_hash = salted_hash_of(value[:10], password) if value != actual_password_hash: LOGGER.warning('found key1_hash %s but expected %s', actual_password_hash, value) break if case('key2_hash'): # TODO: verify some public/private key pair hash here break if case('salt'): salt = value.encode('ascii') assert isinstance(salt, bytes) break if case('session_key_hash'): session_key_hash = value break if case('version'): version = value expected_version_numbers = [OrderedDict([('major',1),('minor',0)]), OrderedDict([('major',3),('minor',0)])] if version not in expected_version_numbers: raise Exception('found version number ' + str(value) + \ ' instead of one of the expected ' + str(expected_version_numbers)) if (version['major'] > 1) != (salt != b''): version_string = '%d.%d' % (version['major'], version['minor']) LOGGER.warning('salt is expected in version 3+ (version: %s, salt present: %s)', version_string, (salt != b'')) break if case(None): if decryptor == None: if password != None and enc_key1_bytes != None: session_key = decrypted_with_password(enc_key1_bytes, password, salt) elif private_key != None and enc_key2_bytes != None: session_key = decrypted_with_private_key(enc_key2_bytes, private_key) if session_key == None: raise Exception('not enough information to decrypt data: need either password and enc_key1 or private key and enc_key2') if session_key_hash == None: LOGGER.warning('did not find session_key_hash to verify the session key') else: actual_session_key_hash = salted_hash_of(session_key_hash[:10], session_key) if session_key_hash != actual_session_key_hash: LOGGER.warning('found session_key_hash %s but expected %s', actual_session_key_hash, session_key_hash) decryptor = decryptor_with_password(binascii.unhexlify(session_key) if salt else session_key, salt=b'') decrypted_chunk = decryptor_update(decryptor, value) decompressor.write(decrypted_chunk) break if case('file_md5'): expected_md5_digest = value break if decrypt_stream.md5_digestor != None and expected_md5_digest != None: actual_md5_digest = decrypt_stream.md5_digestor.hexdigest() if actual_md5_digest != expected_md5_digest: raise Exception('expected md5 digest %s but found %s', expected_md5_digest, actual_md5_digest)
def decrypt_stream(instream, outstream, password=None, private_key=None): session_key = None decryptor = None decrypt_stream.md5_digestor = None # special kind of local variable... expected_md5_digest = None def outstream_writer_and_md5_digestor(decompressed_chunk): outstream.write(decompressed_chunk) if decrypt_stream.md5_digestor != None: decrypt_stream.md5_digestor.update(decompressed_chunk) with util.Lz4Decompressor(decompressed_chunk_handler=outstream_writer_and_md5_digestor) as decompressor: for (key,value) in decode_csenc_stream(instream): for case in switch(key): if case('digest'): if value != 'md5': LOGGER.warning('found unexpected digest "%s": cannot verify checksum', value) decrypt_stream.md5_digestor = hashlib.md5() break if case('enc_key1'): if password != None: session_key = decrypted_with_password(base64.b64decode(value.encode('ascii')), password) decryptor = decryptor_with_password(session_key) break if case('enc_key2'): if private_key != None: session_key = decrypted_with_private_key(base64.b64decode(value.encode('ascii')), private_key) decryptor = decryptor_with_password(session_key) break if case('key1_hash'): if password != None: actual_password_hash = salted_hash_of(value[:10], password) if value != actual_password_hash: LOGGER.warning('found key1_hash %s but expected %s', actual_password_hash, value) break if case('key2_hash'): # TODO: verify some public/private key pair hash here break if case('session_key_hash'): if session_key != None: actual_session_key_hash = salted_hash_of(value[:10], session_key) if value != actual_session_key_hash: LOGGER.warning('found session_key_hash %s but expected %s', actual_session_key_hash, value) break if case('version'): expected_version_number = OrderedDict([('major',1),('minor',0)]) if value != expected_version_number: raise Exception('found version number ' + str(value) + \ ' instead of expected ' + str(expected_version_number)) break if case(None): if decryptor == None: raise Exception('not enough information to decrypt data') decrypted_chunk = decryptor_update(decryptor, value) decompressor.write(decrypted_chunk) break if case('file_md5'): expected_md5_digest = value break if decrypt_stream.md5_digestor != None and expected_md5_digest != None: actual_md5_digest = decrypt_stream.md5_digestor.hexdigest() if actual_md5_digest != expected_md5_digest: raise Exception('expected md5 digest %s but found %s', expected_md5_digest, actual_md5_digest)