#!/usr/bin/env python # reads cesar cipher (english) text from stdin, # or in general any substitution cipher text. # The Deciphered text attempt using frequency analysis # is written to stdout. import sys, string debug=False if len(sys.argv)==2: if sys.argv[1]=="--cipher": def rotation_cipher(s, key): #pass -key to decipher ASC_a = ord('a') ASC_A = ord('A') l_to="".join([chr(ASC_a+(ord(c)-ASC_a+key)%26) for c in string.lowercase]) u_to="".join([chr(ASC_A+(ord(c)-ASC_A+key)%26) for c in string.uppercase]) table=string.maketrans(string.lowercase+string.uppercase,l_to+u_to) return(string.translate(s,table)) import random print rotation_cipher(sys.stdin.read(), random.randint(1,25)), sys.exit(0) else: sys.stderr.write("Usage: " + sys.argv[0] + " [--cipher]\n") sys.stderr.write("Cipher or plain text read from stdin\n") sys.exit(1) # In English, there are more E's than T's etc. singles=["E","T","A","O","N","I","S","R","H","L","D","C","U","P","F","M","W","Y","B","G","V","K","Q","X","J","Z"] doubles=["TH","HE","AN","IN","ER","RE","ES","ON","EA","TI","AT","ST","EN","ND","OR"] ct=sys.stdin.read() ctl=[l for l in ct.upper() if l.isalpha()] class counter: def __init__(self): self.dict = {} def add(self,item): count = self.dict.get(item,0) self.dict[item] = count + 1 def counts(self,descending=False): """Returns list of keys, sorted by values.""" result = zip(self.dict.values(),self.dict.keys()) result.sort() if descending: result.reverse() return result singles_counts=counter() for l in ctl: singles_counts.add(l) singles_counts=singles_counts.counts(descending=True) singles_counts=[i[1] for i in singles_counts] import string for l in string.ascii_uppercase: if l not in singles_counts: singles_counts.append(l) def result(): print string.translate( ct, string.maketrans( ''.join(singles_counts).lower()+''.join(singles_counts).upper(), ''.join(singles).lower()+''.join(singles).upper() ) ) if debug: result() ###################################################### # Refine the result using digraphs ##################################################### #For doubles one needs to consider spaces. #Note there could be 26^2 (676) entries, #but we only take the top len(doubles) doubles_counts=counter() for ctw in ct.upper().split(): for i in range(len(ctw)-1): if ctw[i:i+2].isalpha(): doubles_counts.add((ctw[i],ctw[i+1])) doubles_counts=doubles_counts.counts(descending=True) doubles_counts=doubles_counts[0:len(doubles)] #any more just redundant doubles_counts=[i[1] for i in doubles_counts] def proximity(t1,t2): diff=abs(t1[0]-t2[0])+abs(t1[1]-t2[1]) if diff: #give advantage to items where only one needs to be moved if t1[0]!=t2[0] and t1[1]!=t2[1]: diff += 1 return diff def swap(s,i1,i2): if i1==i2: return tmp=s[i2] s[i2]=s[i1] s[i1]=tmp doubles_in_singles=[] for double in doubles: di=() for letter in double: di+=singles.index(letter), doubles_in_singles.append(di) # This is a bit crap. It assumes digraphs at the # start override subsequent ones, whereas I # really should solve between all the digraphs. def solve_singles_doubles(): def print_debug(newline=True,*args): if not debug: return import sys for arg in args: sys.stderr.write(str(arg)+" ") if newline: sys.stderr.write("\n") for double in doubles_counts: di=() for letter in double: try: di+=singles_counts.index(letter), try: disi=doubles_in_singles.index(di) print_debug(True, double, "match") for i in di: singles_counts[i]=singles_counts[i].lower() except: if len(di) 5: print_debug(True,"ignore") else: swap_candidate = proximity_list.index(nearest) swapped=True for li in range(len(di)): from_index=di[li] to_index=doubles_in_singles[swap_candidate][li] if singles_counts[to_index].islower() or singles_counts[from_index].islower(): swapped=False if swapped: print_debug(True,"swap", di, "to", doubles_in_singles[swap_candidate]) for li in range(len(di)): from_index=di[li] to_index=doubles_in_singles[swap_candidate][li] swap(singles_counts,from_index,to_index) if from_index == to_index: singles_counts[to_index]=singles_counts[to_index].lower() else: print_debug(True,"passed(2)") except: if letter == double[1]: print_debug(True, double, "passed(1)") solve_singles_doubles() ###################################################### # Print the result ###################################################### result()