Subversion Repositories Code-Repo

Compare Revisions

No changes between revisions

Ignore whitespace Rev 139 → Rev 140

/Classwork/MATH4175 - Cryptography 1/AES Encrypt.py
0,0 → 1,186
# S-box lookup table calculated from the field F_{2^8} = Z_2[x]/x^8+x^4+x^3+x+1
# 0 1 2 3 4 5 6 7 8 9 A B C D E F
s_box = [[0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76],#0
[0XCA,0X82,0XC9,0X7D,0XFA,0X59,0X47,0XF0,0XAD,0XD4,0XA2,0XAF,0X9C,0XA4,0X72,0XC0],#1
[0XB7,0XFD,0X93,0X26,0X36,0X3F,0XF7,0XCC,0X34,0XA5,0XE5,0XF1,0X71,0XD8,0X31,0X15],#1
[0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75],#3
[0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84],#4
[0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF],#5
[0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8],#6
[0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2],#7
[0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73],#8
[0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB],#9
[0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79],#A
[0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08],#B
[0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A],#C
[0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E],#D
[0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF],#E
[0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16]]#F
 
# Key expansion lookup table
RCon = [[0x01,0x00,0x00,0x00],[0x02,0x00,0x00,0x00],[0x04,0x00,0x00,0x00],[0x08,0x00,0x00,0x00],
[0x10,0x00,0x00,0x00],[0x20,0x00,0x00,0x00],[0x40,0x00,0x00,0x00],[0x80,0x00,0x00,0x00],
[0x1B,0x00,0x00,0x00],[0x36,0x00,0x00,0x00]]
 
# Encryption key
encryption_key = [0x2B,0x7E,0x15,0x16,0x28,0xAE,0xD2,0xA6,0xAB,0xF7,0x15,0x88,0x09,0xCF,0x4F,0x3C]
 
# Input plaintext
plaintext = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF]
 
def RotWord(byte_array):
'''Takes a list of bytes and cyclic left shifts it by one.'''
byte_array.append(byte_array.pop(0))
return byte_array
 
def SubBytes(byte_array):
'''Substitutes each byte in the given list with the value in the s-box.'''
for i in range(len(byte_array)):
byte_array[i] = s_box[(byte_array[i]&0xF0)>>4][byte_array[i]&0xF]
return byte_array
 
def XorBytes(byte_array,input):
'''XORs two byte arrays and returns the result.'''
for i in range(len(byte_array)):
byte_array[i] ^= input[i]
return byte_array
 
def ShiftRows(byte_array):
'''Shifts the second row left by one, the third row by two, and the fourth row by three.'''
# Shift second row
temp = byte_array[1]
byte_array[1] = byte_array[5]
byte_array[5] = byte_array[9]
byte_array[9] = byte_array[13]
byte_array[13] = temp
# Shift third row
temp = byte_array[2]
byte_array[2] = byte_array[10]
byte_array[10] = temp
temp = byte_array[6]
byte_array[6] = byte_array[14]
byte_array[14] = temp
# Shift fourth row
temp = byte_array[3]
byte_array[3] = byte_array[15]
byte_array[15] = byte_array[11]
byte_array[11] = byte_array[7]
byte_array[7] = temp
return byte_array
 
def MC_Mul(byte,mul):
'''Helper function for MixColumns'''
if mul == 2:
byte <<= 1
if byte > 0xFF:
byte ^= 0x11B
elif mul == 3:
byte = (byte<<1)^byte
if byte > 0xFF:
byte ^= 0x11B
return byte
 
