Created
November 8, 2016 21:11
-
-
Save andreashove/a43fba7bbe02670112f7c21de48ea9ae to your computer and use it in GitHub Desktop.
Simple feistel cipher implementation based on DES
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| __author__ = 'andreas.hove' | |
| from collections import deque | |
| # This script enciphers and deciphers text using a Feistel cipher implementation, inspiration by DES. | |
| # | |
| # author: Andreas Hove | |
| # course: DAT510 | |
| # instructor: Chunlei Li | |
| def main(): | |
| """ | |
| - Queries input from user to be used in encryption and decryption. | |
| - Executes encryption and decryption | |
| - Prints results to console | |
| """ | |
| print("########################") | |
| print("# #") | |
| print("# FEISTEL CIPHER #") | |
| print("# #") | |
| print("########################\n") | |
| print("NOTE: Remember the codeword and number you supply. You will be prompted for it again.\n") | |
| print("Please enter your codeword (at least 8 characters)") | |
| codeword = input("Codeword: ") | |
| print("\nPlease enter your numerical key code (code > 0)") | |
| seed = input("Key code: ") | |
| seed = int(seed) | |
| print("\nEnter plaintext to encrypt (at least 8 characters) ") | |
| text = input("Plaintext: ") | |
| blocksize = 64 | |
| rounds = 32 | |
| key = tobits(codeword) | |
| subkeys = subkeyGenerator(key, seed, rounds) | |
| encryptedBlocks = encryption(text, blocksize, rounds, subkeys) | |
| print("\nEncrypted text given in bits:") | |
| for blocks in encryptedBlocks: | |
| print("".join(str(n) for n in blocks), end="") | |
| print("\n") | |
| print("To decrypt the message, please enter the ciphertext in bits (avoid any spaces in the input") | |
| encryptedbits = input("Ciphertext:") | |
| encryptedbits = tobits(encryptedbits) | |
| print("\nPlease re-enter your codeword") | |
| codeword = input("Codeword: ") | |
| print("\nRe-enter the same numerical key code (code > 0)") | |
| seed = input("Key code: ") | |
| seed = int(seed) | |
| key = tobits(codeword) | |
| subkeys = subkeyGenerator(key, seed, rounds) | |
| decryptedBits = decryption(encryptedbits, blocksize, rounds, subkeys) | |
| try: | |
| print("\nPlaintext: {}".format(frombits(decryptedBits, 8)), end="") | |
| except UnicodeEncodeError: | |
| print("\nERROR: Decryption failed\n") | |
| print("\n") | |
| input("Press any key to quit") | |
| return | |
| def convert_string_to_bits(string): | |
| """ | |
| Converts each character in a string to bits, and returns a list of binary integers. | |
| Original code from: http://stackoverflow.com/questions/10237926/convert-string-to-list-of-bits-and-viceversa | |
| Modified to fit this implementation. | |
| """ | |
| result = [] | |
| for c in string: | |
| bits = bin(ord(c))[2:] | |
| bits = '00000000'[len(bits):] + bits # since all characters are 8 bit, bits is set to '00000000' | |
| result.extend([int(b) for b in bits]) | |
| return result | |
| def tobits(string): | |
| """ Determines the format of input (string or int) and then the string is converted.""" | |
| result = [] | |
| # text is ints | |
| try: | |
| if int(string): | |
| # text is int, but not binary | |
| if not all(x in "01" for x in string): | |
| result = convert_string_to_bits(string) | |
| return result | |
| # text is binary | |
| for b in string: | |
| result.append(int(b)) | |
| return result | |
| # text is not binary | |
| except ValueError: | |
| pass | |
| # each character in 's' is converted to 8-bit block which is appended to the result | |
| result = convert_string_to_bits(string) | |
| return result | |
| def frombits(block, blocksize): | |
| """ | |
| Takes in a block of arbitrary length and converts blocks of 8 bit to characters which is then returned. | |
| This code is from: http://stackoverflow.com/questions/10237926/convert-string-to-list-of-bits-and-viceversa | |
| """ | |
| chars = [] | |
| try: | |
| for b in range(len(block) // blocksize): | |
| byte = block[b*blocksize:(b+1)*blocksize] | |
| chars.append(chr(int(''.join([str(bit) for bit in byte]), 2))) | |
| except UnicodeEncodeError: | |
| print("'N/A'") | |
| return ''.join(chars) | |
| def p32box(key): | |
| """ | |
| Takes a 32 bit key as input, permutes each bit and then returns the new key | |
| Permutation boxes is discussed on Page 3 of report. | |
| """ | |
| perm = list([32, 24, 11, 31, 13, 8, 19, 22, | |
| 12, 3, 4, 29, 21, 10, 28, 26, | |
| 30, 23, 7, 16, 20, 17, 18, 2, | |
| 6, 25, 5, 15, 9, 14, 27, 1]) | |
| newKey = list([0]*32) | |
| for i in range(0,32): | |
| newKey[i] = key[perm[i]-1] | |
| return newKey | |
| def pc1(key): | |
| """Permutation Choice 1. Permutation boxes is discussed on Page 3 of report.""" | |
| perm = list([57,49,41,33,25,17,9, | |
| 1,58,50,42,34,26,18, | |
| 10,2,59,51,43,35,27, | |
| 19,11,3,60,52,44,36, | |
| 63,55,47,39,31,23,15, | |
| 7,62,54,46,38,30,22, | |
| 14,6,61,53,45,37,29, | |
| 21,13,5,28,20,12,4]) | |
| cKey = list([0]*28) | |
| dKey = list([0]*28) | |
| for i in range(0,28): | |
| cKey[i] = key[perm[i]-1] | |
| for i in range(28,56): | |
| dKey[i-28] = key[perm[i]-1] | |
| return cKey, dKey | |
| def pc2(key): | |
| ''' | |
| Permuted Choice 2 - 56 bit key is permuted and reduced to 48 bit | |
| Permutation boxes is discussed on Page 3 of report. | |
| ''' | |
| perm = list([14,17,11,24,1,5 | |
| ,3,28,15,6,21,10, | |
| 23,19,12,4,26,8, | |
| 16,7,27,20,13,2, | |
| 41,52,31,37,47,55, | |
| 30,40,51,45,33,48, | |
| 44,49,39,56,34,53, | |
| 46,42,50,36,29,32]) | |
| newKey = list([0]*48) | |
| for i in range(0,48): | |
| newKey[i] = key[perm[i]-1] | |
| return newKey | |
| def ipbox(key): | |
| """ | |
| Initial permutation. Takes 64 bit key as input which is permuted using the permution table. | |
| Permutation boxes is discussed on Page 3 of report. | |
| """ | |
| perm = list([58,50,42,34,26,18,10,2, | |
| 60,52,44,36,28,20,12,4, | |
| 62,54,46,38,30,22,14,6, | |
| 64,56,48,40,32,24,16,8, | |
| 57,49,41,33,25,17,9,1, | |
| 59,51,43,35,27,19,11,3, | |
| 61,53,45,37,29,21,13,5, | |
| 63,55,47,39,31,23,15,7]) | |
| newKey = list([0]*len(key)) | |
| for i in range(0,len(key)): | |
| newKey[i] = key[perm[i]-1] | |
| return newKey | |
| def invipbox(key): | |
| """ Inverse initial permutation. Permutation boxes is discussed on Page 3 of report.""" | |
| invperm = list([40, 8, 48, 16, 56, 24, 64, 32, | |
| 39, 7, 47, 15, 55, 23, 63, 31, | |
| 38, 6, 46, 14, 54, 22, 62, 30, | |
| 37, 5, 45, 13, 53, 21, 61, 29, | |
| 36, 4, 44, 12, 52, 20, 60, 28, | |
| 35, 3, 43, 11, 51, 19, 59, 27, | |
| 34, 2, 42, 10, 50, 18, 58, 26, | |
| 33, 1, 41, 9, 49, 17, 57, 25]) | |
| newKey = list([0]*64) | |
| for i in range(0,64): | |
| newKey[i] = key[invperm[i]-1] | |
| return newKey | |
| def expansion(key): | |
| """ | |
| Expanding key from 32 to 48 bit | |
| Discussed on page 4 of the report. | |
| """ | |
| size = 4 | |
| newBlcoks = [] | |
| firstBlock = [] | |
| firstBlock.append(key[-1]) | |
| for i in range(0,5): | |
| firstBlock.append(key[i]) | |
| newBlcoks.append(firstBlock) | |
| startvalue1 = -1 | |
| startvalue2 = 5 | |
| for i in range(1,8): | |
| block = key[startvalue1+(i*size):startvalue2+(i*size)] | |
| if i == 7: | |
| block.append(key[0]) | |
| newBlcoks.append(block) | |
| result = [] | |
| for block in newBlcoks: | |
| for bit in block: | |
| result.append(bit) | |
| return result | |
| def sbox(key): | |
| ''' | |
| Substitution box: Takes a key, splits it, and substitutes each part against the declared s-boxes. | |
| Discussed on Page 3 of report. | |
| ''' | |
| s1 = list([14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7, | |
| 0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8, | |
| 4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0, | |
| 15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13 | |
| ]) | |
| s2 = list([15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10, | |
| 3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5, | |
| 0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15, | |
| 13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9 | |
| ]) | |
| s3 = list([10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8, | |
| 13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1, | |
| 13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7, | |
| 1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12 | |
| ]) | |
| s4 = list([7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15, | |
| 13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9, | |
| 10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4, | |
| 3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14 | |
| ]) | |
| s5 = list([2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9, | |
| 14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6, | |
| 4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14, | |
| 11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3 | |
| ]) | |
| s6 = list([12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11, | |
| 10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8, | |
| 9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6, | |
| 4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13 | |
| ]) | |
| s7 = list([4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1, | |
| 13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6, | |
| 1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2, | |
| 6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12 | |
| ]) | |
| s8 = list([13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7, | |
| 1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2, | |
| 7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8, | |
| 2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11]) | |
| rowkvp = {'00':1, '01':2, '10':3, '11':4} | |
| colkvp = {'0000':0, '0001':1, '0010':2, '0011':3, '0100':4, | |
| '0101':5, '0110':6, '0111':7, '1000':8, '1001':9, | |
| '1010':10,'1011':11,'1100':12,'1101':13,'1110':14,'1111':15} | |
| sboxlist = [] | |
| sboxlist.append(s1); sboxlist.append(s2); sboxlist.append(s3); sboxlist.append(s4) | |
| sboxlist.append(s5); sboxlist.append(s6); sboxlist.append(s7); sboxlist.append(s8) | |
| keyStr = ''.join(str(val) for val in key) | |
| blocksize = 6 | |
| blocks = [] | |
| for b in range((len(keyStr) // blocksize)): # // forces integer division | |
| blocks.append(keyStr[b*blocksize:(b+1)*blocksize]) | |
| newKey = [] | |
| for i in range(0,len(blocks)): | |
| fst = blocks[i][0]; snd = blocks[i][1]; trd = blocks[i][2] | |
| fth = blocks[i][3]; fih = blocks[i][4]; six = blocks[i][5] | |
| rowvalue = fst+six | |
| colvalue = snd+trd+fth+fih | |
| chosenrow = rowkvp[rowvalue] | |
| chosencol = colkvp[colvalue] | |
| # sbox number i, and column*row value because it is a list and not a matrix | |
| outputVal = sboxlist[i][chosencol*chosenrow] | |
| outputVal = format(outputVal, '04b') | |
| outputVal = list(outputVal) | |
| for bit in outputVal: | |
| newKey.append(int(bit)) | |
| return newKey | |
| def prng(seed, rounds): | |
| """ | |
| Pseudo Random Number Generator which uses the Linear congruential generator | |
| Creating a list of pseudo random keys, one for each round in roundfunction() | |
| Discussed on Page 3 of report. | |
| """ | |
| X_n = seed | |
| m = 2**32 | |
| c = 1013904223 | |
| a = 1664525 | |
| randkeys = [] | |
| for i in range(0,rounds+1): | |
| X_n_1 = (a*X_n + c) % m | |
| X_n = X_n_1 | |
| randkeys.append(X_n_1) | |
| result = [] | |
| for key in randkeys: | |
| newkey = tobits(str(key)) | |
| result.append(newkey) | |
| return result | |
| def subkeyGenerator(key, seed, rounds): | |
| """ | |
| Generates subkeys from input key and seed from user. | |
| Discussed on Page 2 of report. | |
| """ | |
| prn = prng(seed, rounds) # generate pseudo random numbers | |
| ckey, dkey = pc1(key) # 64 to 56 (28 + 28) | |
| cRotate = deque(ckey) | |
| dRotate = deque(dkey) | |
| keyVal = {1:1, 2:1, 3:2, 4:2, 5:2, 6:2, 7:2, 8:2, 9:1, 10:2, 11:2, 12:2, 13:2, 14:2, 15:2, 16:1, | |
| 32:1, 31:1, 30:2, 29:2, 28:2, 27:2, 26:2, 25:2, 24:1, 23:2, 22:2, 21:2, 20:2, 19:2, 18:2, 17:1} | |
| subkeyList = [] | |
| for i in range(1,rounds+1): | |
| cRotate.rotate(keyVal[i]) # left shift key | |
| dRotate.rotate(keyVal[i]) | |
| ckey = list(cRotate) | |
| dkey = list(dRotate) | |
| key = ckey # 28 bit | |
| for bit in dkey: | |
| key.append(bit) # 56 bit | |
| key = pc2(key) # 48 bit | |
| key = xor(key, prn[i]) # xor the key with a prn | |
| subkeyList.append(key) # add the sub key to the list of sub keys | |
| return subkeyList | |
| # if key is less than 64 bits, the xor is performed 0 - n and then starts from 0 again. Secure? | |
| def xor(block, key): | |
| """ Perform an XOR operation with a block and a key """ | |
| count = 0 | |
| msg = [] | |
| keysize = len(key) | |
| for bit in block: | |
| result_bit = bit ^ key[count] | |
| count += 1 | |
| if count == keysize: | |
| count = 0 | |
| msg.append(result_bit) | |
| return msg | |
| def split_block(block): | |
| """ Splits a block in two halves which is returned """ | |
| half = len(block)//2 | |
| return block[:half], block[half:] | |
| def function(block, subkey): | |
| """ | |
| Block is first expanded from 32 to 48 bit. | |
| Block is XOR'ed with a subkey | |
| Block is substituted and reduced from 48 to 32 bit. | |
| Block is permuted in a permutation box | |
| """ | |
| block = expansion(block) # 32 to 48 | |
| block = xor(block, subkey) # 48 | |
| block = sbox(block) # 48 to 32 | |
| block = p32box(block) | |
| return block | |
| def roundfunction(block, subkey): | |
| """ Roundfunction. See report for explanation """ | |
| lBlock, rBlock = split_block(block) | |
| newLblock = rBlock | |
| rBlock = function(rBlock, subkey) | |
| rBlock = xor(lBlock, rBlock) | |
| lBlock = newLblock | |
| result = lBlock | |
| for c in rBlock: | |
| result.append(c) | |
| return result | |
| def encryption(string, blocksize, rounds, subkeys): | |
| """ Master method for encryption. Discussed on Page 3 of report.""" | |
| block = tobits(string) | |
| bitblocks = [] | |
| for b in range((len(block) // blocksize)): # split blocks into block size | |
| bitblocks.append(block[b*blocksize:(b+1)*blocksize]) | |
| # if the plaintext input from user is not modulo blocksize, | |
| # then the last block is less than blocksize and needs padding. | |
| if (len(block) % 64) != 0: | |
| length = len(block) # get length of block | |
| lastblock = block[(b+1)*blocksize:length] # create block of the actual size (less than block size) | |
| createlast = [] | |
| emptyblock = [0]*(blocksize - len(lastblock)) # create block of leading zeroes | |
| for i in emptyblock: | |
| createlast.append(emptyblock[i]) | |
| for bit in lastblock: | |
| createlast.append(bit) # add the bits of the last block to the block with leading zeroes | |
| bitblocks.append(createlast) | |
| encryptedBlocks = [] | |
| for block in bitblocks: | |
| block = ipbox(block) # initial permutation of the block | |
| for i in range(0, rounds): | |
| block = roundfunction(block, subkeys[i]) # round function on each block | |
| block = swap_block_halves(block) # final swap of block halves | |
| encryptedBlocks.append(block) # append blocks to the result | |
| return encryptedBlocks | |
| def decryption(encryptedbits, blocksize, rounds, subkeys): | |
| """ Master method for decryption. Discussed on Page 3 of report.""" | |
| decryptedBlocks = [] | |
| encryptedBlocks = [] | |
| for b in range((len(encryptedbits) // blocksize)): # create blocks of the input | |
| encryptedBlocks.append(encryptedbits[b*blocksize:(b+1)*blocksize]) | |
| for block in encryptedBlocks: # run the round function with sub keys in opposite order | |
| for i in range(rounds-1,-1,-1): | |
| block = roundfunction(block, subkeys[i]) | |
| block = swap_block_halves(block) | |
| newblock = invipbox(block) # inverse initial permutation | |
| decryptedBlocks.append(newblock) | |
| lastblock = newblock | |
| # if there are more than one block, we need to operate on the last block to see if it | |
| # really is characters or filled with padded 0's | |
| if len(decryptedBlocks) > 1: | |
| decryptedBlocks.pop(-1) # remove the last block, which is full of zeroes. | |
| popblock = list(lastblock) | |
| for bit in lastblock: | |
| if bit == 1: | |
| break | |
| lastblock = list(popblock) | |
| popblock.pop(bit) | |
| decryptedBlocks.append(lastblock) | |
| decryptedArray = [] | |
| for block in decryptedBlocks: | |
| for bit in block: | |
| decryptedArray.append(bit) | |
| return decryptedArray | |
| def swap_block_halves(block): | |
| """Takes in one block and swaps the two halves.""" | |
| lblock, rblock = split_block(block) | |
| newLBlock = lblock | |
| lblock = rblock | |
| rblock = newLBlock | |
| result = lblock | |
| for bit in rblock: | |
| result.append(bit) | |
| return result | |
| # On startup, the following code is called. | |
| main() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment