# # /* zx8 (ps) - Stream Cipher Algorithm/Source Code */ # # /* # Copyright (c) 2012, Karl-Uwe Frank # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # #** zx8 (ps) algorithm developed by Karl-Uwe Frank # */ import sys, os, commands, random, math from hashlib import md5, sha1 from time import time import ctypes ###from ctypes import cdll # # gcc -Wall -Wextra -O -std=c99 -pedantic -shared zx8_b1_struct_cy.c -o zx8_b1_struct_cy.so # zx8 = ctypes.cdll.LoadLibrary('./zx8_b1_struct_cy.so') # ----------------------------------------- # # Global Varaiable Definition # # Secret State Arrays z = bytearray(256) x = bytearray(256) # Global Carry on Array Indices a = int(0) b = int(0) #----------------------------------------- # def ShowUsage(ThisName): printf ("\nUsage: %s -e InFile OutFile for encryption\n" % ThisName) printf ("Usage: %s -d InFile OutFile for decryption\n\n" % ThisName) #----------------------------------------- # # Swap Values # # (primitive Way for some Kind of ANSI-C # Compatibility when comparing Listings) # def swap(X,Y): return Y, X #----------------------------------------- # Simulate missing ANSI-C "printf" # def printf(*args): sys.stdout.write(*args) sys.stdout.flush() def print_stderr(*args): sys.stderr.write(*args) sys.stderr.flush() #----------------------------------------- # # XOR calculate two Hex Strings # of same Length # def XOR(Hex_1, Hex_2): h1 = 0; h2 = 0; rv = '' h_len = len(Hex_1)/2 for i in range(0,h_len): h1 = int(Hex_1[i*2:(i*2)+2], 16) h2 = int(Hex_2[i*2:(i*2)+2], 16) rv += '%02x' % (h1 ^ h2) return rv # ----------------------------------------- # # Read Keyword # def ReadKeyword(KeyWord, KeyLen, PassedKey, Direction): KeyGet = KeyComp = str('') n = c = int(0) if (PassedKey): # If passed as Parameter for i in range(len(PassedKey)): KeyWord[i] = ord(PassedKey[i]) KeyLen = len(PassedKey) else: from getpass import getpass KeyGet = getpass("Enter Keyword: ") if (Direction == "-e"): KeyComp = getpass("Enter Keyword again: ") if ( KeyGet != KeyComp): print("\nEntered Keywords did not match\n") sys.exit() KeyLen = len(KeyGet) if (KeyLen < 8): print("\nKeyword must be at least 8 unsigned characters long\n") sys.exit() for i in range(0,KeyLen): KeyWord[i] = ord(KeyGet[i]) # ----------------------------------------- # # Open Input File # def OpenInFile(InFile): try: fIn = open(InFile, "rb") except IOError: print("error opening file: %s\n" % InFile) sys.exit() return fIn # ----------------------------------------- # # Create Output File # def NewOutFile(OutFile): try: fOut = open(OutFile, "w+b"); except IOError: print("error creating file: %s\n" % OutFile) sys.exit() return fOut # ----------------------------------------- # HMAC MD5 # # H(K XOR opad, H(K XOR ipad, text)) # # ipad = 0x36, opad = 0x5c # def hmac_md5(key, message): blocksize = md5().block_size key_str = str('') inner_pad = bytearray(blocksize) outer_pad = bytearray(blocksize) # Pass a possible keyarray into a String # because len(key) will always give # size of the bytearray as result if (type(key) == bytearray): for i in range(0,len(key)): if (key[i] > 0): key_str += chr(key[i]) else: key_str = key # If the Key is to long use it's Hash if (len(key_str) > blocksize): key_str = md5(key_str).digest() # If the Key is to short fill it up with Zero if (len(key_str) < blocksize): for i in range(0,(blocksize - len(key_str))): key_str += chr(0x00) # Generate the Pads for i in range(0,blocksize): inner_pad[i] = 0x36 outer_pad[i] = 0x5c # XOR the Key against the Pads for i in range(0,len(key_str)): inner_pad[i] = (inner_pad[i] ^ ord(key_str[i])) outer_pad[i] = (outer_pad[i] ^ ord(key_str[i])) # Return the HMAC return md5(outer_pad + md5(inner_pad + message).digest()) #----------------------------------------- # # Password based Key Derivation Function # def PBKDF2_md5(Password, salt, c, dkLen): hLen = 16 # Length of MD5 digest blockCount = int(math.ceil(dkLen / hLen))+2 # (+2 for Python) T_i = U_i = DK = str('') for i in range(1, blockCount): # Encoded "i" as 4 Bytes (Big Endian) i4Byte = bytearray(4) i4Byte[0] = ((i >> 24) & 0xff) i4Byte[1] = ((i >> 16) & 0xff) i4Byte[2] = ((i >> 8) & 0xff) i4Byte[3] = ( i & 0xff) U_i = salt + i4Byte # Perform the first Iteration T_i = hmac_md5(Password, U_i).digest() U_i = T_i # Perform the remaining Iterations for j in range(1, c): U_i = hmac_md5(Password, U_i).digest() T = str('') for k in range(0, hLen): T += chr(ord(T_i[k]) ^ ord(U_i[k])) # Pass over the temp Var T_i = T # Concatenate the finaly derived Key DK = DK + T_i return DK[0:dkLen] #----------------------------------------- # # Calculate the MD5 Hash of a File # def md5digestFile(fDigestFile, SeekPos, MaxByte): BLOCKSIZE = 8192 inBuffer = bytearray(BLOCKSIZE) returnHash = md5() # Set EOF if no value other was passed if (MaxByte == 0): fDigestFile.seek(0,2) MaxByte = fDigestFile.tell() fDigestFile.seek(0,0) # Seek File Pointer fDigestFile.seek(SeekPos) # Buffered Read while MaxByte > 0: if (BLOCKSIZE > MaxByte): BLOCKSIZE = MaxByte inBuffer = fDigestFile.read(BLOCKSIZE) returnHash.update(inBuffer) MaxByte = (MaxByte - len(inBuffer)) return returnHash #----------------------------------------- # # Calculate the MD5 Hash of a File # def md5_FileHash(HashFile): BLOCKSIZE = 8192 # 1 MByte inBuffer = bytearray(BLOCKSIZE) returnHash = md5() try: fhash = open(HashFile, "rb") except IOError: print("error opening file: %s\n" % HashFile) ###sys.exit() # Hash the entire File inBuffer = fhash.read(BLOCKSIZE) while len(inBuffer) > 0: returnHash.update(inBuffer) inBuffer = fhash.read(BLOCKSIZE) fhash.close() return returnHash #----------------------------------------- # # Key Schedule Algorithm (ps) # def KSA(keyString, bVal=0): global z, x, a, b i = j = k = n = t = int(0) a = int(0) b = int(0) Key = bytearray(256) KeyLen = len(keyString) # Fill the Key Array from the passed Key String for i in range(0,KeyLen): Key[i] = ord(keyString[i]) # Prefill the Arrays for i in range(256): z[i] = i x[i] = i for i in range(256): k = (i % KeyLen) for n in range(128): t = PRGA() j = (t + j + z[i] + Key[k]) % 256 z[i], z[j] = swap(z[i], z[j]) for n in range(128): t = PRGA() j = (t + j + x[i] + z[x[j]]) % 256 x[i], x[j] = swap(x[i], x[j]) # Reset the Array Indices Start Point a = int(0) b = bVal # b=1 del(Key) del(keyString) del(KeyLen) #----------------------------------------- # # Pseudo Random Generation Algorithm (ps) # def PRGA(): global z, x, a, b n1 = n2 = y = m = bytearray(1) # Calculate distant Array Element Indices n1 = (z[a] + x[a]) % 256 n2 = (z[b] + x[b]) % 256 # First Swap randomly selected Array Element z[a], z[n1] = swap(z[a], z[n1]) x[a], x[n2] = swap(x[a], x[n2]) # Update the global Carry on Array Indices a = (a + b + (n1^n2)) % 256 b = (b + 1) % 256 # Second Swap sequentially cycle over every Array Element z[b], z[n1] = swap(z[b], z[n1]) x[b], x[n2] = swap(x[b], x[n2]) # Calculate the internal State Selector Value y = (z[n1] ^ x[n2]) % 256 # Calculate the internal State Protection Value m = (n1 + n2) % 256 # Never reveal internal State Values directly return (z[x[y]] ^ m) % 256 #----------------------------------------- # # Calculate the IV # def IV__MD5_file(KeyWord, nKeyByte, InFile, nDropKSA, KDF_Rounds, IV_Length, bVal): K = KeyWord P_h = str('') seed = str('') salt = str('') time_str = str('') IV = str('') R = str('') FileInfo = str('') NetInfo = str('') # Because we can not trust Windows # gather as much different Information as possible # for generating the IV from statinfo = os.stat(InFile) FileInfo = InFile + str(statinfo.st_size) + str(statinfo.st_atime) + str(statinfo.st_mtime) + str(statinfo.st_ctime) if (os.name == 'posix'): NetInfo = commands.getoutput("ifconfig") else: NetInfo = commands.getoutput("ifconfig /all") FileInfo = md5(FileInfo).digest() NetInfo = md5(NetInfo).digest() # Random Value on MacOSX or Linux from /dev/urandom # on Windoofs from RtlGenRandom <== call "RtlGenRandom", an undocumented function # that's been part of the Windows cryptography suite since Windows XP SP 3. R = os.urandom(16) # Use time with Millisecond Precision time_str = md5(str(time()) + str('%f' % (time()/100))).digest() # P_h = MD5(P) P_h = md5_FileHash(InFile).digest() # seed = HMAC(K, P_h || time || R) seed = hmac_md5(P_h, FileInfo + NetInfo + time_str + R).digest() + R # Calculate IV # IV = KBF(RC4, seed, drop, dkLen*) IV = KBF(seed, (nDropKSA*256), nKeyByte, bVal) del(K) del(seed) del(time_str) return IV #----------------------------------------- # # Keystream Byte capture Function # # KBF(RC4, seed, 1024, dkLen) # def KBF(seed, drop, dkLen, bVal): global z, x, a, b DropByte = str('') keystreamString = str('') # Initialise the Ciphers internal State Arrays KSA(seed, bVal) # Perform Keystream Drop for i in range(0,drop): DropByte = chr(PRGA()) # Capture the Keystream Bytes for i in range(0,dkLen): keystreamString += chr(PRGA()) # Drop KeyWord and seed to prevent # memory trade off attacks del(seed) # Return keystream Bytes as String return keystreamString #----------------------------------------- # # Calculate the two different Keys # def DeriveKeys(KeyWord, IV, Crypto_Key, MAC_Key, nKeyByte, nDropKSA, KDF_Rounds, bVal): global z, x, a, b iterations = (KDF_Rounds*256) # # Calculate the unique HMac of Keyword and IV # keyString = PBKDF2_md5(KeyWord, IV, iterations, nKeyByte) # Using the Python-style Algorithm of zx8 (ps) # here because the cType-style throw memory errors # # Keep a good Distance between Keys Crypto_Key = KBF(keyString, (nDropKSA*256) , nKeyByte, bVal) MAC_Key = KBF(keyString, (nDropKSA*256)*2, nKeyByte, bVal) # Free all unused Cipher Elements and # drop the Internal State to prevent # memory trade off Attacks del(keyString) del(z) del(x) del(a) del(b) # Re-Initialise the Cipher # with the derived symmetric Encryption Key # zx8.KSA.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_int] zx8.KSA(Crypto_Key, len(Crypto_Key), bVal) del(Crypto_Key) # MAC_Key used later return MAC_Key # ----------------------------------------- # # Get the Message Authentication Code # def CompareMAC(InFile, inFileSize, MAC_Key, nMACsize): MAC = str('') try: fInMAC = open(InFile, "rb"); except IOError: print("error opening file: %s\n" % fInFile) ###sys.exit() fInMAC.seek(-nMACsize,2) for i in range(0,nMACsize): MAC += fInMAC.read(1) MAC = MAC.encode('hex') # Seek to Begin of File # and Hash IV and encrypted Data fInMAC.seek(0,0) # BinaryStream, SeekPos, MaxByte MD5Result = md5digestFile(fInMAC, 0, inFileSize).digest() # Calculate HMAC using intermediate Key MACverify = hmac_md5(MAC_Key, MD5Result).hexdigest() fInMAC.close() # Verify that the File is untampered if (MAC != MACverify): printf("\nATTENTION!\n\nThe Message Authentication Code is incorrect.\nDecryption aborted.\n\n") sys.exit(1) # ----------------------------------------- # Call the symmetric Encryption Routine # def Encrypt_Decrypt(InFile, OutFile, inFileSize, inFileSeekPos, MAC_Key, Direction): # # Use the external shared Library # zx8.Encrypt_file.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int] zx8.Encrypt_file(InFile, OutFile, inFileSize, inFileSeekPos) # # If we Encrypt then generate and # concatenate the corresponding MAC # if (Direction == "-e"): # need to open the file for reading first # in order to calculate the MD5 of IV+Ciphertext try: fOutMAC = open(OutFile, "rb"); except IOError: print("error opening file: %s\n" % OutFile) ###sys.exit() # Seek to Begin of the File # and Hash IV and encrypted Data fOutMAC.seek(0,2) MaxByte = fOutMAC.tell() fOutMAC.seek(0,0) ###print "fOutMAC.length = "+ str(MaxByte) # BinaryStream, SeekPos, MaxByte MD5Result = md5digestFile(fOutMAC, 0, MaxByte).digest() # close here fOutMAC.close() # Calculate HMAC using intermediate Key MAC = hmac_md5(MAC_Key, MD5Result).digest() # re-open the file in order to append the MAC try: fOutMAC = open(OutFile, "ab"); except IOError: print("error opening file: %s\n" % OutFile) ###sys.exit() # Seek to End of the File # and add the Message Authentication Code (MAC) fOutMAC.seek(0,2) for i in range(0,len(MAC)): fOutMAC.write(MAC[i]) fOutMAC.close()