def MixColumns(byte_array):
'''Mixes each column by multiplying it with a certain matrix of elements of the field F_{2^8}.'''
ret = []
for i in [0,4,8,12]:
ret.append((MC_Mul(byte_array[i],2) ^ MC_Mul(byte_array[i+1],3) ^ byte_array[i+2] ^ byte_array[i+3])&0xFF)
ret.append((byte_array[i] ^ MC_Mul(byte_array[i+1],2) ^ MC_Mul(byte_array[i+2],3) ^ byte_array[i+3])&0xFF)
ret.append((byte_array[i] ^ byte_array[i+1] ^ MC_Mul(byte_array[i+2],2) ^ MC_Mul(byte_array[i+3],3))&0xFF)
ret.append((MC_Mul(byte_array[i],3) ^ byte_array[i+1] ^ byte_array[i+2] ^ MC_Mul(byte_array[i+3],2))&0xFF)
return ret
 
def KeyExpansion(key):
'''Computes and returns the expansion key for a given encryption key.'''
expanded_key = []
for i in range(4):
expanded_key.append([key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]])
for i in range(4,44):
temp = list(expanded_key[i-1])
if i % 4 == 0:
temp = XorBytes(SubBytes(RotWord(temp)),RCon[(i/4)-1])
expanded_key.append(XorBytes(list(expanded_key[i-4]),temp))
return expanded_key
 
def RoundKey(expansion_key,round):
'''Computes and returns the round key given the expansion key.'''
round_key = []
for i in range(4):
for j in range(4):
round_key.append(expansion_key[4*round+i][j])
return round_key
 
if __name__ == '__main__':
# Calculate and print out the round keys
expansion_key = KeyExpansion(encryption_key)
for i in range(11):
round_key = RoundKey(expansion_key, i)
print "Round Key %d:" % i,
for j in range(16):
print "%02X" % round_key[j],
print
print
 
# Print out round 0 info
state = list(plaintext)
print "Initial state = plaintext:\t",
for i in range(16):
print "%02X" % state[i],
print
 
print "Round 0 (Add Round Key 0):\t",
state = XorBytes(state, RoundKey(expansion_key, 0))
for i in range(16):
print "%02X" % state[i],
print "\n"
 
# Print out the results of each round
for i in range(1,10):
print "Round %d (SubBytes):\t\t\t" % i,
state = SubBytes(state)
for j in range(16):
print "%02X" % state[j],
print
 
print "Round %d (ShiftRows):\t\t" % i,
state = ShiftRows(state)
for j in range(16):
print "%02X" % state[j],
print
 
print "Round %d (MixColumns):\t\t" % i,
state = MixColumns(state)
for j in range(16):
print "%02X" % state[j],
print
 
print "Round %d (Add Round Key %d):\t" % (i,i),
state = XorBytes(state, RoundKey(expansion_key, i))
for j in range(16):
print "%02X" % state[j],
print "\n"
 
# Print out the results for the final round
print "Round 10 (SubBytes):\t\t",
state = SubBytes(state)
for j in range(16):
print "%02X" % state[j],
print
 
print "Round 10 (ShiftRows):\t\t",
state = ShiftRows(state)
for j in range(16):
print "%02X" % state[j],
print
 
print "Round 10 (Add Round Key 10):",
state = XorBytes(state, RoundKey(expansion_key, 10))
for j in range(16):
print "%02X" % state[j],
print "\n"
 
# Print the ciphertext
print "Ciphertext:\t\t\t",
for j in range(16):
print "%02X" % state[j],
print
/Classwork/MATH4175 - Cryptography 1/Affine Cipher.py
0,0 → 1,11
# e(x) = 3x + 3 mod 26 d(x) = 9(x-3) mod 26
if __name__ == '__main__':
string = 'NPVGP GIZGH LGZKP LKKLF EQZUN PGEHZ RKZOJ ZGZXZ MZQKA'
output = ""
for c in string:
if c != ' ':
char = ord(c) - 64
output += (chr((3 * char - 21) % 26 + 64))
else:
output += ' '
print output
/Classwork/MATH4175 - Cryptography 1/Enigma.py
0,0 → 1,74
def perms_insert(perms, char_prev, char_next):
prev_exist = False
prev_index = 0
next_exist = False
next_index = 0
 
