//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 out round 0 info |
state = list(plaintext) |
print "Initial state = plaintext:\t", |
for i in range(16): |
print "%02X" % state[i], |
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 "Round %d (ShiftRows):\t\t" % i, |
state = ShiftRows(state) |
for j in range(16): |
print "%02X" % state[j], |
print "Round %d (MixColumns):\t\t" % i, |
state = MixColumns(state) |
for j in range(16): |
print "%02X" % state[j], |
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 "Round 10 (ShiftRows):\t\t", |
state = ShiftRows(state) |
for j in range(16): |
print "%02X" % state[j], |
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], |
//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, |
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 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 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 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 |
# 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 "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]) |
# 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 |