def columnarTransportExample(): print("Example of the columnar transport cipher") key = "BIRTHDAY" ptext = "THEQUICKBROWNFOXJUMPSOVERTHELAZYDOG" rank = uniqueRank(key) print("\nThe Key Is {}".format(key)) print("Each letter of the key is ranked to get") print(*rank) print("\nThe plaintext is\n{}".format(ptext)) print( "\nNow the text is read into the grid by rows. The key is placed above.\n" ) print(" ".join([str(i) for i in rank])) for i in groups(ptext, 8): print(" ".join([e for e in i])) print( "\nFinally the grid is read off in accordance with column numbers starting with zero, then one, and so on.\n" ) ctext, dtext = example(columnarTransport, ptext, key, complete=False) print(ctext)
def hillCipher(text,key,decode=False,alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ"): """Encrypt text using matrix multiplication.""" M = len(alphabet) # If a list is provided turn it into a sympy Matrix if type(key) == list: key = Matrix(key) # If we are decoding invert the key if decode == True: key = key.inv_mod(M) # Get the dimension of the key N = key.shape[0] # Apply any nulls needed rem = len(text) % N if rem != 0: text += "X"*(N-rem) out = "" for i in groups(text,N): x = Matrix([alphabet.index(let) for let in i]) y = key.dot(x) out += "".join([alphabet[j%M] for j in y]) return out
def binaryBraille(text, decode=False): alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" braille = [ "010000", "100000", "101000", "110000", "110100", "100100", "111000", "111100", "101100", "011000", "011100", "100010", "101010", "110010", "110110", "100110", "111010", "111110", "101110", "011010", "011110", "100011", "101011", "011101", "110011", "110111", "100111" ] D = {} if decode == False: for i, j in zip(alpha, braille): D[i] = j out = "" for i in text: out += D[i] return out if decode == True: for i, j in zip(alpha, braille): D[j] = i out = "" for i in groups(text, 6): out += D[i] return out
def columnarTransport(text,key,decode=False,complete=False): k = uniqueRank(key) # Determine how many columns numcol = len(k) numrow,rem = divmod(len(text),numcol) longCols = k[:rem] # If complete is selected nulls are added so that the ciphertext fits # perfectly into the grid. if complete == True: if rem > 0: text = addNulls(text,total_len=numcol*(numrow+1)) else: text = addNulls(text,total_len=numcol*numrow) if decode == False: # Read the text into the rows L = groups(text,len(k)) # Read down each column out = [] for col in argsort(k): for row in L: if len(row) > col: out.append(row[col]) return "".join(out) if decode == True: ctr = 0 L = [] for i in range(numcol): if i in longCols: L.append(text[ctr:ctr+numrow+1]) #print("#",text[ctr:ctr+numrow+1],"#") ctr += (numrow+1) else: L.append(text[ctr:ctr+numrow]) #print("#",text[ctr:ctr+numrow],"#") ctr += numrow out = [] for row in range(numrow+1): for col in k: if len(L[col]) > row: out.append( L[col][row] ) return "".join(out)
def DRYAD(text, key, decode=False, codepage=False): # Extend the text with zeroes so groups are all the same size while len(text) % 5 != 0: text += "0" # Use the key value to generate a random DRYAD page page = [] random.seed(key) for i in range(26): L = [let for let in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"] random.shuffle(L) pos = 0 row = [] for chunk in [4, 3, 3, 2, 2, 3, 2, 2, 2, 2]: row.append("".join(L[pos:pos + chunk])) pos += chunk page.append(row) # If one wants to simply produce a DRYAD codepage this does so if codepage == True: ctr = 0 for let, row in zip("ABCDEFGHIJKLMNOPQRSTUVWXYZ", page): if ctr % 4 == 0: print("\n 0 1 2 3 4 5 6 7 8 9") ctr += 1 print(let, ":", " ".join(row)) # Now reset the random seed random.seed() if decode == False: out = [] for grp in groups(text, 5): row = random.choice([n for n in range(26)]) # write down the letter indicating the row we are using out.append(chr(row + 65)) # Pick a random letter from the options to represent that digit for digit in grp: out.append(random.choice(page[row][int(digit)])) out.append(" ") # The [:-1] removes the trailing space return "".join(out)[:-1] if decode == True: out = [] text = text.split(" ") for section in text: code = page[ord(section[0]) - 65] for letter in section[1:]: for x, y in enumerate(code): if letter in y: out.append(str(x)) return "".join(out)
def routeCipher(text, key, decode=False): while len(text) % key != 0: text += "X" if decode == False: G = groups(text, key) out = "" ctr = 0 while ctr < key: gr = [] for i in G: gr.append(i[ctr]) if ctr % 2 == 1: gr.reverse() out += "".join(gr) ctr += 1 return out if decode == True: key = len(text) // key G = groups(text, key) out = "" for passthru in range(key): for pos, lets in enumerate(G): if pos % 2 == 0: a = lets[0] G[pos] = lets[1:] if pos % 2 == 1: a = lets[-1] G[pos] = lets[:-1] out += a return out
def fourSquare(text,keys,decode=False,mode="IJ",printkey=False): # Convert the squares to numpy arrays to we can use numpy's indexing sq1 = np.array(makeSquare(keys[0],mode=mode)) sq2 = np.array(makeSquare(keys[1],mode=mode)) alphasq = np.array(makeSquare("",mode=mode)) if mode == "IJ" or mode == "JI": text = text.replace("J","I") if mode == "KQ" or mode == "QK": text = text.replace("Q","K") if mode == "CK" or mode == "KC": text = text.replace("C","K") if printkey == True: n = 5 if mode == "EX": n = 6 for i in range(n): print(" ".join(alphasq[i]),end=" ") print(" ".join(sq1[i])) print() for i in range(n): print(" ".join(sq2[i]),end=" ") print(" ".join(alphasq[i])) return "" if len(text) % 2 == 1: text += "X" G = groups(text,2) if decode == False: out = "" for g in G: A = np.where(sq1 == g[0]) B = np.where(sq2 == g[1]) out += alphasq[A[0],B[1]][0] out += alphasq[B[0],A[1]][0] return out if decode == True: out = "" for g in G: A = np.where(alphasq == g[0]) B = np.where(alphasq == g[1]) out += sq1[A[0],B[1]][0] out += sq2[B[0],A[1]][0] return out
def enigmaExample(): print("Enigma Example\n") # The Enigma machine had a codebook with different settings to be used # each day. This example just uses a hash of the date to randomly pick # what the settings will be. In actual use a stronger form of randomness is # needed. today = datetime.datetime.now().date() print("Today is {}\nThe Codebook Settings Are:".format(today)) random.seed(hash(today)) # Randomly pick the rotors, the reflector, the positions, and the plugboard # settings for the day rotors = random.sample(["I","II","III","IV","V"],k=3) reflector = random.choice(["A","B","C"]) positions = random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZ",k=3) plugs = [] for i in groups(random.sample("ABCDEFGHIJKLMNOPQRSTUVWXYZ",k=20),2): plugs.append("".join(i)) # Write out those settings separated by a | symbol print(reflector,end = " | ") for i in rotors: print(i,end = " ") print("| ",end = "") for i in positions: print(i,end = "") print(" | ",end = "") for i in plugs: print(i,end = " ") # Whenever an Enigma message was sent it was preceeded by the ring settings # which told the recieving operator what offset from the day's rotor # positions should be used. This meant that a different cipher could be # used for every message without revealing exactly what the settnings were. rings = ["A","B","C"] print("\n\nRing Settings:",end= " ") for i in rings: print(i,end = " ") print("\n") ptext = "THEQUICKBROWNFOXJUMPSOVERTHELAZYDOGANDJACKDAWSLOVEMYBIGSPHINXOFQUARTZ" ctext = enigma(ptext,keys=[rotors,reflector,positions,plugs,rings]) dtext = enigma(ctext,keys=[rotors,reflector,positions,plugs,rings]) print(ctext) if dtext != ptext: print("ERROR") print(dtext)
def VICtranskeys(kstr,num): # Turn the first ten outputs into a unique list of integers transKey = uniqueRank(kstr[:10]) # Break the rest of the keystream into ten rows G = groups(kstr[10:],10) transLens = [num+kstr[-2], num+kstr[-1]] #print(transLens) out = [] for col in range(10): for row in G: out.append( row[transKey.index(col)] ) if len(out) == sum(transLens): return out[:transLens[0]], out[transLens[0]:] return out[:transLens[0]], out[transLens[0]:]
def printGrille(key, N): S = N * 2 key = groups(key, (N // 2)**2) # The grille is actual key used for encryption, the key argument provided # specifies how to put it together. grille = np.zeros([S, S], dtype=int) # Generate the grille to be used as the key for block in key: for digit in block: pos = np.divmod(digit, S // 2) grille[pos[0], pos[1]] = 1 grille = np.rot90(grille) # If requested print out the grille in a more human readable way for i in grille: t = ["_" if j == 0 else "#" for j in i] print("|", "|".join(t), "|", sep="")
def ADFGX(text,keys=["A",[0,1]],decode=False,printkey=False): """ :param text: The text to be encrypyed. Must be alphanumeric and uppercase. The letter J will be replaced with I. :param keys: Two keywords, the first to prepare a 5x5 square a the second to control a columnar transport cipher. :param decode: Boolean. If false encrypt plaintext. If true decode ciphertext """ # Adjust the text if necessary text = text.replace("J","I") while len(text) % len(keys[1]) != 0: text += "X" alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ" alpha = alphabetPermutation(keys[0],alpha) if printkey == True: sq = makeSquare(keys[0],mode="EX") for i in range(6): print(" ".join(sq[i])) pairs = product("ADFGX",repeat=2) D1 = {} D2 = {} for letter,pair in zip(alpha,pairs): D1[letter] = "".join(pair) D2["".join(pair)] = letter # The ADFGX cipher has a roughly symmetric encode and decoding process # the only difference is that the columnar transport is reversed. # Turn every letter into a pair of symbols ctext = "".join([D1[i] for i in text]) # Scramble the symbols, this will break apart some of the pairs ctext = columnarTransport(ctext,keys[1],decode=decode) # Now take the scrambled symbols and turn them back into letters ctext = groups(ctext,2) ctext = "".join([D2[i] for i in ctext]) return ctext
def baconCode(text, decode=False): letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" codes = [ "00000", "00001", "00010", "00011", "00100", "00101", "00110", "00111", "01000", "01001", "01010", "01011", "01100", "01101", "01110", "01111", "10000", "10001", "10010", "10011", "10100", "10101", "10110", "10111", "11000", "11001" ] if decode == False: out = "" for letter in text: out += codes[letters.index(letter)] return out if decode == True: out = "" for code in groups(text, 5): out += letters[codes.index(code)] return out
def turningGrilleExtended(text, key, decode=False, N=4): S = N * 2 block_size = S**2 # If the length of the text is not a multiple of the block size first # append some Xs to indicate the end of the message then random letters ctr = 0 while len(text) % S**2 != 0: if ctr > 3: text += choice(list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) else: text += "X" ctr += 1 out = [] for i in groups(text, block_size): out.append(turningGrille(i, key=key, decode=decode, N=N)) return "".join(out)
def trifid(text, key, decode=False): triplets = product("123", repeat=3) alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ+" alphabet = alphabetPermutation(key, alphabet) D1 = {} D2 = {} for trip, alph in zip(triplets, alphabet): D1[alph] = "".join(trip) D2["".join(trip)] = alph if decode == False: A, B, C = "", "", "" # Convert the letter into their triplets for letter in text: gr = D1[letter] A += gr[0] B += gr[1] C += gr[2] ctext = "" for gr in groups(A + B + C, 3): ctext += D2[gr] return ctext if decode == True: grs = "" for letter in text: gr = D1[letter] grs += gr A = grs[:len(grs) // 3] B = grs[len(grs) // 3:2 * len(grs) // 3] C = grs[2 * len(grs) // 3:] dtext = [i + j + k for i, j, k in zip(A, B, C)] return "".join([D2[n] for n in dtext])
def ASCII(text, decode=False, mode="BIN"): # We use Python's triple quotes so that its possible to include " and ' # There are other ASCII characters but they are control characters not text chars = """ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~""" # Use the mode to determine the base to convert to and how many digits are # will be used. This determines any padding needed. modes = { "BIN": [2, 7], "UTF": [2, 8], "OCT": [8, 3], "DEC": [10, 3], "HEX": [16, 2] } base, length = modes[mode] out = [] # Convert to a number then convert that number to a binary string if decode == False: for let in text: if let not in chars: raise Exception("{} is not part of the ASCII67 standard") t = baseConvert(chars.index(let) + 32, base) while len(t) < length: t = "0" + t out.append(t) if decode == True: for block in groups(text, length): n = str2dec(block, base) out.append(chars[n - 32]) return "".join(out)
def ADFGVX(text, keys=["A", [0, 1]], decode=False, printkey=False): """ :param text: The text to be encrypyed. Must be alphanumeric and uppercase. :param keys: Two keywords, the first to prepare a 6x6 square a the second to control a columnar transport cipher. :param decode: Boolean. If false encrypt plaintext. If true decode ciphertext """ while len(text) % len(keys[1]) != 0: text += "X" alpha = alphabetPermutation(keys[0], "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") sq = makeSquare(keys[0], mode="EX") if printkey == True: for i in range(6): print(" ".join(sq[i])) pairs = product("ADFGVX", repeat=2) D1 = {} D2 = {} for letter, pair in zip(alpha, pairs): D1[letter] = "".join(pair) D2["".join(pair)] = letter # Turn every letter into a pair of symbols ctext = "".join([D1[i] for i in text]) # Scramble the symbols, this will break apart some of the pairs ctext = columnarTransport(ctext, keys[1], decode=decode) # Now take the scrambled symbols and turn them back into letters ctext = groups(ctext, 2) ctext = "".join([D2[i] for i in ctext]) return ctext
def twoSquare(text, keys, decode=False, mode="EX", printkey=False): # Convert the squares to numpy arrays to we can use numpy's indexing sq1 = np.array(makeSquare(keys[0], mode)) sq2 = np.array(makeSquare(keys[1], mode)) if mode == "IJ" or mode == "JI": text = text.replace("J", "I") if mode == "KQ" or mode == "QK": text = text.replace("Q", "K") if mode == "CK" or mode == "KC": text = text.replace("C", "K") if len(text) % 2 == 1: text += "X" # Print out the key in a nice way if the user needs it if printkey == True: if mode == "EX": for i in range(6): print(" ".join(sq1[i])) print() for i in range(6): print(" ".join(sq2[i])) else: for i in range(5): print(" ".join(sq1[i])) print() for i in range(5): print(" ".join(sq2[i])) if mode == "EX": sz = 6 else: sz = 5 G = groups(text, 2) if decode == False: out = "" for g in G: A = np.where(sq1 == g[0]) B = np.where(sq2 == g[1]) if A[0] == B[0]: out += sq1[(A[0] + 1) % sz, A[1]][0] out += sq2[(B[0] + 1) % sz, B[1]][0] elif A[1] == B[1]: out += sq1[A[0], (A[1] + 1) % sz][0] out += sq2[B[0], (B[1] + 1) % sz][0] else: out += sq1[A[0], B[1]][0] out += sq2[B[0], A[1]][0] if decode == True: out = "" for g in G: A = np.where(sq1 == g[0]) B = np.where(sq2 == g[1]) if A[0] == B[0]: out += sq1[(A[0] - 1) % sz, A[1]][0] out += sq2[(B[0] - 1) % sz, B[1]][0] elif A[1] == B[1]: out += sq1[A[0], (A[1] - 1) % sz][0] out += sq2[B[0], (B[1] - 1) % sz][0] else: out += sq1[A[0], B[1]][0] out += sq2[B[0], A[1]][0] return out
def turningGrille(text, key, decode=False, N=4): if len(key) != N**2: raise Exception("Key must have of the size {}".format(N**2)) key = groups(key, (N // 2)**2) S = N * 2 # Can't work with more than S^2 characters at a time if len(text) > S**2: raise Exception("Text cannot be longer than {}".format(S**2)) # If we have less than 64 characters first insert Xs as nulls to indicate # that we have reached the end of the message. Then put in common letters # to make the less less obvious. ctr = 0 while len(text) < S**2: if ctr > 3: text += choice(list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) else: text += "X" ctr += 1 # The grille is actual key used for encryption, the key argument provided # specifies how to put it together. grille = np.zeros([S, S], dtype=int) # This matrix is what we will write the results into outmat = np.full([S, S], "") # Generate the grille to be used as the key for block in key: for digit in block: pos = np.divmod(digit, S // 2) grille[pos[0], pos[1]] = 1 grille = np.rot90(grille) # When encoding write the letters of the text into the open spaces of the # grille. Then rotate the grille 90 degrees and continue. if decode == False: for rot in range(4): X = np.where(grille == 1) for i, j in zip(X[0], X[1]): a, text = text[0], text[1:] outmat[i, j] = a grille = np.rot90(grille) out = "" for i in outmat: out += "".join(i) return out if decode == True: gr = groups(text, S) out = "" for rot in range(4): X = np.where(grille == 1) for i, j in zip(X[0], X[1]): out += gr[i][j] grille = np.rot90(grille) return out
def hillCipherCracker(ctext, crib, N): if len(crib) < N * N: raise Exception("crib must have length {}".format(N * N)) bestScore = quadgramScore(ctext) bestKey = Matrix.eye(N) # We can use this to reduce a matrix modulo 26 mod26 = lambda x: x % 26 # Convert the text and crib to numbers so they're more useful alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ctextN = [alpha.index(i) for i in ctext] cribN = [alpha.index(i) for i in crib] # Break text into pieces G = groups(ctextN, N) # If the crib is long enough try it in sections to find if they line up # properly. for pos in range(len(crib) - (N * N - 1)): subCrib = cribN[pos:pos + (N * N)] #print(subCrib) A = Matrix(groups(subCrib, N)).T for i in range(len(G) - (N - 1)): L = [] for x in range(N): L.append(G[x + i]) B = Matrix(L).T # If the matrix is not invertible skip it if B.det() % 2 == 0: continue if B.det() % 13 == 0: continue C = A * (B.inv_mod(26)) tKey = C.applyfunc(mod26) # We run the Hill Cipher in encryption mode NOT decryption t = hillCipher(ctext, tKey) score = quadgramScore(t) if score > bestScore: bestScore = score bestKey = tKey if bestKey.det() % 2 == 0 or bestKey.det() % 13 == 0: print("Non-singular matrix error") print("Key Should be Inverse of:") pprint(bestKey) print() else: print("Key Is:") pprint(bestKey.inv_mod(26)) print() print("Decrypt Looks Like:") print(hillCipher(ctext, bestKey))
def playfair(text, key, decode=False, mode="IJ", printkey=False): # Make sure the text will work correctly for a playfair cipher in this mode text = playfairPrep(text, mode=mode) # Derive the alphabet to be used for the key based on the mode sq = makeSquare(key, mode=mode) sqWhere = squareIndex(sq) if printkey == True: if mode == "EX": for i in range(6): print(" ".join(sq[i])) else: for i in range(5): print(" ".join(sq[i])) G = groups(text, 2) if decode == False: if mode == "EX": sz = 6 else: sz = 5 out = "" for g in G: A = sqWhere[g[0]] B = sqWhere[g[1]] # If they share a column if A[0] == B[0]: out += sq[A[0]][(A[1] + 1) % sz] out += sq[B[0]][(B[1] + 1) % sz] # If they share a row elif A[1] == B[1]: out += sq[(A[0] + 1) % sz][A[1]] out += sq[(B[0] + 1) % sz][B[1]] # Otherwise else: out += sq[A[0]][B[1]] out += sq[B[0]][A[1]] return out if decode == True: if mode == "EX": sz = 6 else: sz = 5 out = "" for g in G: A = sqWhere[g[0]] B = sqWhere[g[1]] if A[0] == B[0]: out += sq[A[0]][(A[1] - 1) % sz] out += sq[B[0]][(B[1] - 1) % sz] elif A[1] == B[1]: out += sq[(A[0] - 1) % sz][A[1]] out += sq[(B[0] - 1) % sz][B[1]] else: out += sq[A[0]][B[1]] out += sq[B[0]][A[1]] return out