# First check if either characters are already in a permutation
for perm in perms:
# Check if the prev char exists in the permutation set
if perm.count(char_prev) != 0:
prev_exist = True
prev_index = perms.index(perm)
# Check if the next char exists in the permutation set
if perm.count(char_next) != 0:
next_exist = True
next_index = perms.index(perm)
# Break out of the loop once both are found
if prev_exist and next_exist:
break
 
# Neither exists
if not prev_exist and not next_exist:
# If both chars are the same, insert the character into the permutation set by itself
if char_prev == char_next:
perms.append(char_next)
# Otherwise append the two characters together and insert into the permutation set
else:
perms.append(char_prev+char_next)
 
# Only the prev char exists so append the next char to the end of the permutation with the prev char
if prev_exist and not next_exist:
new_perm = perms[prev_index] + char_next
perms.remove(perms[prev_index])
perms.append(new_perm)
 
# Only the next char exists so append the prev char to the start of the permutation with the next char
if not prev_exist and next_exist:
new_perm = char_prev + perms[next_index]
perms.remove(perms[next_index])
perms.append(new_perm)
 
# Both chars exist
if prev_exist and next_exist:
# Check if they are part of the same permutation, combine permutations if they are not
if prev_index != next_index:
str1 = perms[prev_index]
str2 = perms[next_index]
new_perm = str1 + str2
perms.remove(str1)
perms.remove(str2)
perms.append(new_perm)
 
return perms
 
if __name__ == '__main__':
input_text = 'GAWGST OTJPNA VBPLKM YPSYVU CZSXGU HTNINR PCLSOI WMGREH \
YPSYVU CZSXGU HIZIMP PCLSOI WMGREH YPSYVU CZSXGU IOQHFZ \
QNHDPB XPRCVD YAMYSL CQOXQQ JJHBJB QNHDPB XPRCVD YAMYSL \
DXYMTS KPVOVY RJXNJK XPRCVD ZSTKHE DXYMTS LHXQRK RJXNJK \
XPRCVD ZSTKHE DUOMAQ MVHFYB SGCADV XLICWW ZGGKDH DECMIV \
NTUZNJ SGCADV YEDYIC AYKTCN EVIUYW NTUZNJ TDJWZA YEDYIC \
BOOVFQ EVIUYW NDBZZO TDJWZA YEFYIC BOOVFQ FFEEXG OQMPQL \
TRAWLF YEDYIC BWFVBX FKLEUI OQMPQL UHAJRF YEDYIC BWFVBX \
FYPECM'
# Split the input into an array of 6 letter strings
input_split = input_text.split();
 
# Iterate and process through D*A, E*B, F*C
for offset in range(3):
perms = []
# Go through each letter set and insert into permutation list
for entry in input_split:
perms = perms_insert(perms, entry[offset], entry[offset+3])
print offset, offset+3, perms
/Classwork/MATH4175 - Cryptography 1/Frequency Counter.py
0,0 → 1,58
import string, re
 
common_digram = ['TH','HE','IN','ER','AN','RE','ED','ON','ES','ST','EN','AT','TO','NT','HA','ND','OU','EA','NG','AS','OR','TI','IS','ET','IT','AR','TE','SE','HI','OF']
common_trigram = ['THE','ING','AND','HER','ERE','ENT','THA','NTH','WAS','ETH','FOR','DTH']
 
def print_frequency(str, num_chars):
# Calculate the frequency of 'num_chars' characters in 'str'
frequency = dict()
for i in range(0,len(str)+1-num_chars):
result = input_stripped.count(input_stripped[i:i+num_chars])
if result != 0:
if input_stripped[i:i+num_chars] not in frequency:
frequency[input_stripped[i:i+num_chars]] = 1
else:
frequency[input_stripped[i:i+num_chars]] += 1
 
