#!/usr/bin/env python # # /* HBSC - Hash Based Stream Cipher Algorithm/Source Code */ # # /* # Copyright (c) 2013, 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. # # #** HBSC - Hash Based Stream Cipher Algorithm developed by Karl-Uwe Frank # */ # HBSC (Hash Based Stream Cipher) ** 23.08.2013 ** # # # aH[] 256 * 128 Bit Array of Hash Values # # counter 32 or 64 Bit integer value # # seq_idx 1 Byte sequential index pointer # # rnd_idx 1 Byte pseudo-random index pointer # # KDF_Rounds PBKDF2 Iteration Count # # dkLen Derived Key Length in Byte # # Keystream 128 Bit Return Value from PRGA # # || Concatenating Strings # # # # # KSA (Key Schedule Algorithm) # aH[0] = PBKDF2_md5(Keyword, IV, KDF_Rounds, dkLen) # for i=1 to 255 # aH[i] = HMAC_md5(Keyword, IV || aH[i-1]) # next # # PRGA (Pseudo Random Generation Algorithm) # counter = counter +1 # seq_idx = seq_idx +1 mod256 # rnd_idx = first Byte of aH[seq_idx] # Keystream = md5(aH[seq_idx] || md5(aH[rnd_idx] || counter)) # aH[seq_idx] = md5(aH[seq_idx] || aH[rnd_idx]) # return Keystream # # # Revision: 2013-08-26 # # addjusted conversion difference in the PRGA between ascii and binary # with thanks to Collin on sci.crypt # import sys, os, random, math from hashlib import md5 #----------------------------------------- # 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() #----------------------------------------- # def ShowUsage(ThisName): print_stderr ( "\nUsage : %s IV(in Hex) Key(in Hex) out file\n" % ThisName); print_stderr ( "\nExample: %s 13a775db820bac43e01fe6557ccbe3cd 7d0ef66789aca2cfa6c76db7560554 CipherFile\n" % ThisName); print_stderr ( "\n %s 13a775db820bac43e01fe6557ccbe3cd $(echo -en 'egN99T8eK6peC2UC' | md5) < Plainfile > CipherFile\n" % ThisName); print_stderr ( "\n cat CipherFile | %s 13a775db820bac43e01fe6557ccbe3cd 7d0ef66789aca2cfa6c76db7560554 > Plainfile\n\n" % ThisName); # ----------------------------------------- # 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] ############################################################################################## # # Cipher Algorithm # #----------------------------------------- # # Key Schedule Algorithm # def KSA(KeyWord, IV, KDF_Rounds, dkLen): global hashArray # Calculate the first Array Element of Keyword and IV hashArray.append('') hashArray[0] = PBKDF2_md5(KeyWord, IV, KDF_Rounds, dkLen) for i in range(1,256): # Fill in all remaining Array Elemets using a HMAC hashArray.append('') hashArray[i] = hmac_md5(KeyWord, IV + hashArray[i-1]).digest() #----------------------------------------- # # Pseudo Random Generation Algorithm # def PRGA(): global hashArray, counter, seq_idx, rnd_idx # Update the sequential counter counter = counter +1 seq_idx = (seq_idx +1) % 256 # Update the random index pointer rnd_idx = int(hashArray[seq_idx].encode('hex')[0:2], 16) # Calculate the Keystream Value # // # // addjusted conversion difference between ascii and binary # // with thanks to Collin on sci.crypt # // counter_byte = bytearray(8) counter_byte = '%02x' % counter Keystream = md5(hashArray[seq_idx] + md5(hashArray[rnd_idx] + counter_byte).digest()).hexdigest() # Update the current hashArray Value hashArray[seq_idx] = md5(hashArray[seq_idx] + hashArray[rnd_idx]).digest() return Keystream # # # ############################################################################################## # Define the global cipher array hashArray = [] # Define the global counter, sequential and random index pointer counter = int(0) seq_idx = int(0) rnd_idx = int(0) # ----------------------------------------- # # Main # def main(): # Check if a Prarmeter is passed through PrgName = sys.argv[0] if (len(sys.argv) < 3): ShowUsage(PrgName) sys.exit() # Pass on IV and Keyword IV = sys.argv[1] KeyWord = sys.argv[2] # Declare Variables dkLen = 16 # Byte Length (128 bit) using md5 KDF_Rounds = 2048 # Addjustable # Call the Key Schedule Algorithm KSA(KeyWord, IV, KDF_Rounds, dkLen) BLOCKSIZE = 16 # Byte inBuffer = bytearray(BLOCKSIZE) outBuffer = '' # Encrypt/Decrypt - read from STDIN write to STDOUT inBuffer = sys.stdin.read(BLOCKSIZE) while len(inBuffer) > 0: # Call the Pseudo Random Generation Algorithm keyStream = PRGA() for i in range(len(inBuffer)): outBuffer += chr(ord(inBuffer[i]) ^ int(keyStream[i*2:(i*2)+2], 16)) sys.stdout.write(outBuffer) outBuffer = '' inBuffer = sys.stdin.read(BLOCKSIZE) sys.stdout.flush() # # # ----------------------------------------- try: raise SystemExit(main()) except KeyboardInterrupt: raise SystemExit("Aborted by user request.")