Testing done for now.
[szyfrow.git] / szyfrow / cadenus.py
1 from itertools import chain
2 import multiprocessing
3 from szyfrow.support.utilities import *
4 from szyfrow.support.language_models import *
5 from szyfrow.column_transposition import transpositions_of
6
7
8 def make_cadenus_keycolumn(doubled_letters = 'vw', start='a', reverse=False):
9 """Makes the key column for a Cadenus cipher (the column down between the
10 rows of letters)
11
12 >>> make_cadenus_keycolumn()['a']
13 0
14 >>> make_cadenus_keycolumn()['b']
15 1
16 >>> make_cadenus_keycolumn()['c']
17 2
18 >>> make_cadenus_keycolumn()['v']
19 21
20 >>> make_cadenus_keycolumn()['w']
21 21
22 >>> make_cadenus_keycolumn()['z']
23 24
24 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['a']
25 1
26 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['b']
27 0
28 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['c']
29 24
30 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['i']
31 18
32 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['j']
33 18
34 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['v']
35 6
36 >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['z']
37 2
38 """
39 index_to_remove = string.ascii_lowercase.find(doubled_letters[0])
40 short_alphabet = string.ascii_lowercase[:index_to_remove] + string.ascii_lowercase[index_to_remove+1:]
41 if reverse:
42 short_alphabet = cat(reversed(short_alphabet))
43 start_pos = short_alphabet.find(start)
44 rotated_alphabet = short_alphabet[start_pos:] + short_alphabet[:start_pos]
45 keycolumn = {l: i for i, l in enumerate(rotated_alphabet)}
46 keycolumn[doubled_letters[0]] = keycolumn[doubled_letters[1]]
47 return keycolumn
48
49 def cadenus_encipher(message, keyword, keycolumn, fillvalue='a'):
50 """Encipher with the Cadenus cipher
51
52 >>> cadenus_encipher(sanitise('Whoever has made a voyage up the Hudson ' \
53 'must remember the Kaatskill mountains. ' \
54 'They are a dismembered branch of the great'), \
55 'wink', \
56 make_cadenus_keycolumn(doubled_letters='vw', start='a', reverse=True))
57 'antodeleeeuhrsidrbhmhdrrhnimefmthgeaetakseomehetyaasuvoyegrastmmuuaeenabbtpchehtarorikswosmvaleatned'
58 >>> cadenus_encipher(sanitise('a severe limitation on the usefulness of ' \
59 'the cadenus is that every message must be ' \
60 'a multiple of twenty-five letters long'), \
61 'easy', \
62 make_cadenus_keycolumn(doubled_letters='vw', start='a', reverse=True))
63 'systretomtattlusoatleeesfiyheasdfnmschbhneuvsnpmtofarenuseieeieltarlmentieetogevesitfaisltngeeuvowul'
64 """
65 transpositions = transpositions_of(keyword)
66 enciphered_chunks = []
67 for message_chunk in chunks(message, len(transpositions) * 25,
68 fillvalue=fillvalue):
69 rows = chunks(message_chunk, len(transpositions), fillvalue=fillvalue)
70 columns = zip(*rows)
71 rotated_columns = [col[start:] + col[:start] for start, col in zip([keycolumn[l] for l in keyword], columns)]
72 rotated_rows = zip(*rotated_columns)
73 transposed = [transpose(r, transpositions) for r in rotated_rows]
74 enciphered_chunks.append(cat(chain(*transposed)))
75 return cat(enciphered_chunks)
76
77 def cadenus_decipher(message, keyword, keycolumn, fillvalue='a'):
78 """
79 >>> cadenus_decipher('antodeleeeuhrsidrbhmhdrrhnimefmthgeaetakseomehetyaa' \
80 'suvoyegrastmmuuaeenabbtpchehtarorikswosmvaleatned', \
81 'wink', \
82 make_cadenus_keycolumn(reverse=True))
83 'whoeverhasmadeavoyageupthehudsonmustrememberthekaatskillmountainstheyareadismemberedbranchofthegreat'
84 >>> cadenus_decipher('systretomtattlusoatleeesfiyheasdfnmschbhneuvsnpmtof' \
85 'arenuseieeieltarlmentieetogevesitfaisltngeeuvowul', \
86 'easy', \
87 make_cadenus_keycolumn(reverse=True))
88 'aseverelimitationontheusefulnessofthecadenusisthateverymessagemustbeamultipleoftwentyfiveletterslong'
89 """
90 transpositions = transpositions_of(keyword)
91 deciphered_chunks = []
92 for message_chunk in chunks(message, len(transpositions) * 25,
93 fillvalue=fillvalue):
94 rows = chunks(message_chunk, len(transpositions), fillvalue=fillvalue)
95 untransposed_rows = [untranspose(r, transpositions) for r in rows]
96 columns = zip(*untransposed_rows)
97 rotated_columns = [col[-start:] + col[:-start] for start, col in zip([keycolumn[l] for l in keyword], columns)]
98 rotated_rows = zip(*rotated_columns)
99 deciphered_chunks.append(cat(chain(*rotated_rows)))
100 return cat(deciphered_chunks)
101
102
103
104 def cadenus_break(message, wordlist=keywords,
105 doubled_letters='vw', fitness=Pbigrams):
106 # c = make_cadenus_keycolumn(reverse=True)
107 # valid_words = [w for w in wordlist
108 # if len(transpositions_of(w)) == len(message) // 25]
109 with multiprocessing.Pool() as pool:
110 results = pool.starmap(cadenus_break_worker,
111 [(message, w,
112 make_cadenus_keycolumn(doubled_letters=doubled_letters,
113 start=s, reverse=r),
114 fitness)
115 for w in wordlist
116 for s in string.ascii_lowercase
117 for r in [True, False]
118 # if max(transpositions_of(w)) <= len(
119 # make_cadenus_keycolumn(
120 # doubled_letters=doubled_letters, start=s, reverse=r))
121 ])
122 # return list(results)
123 return max(results, key=lambda k: k[1])
124
125 def cadenus_break_worker(message, keyword, keycolumn, fitness):
126 # message_chunks = chunks(message, 175)
127 # plaintext = ''.join(cadenus_decipher(c, keyword, keycolumn) for c in message_chunks)
128 plaintext = cadenus_decipher(message, keyword, keycolumn)
129 fit = fitness(plaintext)
130 return (keyword, keycolumn), fit
131
132 if __name__ == "__main__":
133 import doctest