# Print out the frequency in decreasing order
m = max(frequency.values())
while m > 0:
if m in frequency.values():
print m,':',
for entry in frequency:
if frequency[entry] == m:
print entry,
print
m = m - 1
 
if __name__ == '__main__':
# input = raw_input("Enter string: ")
input = 'XKJUROWMLLPXWZNPIMBVBQJCNOWXPCCHHVVFVSLLFVXHAZITYXOHULX \
QOJAXELXZXMYJAQFSTSRULHHUCDSKBXKNJQIDALLPQSLLUHIAQFPBPC \
IDSVCIHWHWEWTHBTXRLJNRSNCIHUVFFUXVOUKJLJSWMAQFVJWJSDYLJ \
OGJXDBOXAJULTUCPZMPLIWMLUBZXVOODYBAFDSKXGQFADSHXNXEHSAR \
UOJAQFPFKNDHSAAFVULLUWTAQFRUPWJRSZXGPFUTJQIYNRXNYNTWMHC'
# Remove all whitespace from the input string
input_stripped = re.sub('\s', '', input)
# Print out the sample size
sample_size = len(input_stripped)
print "Sample Size:", sample_size
# Print out the letter frequency and % for each letter
print "Letter Frequency Count:"
for letter in string.uppercase:
count = input_stripped.count(letter)
print "{0} : {1:<2} : {2:.2}".format(letter,count,float(count)/float(sample_size))
 
# Print out the sorted frequency for letters
print "Letter Frequency Count (Sorted):"
print_frequency(input_stripped, 1)
 
# Print out the sorted frequency for digrams
print "Digram Frequency Count:"
print_frequency(input_stripped, 2)
 
# Print out the sorted frequency for trigrams
print "Trigram Frequency Count:"
print_frequency(input_stripped, 3)
/Classwork/MATH4175 - Cryptography 1/LFSR.py
0,0 → 1,25
if __name__ == '__main__':
arrays = [
[0,0,0,0,0],[0,0,0,0,1],[0,0,0,1,0],[0,0,0,1,1],
[0,0,1,0,0],[0,0,1,0,1],[0,0,1,1,0],[0,0,1,1,1],
[0,1,0,0,0],[0,1,0,0,1],[0,1,0,1,0],[0,1,0,1,1],
[0,1,1,0,0],[0,1,1,0,1],[0,1,1,1,0],[0,1,1,1,1],
[1,0,0,0,0],[1,0,0,0,1],[1,0,0,1,0],[1,0,0,1,1],
[1,0,1,0,0],[1,0,1,0,1],[1,0,1,1,0],[1,0,1,1,1],
[1,1,0,0,0],[1,1,0,0,1],[1,1,0,1,0],[1,1,0,1,1],
[1,1,1,0,0],[1,1,1,0,1],[1,1,1,1,0],[1,1,1,1,1]]
 
# Iterate through each possible initialization vector
for array in arrays:
# Calculate the resulting array up to 64 places
for i in range(200):
array.append((array[i] + array[i+1]) % 2)
 
# Find equal consecutive arrays
for period in range(5,100):
array1 = array[0:period]
array2 = array[period:2*period]
# If match, print out array and period
if array1 == array2:
print array[0:5], '=', array1, "; Period =", period
break
/Classwork/MATH4175 - Cryptography 1/Notes.pdf
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/Classwork/MATH4175 - Cryptography 1/Notes.pdf
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/Classwork/MATH4175 - Cryptography 1/SPN Decode.py
0,0 → 1,66
import math
 
substitution_map = {14:0,4:1,13:2,1:3,2:4,15:5,11:6,8:7,3:8,10:9,6:10,12:11,5:12,9:13,0:14,7:15}
 
key = [3,10,9,4,13,6,3,15] # Key
key_length = len(key) * 4 # Calculate length of the key in bits
bytes_per_key = key_length / 8 # Calculate the number of bytes for each round key (4 for a 32-bit key)
rounds = int(math.log(key_length,2)) # Calculate the number of rounds (5 for a 32-bit key)
 
ciphertext = [6,4,15,13]
 
def get_key(key, round):
'''Returns the round key for the specified key.
The round key is bytes_per_key bytes starting at the specified offset determined by round.'''
round -= 1
ret = []
for index in range(bytes_per_key):
ret.append(key[index + round])
return ret
 
def xor_array(array, key):
'''Xor the two arrays and returns the result.'''
ret = []
for index in range(len(array)):
ret.append(array[index] ^ key[index])
return ret
 
def sub_array(array):
'''Runs the given array through the substitution map and returns the result.'''
ret = []
for index in range(len(array)):
ret.append(substitution_map[array[index]])
return ret
 
def perm_array(array):
'''Permutes the given array and returns the results.'''
ret = []
for index in range(bytes_per_key-1,-1,-1):
value = 0
for entry in array:
value <<= 1
value |= (entry >> index) & 1
ret.append(value)
return ret
 
if __name__ == '__main__':
working_array = ciphertext # Initial array is the ciphertext
print "Ciphertext:\t\t\t\t", ciphertext
print
 
# Print out the results for each step as it is calculated
for round in range(rounds,1,-1):
print "Round {0} Key:\t\t\t".format(round), get_key(key, round)
working_array = xor_array(working_array, get_key(key, round))
print "Round {0} U (xor'ed):\t\t".format(round),working_array
if round != rounds:
working_array = perm_array(working_array)
print "Round {0} W (perm'ed):\t".format(round),working_array
working_array = sub_array(working_array)
print "Round {0} V (sub'ed):\t\t".format(round),working_array
print
 
# Print out the final round key and xor'ed result (plaintext)
print "Round 1 Key:\t\t\t",get_key(key, 1)
working_array = xor_array(working_array, get_key(key, 1))
print("Plaintext (xor'ed):\t\t"), working_array
/Classwork/MATH4175 - Cryptography 1/SPN Encode.py
0,0 → 1,75
import math
 
substitution_map = {0:14,1:4,2:13,3:1,4:2,5:15,6:11,7:8,8:3,9:10,10:6,11:12,12:5,13:9,14:0,15:7}
 
key = [3,10,9,4,13,6,3,15] # Key
# key = [8,9,10,12,3,6,11,5]
key_length = len(key) * 4 # Calculate length of the key in bits
bytes_per_key = key_length / 8 # Calculate the number of bytes for each round key (4 for a 32-bit key)
rounds = int(math.log(key_length,2)) # Calculate the number of rounds (5 for a 32-bit key)
 
# plaintext = [2,6,11,7]
# plaintext = [15,14,2,6]
# plaintext = [15,14,2,7]
plaintext = [2,13,0,0]
 
def get_key(key, round):
'''Returns the round key for the specified key.
The round key is bytes_per_key bytes starting at the specified offset determined by round.'''
round -= 1
ret = []
for index in range(bytes_per_key):
ret.append(key[index + round])
return ret
 
def xor_array(array, key):
'''Xor the two arrays and returns the result.'''
ret = []
for index in range(len(array)):
ret.append(array[index] ^ key[index])
return ret
 
def sub_array(array):
'''Runs the given array through the substitution map and returns the result.'''
ret = []
for index in range(len(array)):
ret.append(substitution_map[array[index]])
return ret
 
def perm_array(array):
'''Permutes the given array and returns the results.'''
ret = []
for index in range(bytes_per_key-1,-1,-1):
value = 0
for entry in array:
value <<= 1
value |= (entry >> index) & 1
ret.append(value)
return ret
 
if __name__ == '__main__':
working_array = plaintext # Initial array is the plaintext
print "Round 0 W (Plaintext):\t", plaintext
print
 
# Print out the results for each step as it is calculated
for round in range(1,rounds-1):
print "Round {0} Key:\t\t\t".format(round),get_key(key, round)
working_array = xor_array(working_array, get_key(key, round))
print "Round {0} U (xor'ed):\t\t".format(round),working_array
working_array = sub_array(working_array)
print "Round {0} V (sub'ed):\t\t".format(round),working_array
working_array = perm_array(working_array)
print "Round {0} W (perm'ed):\t".format(round),working_array
print
 
# Calculate and print out the final round without the permutation
print "Round {0} Key:\t\t\t".format(rounds-1),get_key(key, rounds-1)
working_array = xor_array(working_array, get_key(key, rounds-1))
print "Round {0} U (xor'ed):\t\t".format(rounds-1),working_array
working_array = sub_array(working_array)
print "Round {0} V (sub'ed):\t\t".format(rounds-1),working_array
print
print "Round {0} Key:\t\t\t".format(rounds),get_key(key, rounds)
working_array = xor_array(working_array, get_key(key,rounds))
print("Ciphertext (xor'ed):\t"), working_array
/Classwork/MATH4175 - Cryptography 1/Shift Cipher.py
0,0 → 1,27
def shift_string(str):
'''Returns a list of strings that is the original string shifted by 1-25 places.
The input string must have no whitespaces'''
ret = []
for i in range(0,26):
output = ''
for c in str:
output += chr((ord(c)-65+i)%26 + 65) # Increment the letter by 1 mod(26) and append to output
ret.append(output)
return ret
if __name__ == '__main__':
# input = raw_input("Enter string: ")
input = 'NPVGP GIZGH LGZKP LKKLF EQZUN PGEHZ RKZOJ ZGZXZ MZQKA'
input = input.upper()
# Increment amount, here it's 26 for the english alphabet
for i in range(0,26):
output = ''
for c in input:
if c != ' ':
# Increment the letter by 1 mod(26) and append to output
output += chr((ord(c)-65+i)%26 + 65)
else:
# Ignore whitespaces
output += ' '
print output
/Classwork/MATH4175 - Cryptography 1/Vigenere Cipher.py
0,0 → 1,139
import string, re
 
# Mapping of letter frequencies
letter_freq = {'A':.082,'B':.015,'C':0.028,'D':.043,'E':.127,'F':.022,'G':.02,'H':.061,\
'I':.07,'J':.002,'K':.008,'L':.04,'M':.024,'N':.067,'O':.075,'P':.019,'Q':.001,\
'R':.06,'S':.063,'T':.091,'U':.028,'V':.01,'W':.023,'X':.001,'Y':.02,'Z':.001}
alphabet_length = 26 # Number of letters in the alphabet
alphabet_offset = 65 # Offset of 'A' for ASCII characters
english_IC = 0.065 # IC of english
key_lengths = 10 # Number of key lengths to test
# Encrypted message
input = 'XKJUROWMLLPXWZNPIMBVBQJCNOWXPCCHHVVFVSLLFVXHAZITYXOHULX \
QOJAXELXZXMYJAQFSTSRULHHUCDSKBXKNJQIDALLPQSLLUHIAQFPBPC \
IDSVCIHWHWEWTHBTXRLJNRSNCIHUVFFUXVOUKJLJSWMAQFVJWJSDYLJ \
OGJXDBOXAJULTUCPZMPLIWMLUBZXVOODYBAFDSKXGQFADSHXNXEHSAR \
UOJAQFPFKNDHSAAFVULLUWTAQFRUPWJRSZXGPFUTJQIYNRXNYNTWMHC'
 
# letter_freq = {'A':0.7,'B':0.2,'C':0.1}
# alphabet_length = 3
# alphabet_offset = 65
# english_IC = 0.54
# key_lengths = 4
# input = 'ABCBABBBAC'
 
def calculate_IC(str):
'''Calculates and returns the IC for a given string'''
IC = 0
for letter in letter_freq.keys():
count = str.count(letter) # count the frequency of every letter
IC += float(count * (count-1))/float(len(str) * (len(str)-1))
return IC
 
def get_substring(str, keylen):
'''Returns a list of substrings of an offset of mod keylen'''
ret = []
for incr in range(keylen): # Iterate through the positions within each key
substring = ''
for char in range(len(str)): # Get every X characters from the input
if char % keylen == incr:
substring += str[char]
ret.append(substring)
return ret
 
def char_shift(char, x):
'''Shifts a given character x amount and returns it'''
i = ord(char) - alphabet_offset
i += x
while i < 0:
i += alphabet_length
return chr(i + alphabet_offset)
 
if __name__ == '__main__':
# input = raw_input("Enter string: ")
 
# Remove all whitespace from the input string and convert the string to uppercase
input_stripped = re.sub('\s', '', input)
input_stripped = input_stripped.upper()
 
# Print out the sample size (length of input)
sample_size = len(input_stripped)
print "Sample Size: {0}\n".format(sample_size)
 
# Calculate and print out the IC with keys of length 1-9
print "Index of Coincidence (0.065 = IC of english):"
freq_avg = dict()
freq_diff = dict()
 
# Create a mapping of IC to key lengths
for length in range(1,key_lengths):
avg_IC = 0
substrings = get_substring(input_stripped, length) # Get a list of substrings
print "M = {0}:".format(length),
for str in substrings: # Calculate the average IC of all the substrings
IC = calculate_IC(str)
avg_IC += IC
print "{0:.2}".format(IC),
print " : {0:.2}".format(avg_IC/length)
freq_avg[avg_IC/length] = length # Store the key length and resulting IC
 
# Create a mapping of |IC - 0.065| to key lengths
for entry in freq_avg:
freq_diff[abs(entry - english_IC)] = freq_avg[entry]
 
# Sort this mapping and print out the most likely key lengths
keys = freq_diff.keys()
keys.sort()
print "\nThe most likely key lengths are:"
for entry in keys:
print "{0} : {1:.3}".format(freq_diff[entry], entry)
 
# Using the found keylength, calculate the most likely decrypting key
print "\nAssuming the key has a length of {0} characters".format(freq_diff[keys[0]])
key_length = freq_diff[keys[0]]
 
# Now we shift each substring and try to find a shift where the IC is closest to 0.065
print "The most likely key solutions are (read down on the left column):"
substrings = get_substring(input_stripped, key_length) # First divide the input string by the key length
key_final = ''
 
for substr in substrings:
freq_avg.clear() # First clear out both frequency dictionaries
freq_diff.clear()
 
# Create a mapping of IC to letters
for g in range(alphabet_length): # Iterate through each letter + shifted value
IC = 0
for i in range(alphabet_length): # Iterate through each letter
# Calculate the IC using M_g = Sum(i=0-25) : p_i * f_(i+g) / n'
IC += float(letter_freq[chr(i+alphabet_offset)] * substr.count(chr((i+g)%alphabet_length + alphabet_offset))) / float(sample_size/key_length)
freq_avg[IC] = chr((g)%alphabet_length + alphabet_offset) # Store the letter and calculated IC
 
# Create a mapping of |IC - 0.065| to letters
for entry in freq_avg:
freq_diff[abs(entry - english_IC)] = freq_avg[entry]
 
# Sort this mapping and print out the key
keys = freq_diff.keys()
keys.sort()
output = ''
key_final += freq_diff[keys[0]]
for entry in keys:
output += freq_diff[entry] + ' '
print output
for i in range(3):
print "{0} : {1:.3} ".format(freq_diff[keys[i]], keys[i])
print
 
# Decode the encrypted message using the key
key_ind = 0
output = ''
for i in range(sample_size):
output += char_shift(input_stripped[i], 0-ord(key_final[key_ind])-alphabet_offset)
key_ind += 1
key_ind %= key_length
 
# Print the decoded message
print "Encryption key: {0}, Decrypted message:".format(key_final)
print output