1 from support
.utilities
import *
2 from support
.language_models
import *
3 from cipher
.keyword_cipher
import KeywordWrapAlphabet
, keyword_cipher_alphabet_of
4 from cipher
.polybius
import polybius_grid
7 from logger
import logger
9 def playfair_wrap(n
, lowest
, highest
):
10 skip
= highest
- lowest
+ 1
11 while n
> highest
or n
< lowest
:
18 def playfair_encipher_bigram(ab
, grid
, padding_letter
='x'):
20 max_row
= max(c
[0] for c
in grid
.values())
21 max_col
= max(c
[1] for c
in grid
.values())
22 min_row
= min(c
[0] for c
in grid
.values())
23 min_col
= min(c
[1] for c
in grid
.values())
26 if grid
[a
][0] == grid
[b
][0]: # same row
27 cp
= (grid
[a
][0], playfair_wrap(grid
[a
][1] + 1, min_col
, max_col
))
28 dp
= (grid
[b
][0], playfair_wrap(grid
[b
][1] + 1, min_col
, max_col
))
29 elif grid
[a
][1] == grid
[b
][1]: # same column
30 cp
= (playfair_wrap(grid
[a
][0] + 1, min_row
, max_row
), grid
[a
][1])
31 dp
= (playfair_wrap(grid
[b
][0] + 1, min_row
, max_row
), grid
[b
][1])
33 cp
= (grid
[a
][0], grid
[b
][1])
34 dp
= (grid
[b
][0], grid
[a
][1])
35 c
= [k
for k
, v
in grid
.items() if v
== cp
][0]
36 d
= [k
for k
, v
in grid
.items() if v
== dp
][0]
39 def playfair_decipher_bigram(ab
, grid
, padding_letter
='x'):
41 max_row
= max(c
[0] for c
in grid
.values())
42 max_col
= max(c
[1] for c
in grid
.values())
43 min_row
= min(c
[0] for c
in grid
.values())
44 min_col
= min(c
[1] for c
in grid
.values())
47 if grid
[a
][0] == grid
[b
][0]: # same row
48 cp
= (grid
[a
][0], playfair_wrap(grid
[a
][1] - 1, min_col
, max_col
))
49 dp
= (grid
[b
][0], playfair_wrap(grid
[b
][1] - 1, min_col
, max_col
))
50 elif grid
[a
][1] == grid
[b
][1]: # same column
51 cp
= (playfair_wrap(grid
[a
][0] - 1, min_row
, max_row
), grid
[a
][1])
52 dp
= (playfair_wrap(grid
[b
][0] - 1, min_row
, max_row
), grid
[b
][1])
54 cp
= (grid
[a
][0], grid
[b
][1])
55 dp
= (grid
[b
][0], grid
[a
][1])
56 c
= [k
for k
, v
in grid
.items() if v
== cp
][0]
57 d
= [k
for k
, v
in grid
.items() if v
== dp
][0]
60 def playfair_bigrams(text
, padding_letter
='x', padding_replaces_repeat
=True):
67 bigram
= bigram
+ padding_letter
69 if bigram
[0] == bigram
[1]:
70 bigram
= bigram
[0] + padding_letter
71 if padding_replaces_repeat
:
80 def playfair_encipher(message
, keyword
, padding_letter
='x',
81 padding_replaces_repeat
=False, letters_to_merge
=None,
82 wrap_alphabet
=KeywordWrapAlphabet
.from_a
):
83 column_order
= list(range(5))
84 row_order
= list(range(5))
85 if letters_to_merge
is None:
86 letters_to_merge
= {'j': 'i'}
87 grid
= polybius_grid(keyword
, column_order
, row_order
,
88 letters_to_merge
=letters_to_merge
,
89 wrap_alphabet
=wrap_alphabet
)
90 message_bigrams
= playfair_bigrams(sanitise(message
), padding_letter
=padding_letter
,
91 padding_replaces_repeat
=padding_replaces_repeat
)
92 ciphertext_bigrams
= [playfair_encipher_bigram(b
, grid
, padding_letter
=padding_letter
) for b
in message_bigrams
]
93 return cat(ciphertext_bigrams
)
95 def playfair_decipher(message
, keyword
, padding_letter
='x',
96 padding_replaces_repeat
=False, letters_to_merge
=None,
97 wrap_alphabet
=KeywordWrapAlphabet
.from_a
):
98 column_order
= list(range(5))
99 row_order
= list(range(5))
100 if letters_to_merge
is None:
101 letters_to_merge
= {'j': 'i'}
102 grid
= polybius_grid(keyword
, column_order
, row_order
,
103 letters_to_merge
=letters_to_merge
,
104 wrap_alphabet
=wrap_alphabet
)
105 message_bigrams
= playfair_bigrams(sanitise(message
), padding_letter
=padding_letter
,
106 padding_replaces_repeat
=padding_replaces_repeat
)
107 plaintext_bigrams
= [playfair_decipher_bigram(b
, grid
, padding_letter
=padding_letter
) for b
in message_bigrams
]
108 return cat(plaintext_bigrams
)
110 def playfair_break_mp(message
,
111 letters_to_merge
=None, padding_letter
='x',
112 wordlist
=keywords
, fitness
=Pletters
,
113 number_of_solutions
=1, chunksize
=500):
114 if letters_to_merge
is None:
115 letters_to_merge
= {'j': 'i'}
117 with multiprocessing
.Pool() as pool
:
118 helper_args
= [(message
, word
, wrap
,
119 letters_to_merge
, padding_letter
,
123 for wrap
in KeywordWrapAlphabet
124 for pad_replace
in [False, True]]
125 # Gotcha: the helper function here needs to be defined at the top level
126 # (limitation of Pool.starmap)
127 breaks
= pool
.starmap(playfair_break_worker
, helper_args
, chunksize
)
128 if number_of_solutions
== 1:
129 return max(breaks
, key
=lambda k
: k
[1])
131 return sorted(breaks
, key
=lambda k
: k
[1], reverse
=True)[:number_of_solutions
]
133 def playfair_break_worker(message
, keyword
, wrap
,
134 letters_to_merge
, padding_letter
,
137 plaintext
= playfair_decipher(message
, keyword
, padding_letter
,
142 fit
= fitness(plaintext
)
145 logger
.debug('Playfair break attempt using key {0} (wrap={1}, merging {2}, '
146 'pad replaces={3}), '
147 'gives fit of {4} and decrypt starting: '
148 '{5}'.format(keyword
, wrap
, letters_to_merge
, pad_replace
,
149 fit
, sanitise(plaintext
)[:50]))
150 return (keyword
, wrap
, letters_to_merge
, padding_letter
, pad_replace
), fit
152 def playfair_simulated_annealing_break(message
, workers
=10,
153 initial_temperature
=200,
154 max_iterations
=20000,
156 cipher_alphabet
=None,
157 fitness
=Pletters
, chunksize
=1):
159 ciphertext
= sanitise(message
)
160 for i
in range(workers
):
161 if plain_alphabet
is None:
162 used_plain_alphabet
= string
.ascii_lowercase
164 used_plain_alphabet
= plain_alphabet
165 if cipher_alphabet
is None:
166 # used_cipher_alphabet = list(string.ascii_lowercase)
167 # random.shuffle(used_cipher_alphabet)
168 # used_cipher_alphabet = cat(used_cipher_alphabet)
169 used_cipher_alphabet
= random
.choice(keywords
)
171 used_cipher_alphabet
= cipher_alphabet
172 worker_args
.append((ciphertext
, used_plain_alphabet
, used_cipher_alphabet
,
173 initial_temperature
, max_iterations
, fitness
))
174 with multiprocessing
.Pool() as pool
:
175 breaks
= pool
.starmap(playfair_simulated_annealing_break_worker
,
176 worker_args
, chunksize
)
177 return max(breaks
, key
=lambda k
: k
[1])
179 def playfair_simulated_annealing_break_worker(message
, plain_alphabet
, cipher_alphabet
,
180 t0
, max_iterations
, fitness
):
181 def swap(letters
, i
, j
):
187 return (letters
[:i
] + letters
[j
] + letters
[i
+1:j
] + letters
[i
] +
192 dt
= t0
/ (0.9 * max_iterations
)
194 current_alphabet
= cipher_alphabet
195 current_wrap
= KeywordWrapAlphabet
.from_a
196 current_letters_to_merge
= {'j': 'i'}
197 current_pad_replace
= False
198 current_padding_letter
= 'x'
200 alphabet
= current_alphabet
202 letters_to_merge
= current_letters_to_merge
203 pad_replace
= current_pad_replace
204 padding_letter
= current_padding_letter
205 plaintext
= playfair_decipher(message
, alphabet
, padding_letter
,
209 current_fitness
= fitness(plaintext
)
211 best_alphabet
= current_alphabet
212 best_fitness
= current_fitness
213 best_plaintext
= plaintext
215 # print('starting for', max_iterations)
216 for i
in range(max_iterations
):
217 chosen
= random
.random()
219 # swap_a = random.randrange(26)
220 # swap_b = (swap_a + int(random.gauss(0, 4))) % 26
221 # alphabet = swap(current_alphabet, swap_a, swap_b)
223 # wrap = random.choice(list(KeywordWrapAlphabet))
225 # pad_replace = random.choice([True, False])
226 # elif chosen < 0.95:
227 # letter_from = random.choice(string.ascii_lowercase)
228 # letter_to = random.choice([c for c in string.ascii_lowercase if c != letter_from])
229 # letters_to_merge = {letter_from: letter_to}
231 # padding_letter = random.choice(string.ascii_lowercase)
233 swap_a
= random
.randrange(len(current_alphabet
))
234 swap_b
= (swap_a
+ int(random
.gauss(0, 4))) % len(current_alphabet
)
235 alphabet
= swap(current_alphabet
, swap_a
, swap_b
)
237 new_letter
= random
.choice(string
.ascii_lowercase
)
238 alphabet
= swap(current_alphabet
+ new_letter
, random
.randrange(len(current_alphabet
)), len(current_alphabet
))
240 if len(current_alphabet
) > 1:
241 deletion_position
= random
.randrange(len(current_alphabet
))
242 alphabet
= current_alphabet
[:deletion_position
] + current_alphabet
[deletion_position
+1:]
244 alphabet
= current_alphabet
247 plaintext
= playfair_decipher(message
, alphabet
, padding_letter
,
252 print("Error", alphabet
, padding_letter
,
258 new_fitness
= fitness(plaintext
)
260 sa_chance
= math
.exp((new_fitness
- current_fitness
) / temperature
)
261 except (OverflowError, ZeroDivisionError):
262 # print('exception triggered: new_fit {}, current_fit {}, temp {}'.format(new_fitness, current_fitness, temperature))
264 if (new_fitness
> current_fitness
or random
.random() < sa_chance
):
265 # logger.debug('Simulated annealing: iteration {}, temperature {}, '
266 # 'current alphabet {}, current_fitness {}, '
267 # 'best_plaintext {}'.format(i, temperature, current_alphabet,
268 # current_fitness, best_plaintext[:50]))
270 # logger.debug('new_fit {}, current_fit {}, temp {}, sa_chance {}'.format(new_fitness, current_fitness, temperature, sa_chance))
271 current_fitness
= new_fitness
272 current_alphabet
= alphabet
274 current_letters_to_merge
= letters_to_merge
275 current_pad_replace
= pad_replace
276 current_padding_letter
= padding_letter
278 if current_fitness
> best_fitness
:
279 best_alphabet
= current_alphabet
280 best_wrap
= current_wrap
281 best_letters_to_merge
= current_letters_to_merge
282 best_pad_replace
= current_pad_replace
283 best_padding_letter
= current_padding_letter
284 best_fitness
= current_fitness
285 best_plaintext
= plaintext
287 logger
.debug('Simulated annealing: iteration {}, temperature {}, '
288 'current alphabet {}, current_fitness {}, '
289 'best_plaintext {}'.format(i
, temperature
, current_alphabet
,
290 current_fitness
, plaintext
[:50]))
291 temperature
= max(temperature
- dt
, 0.001)
293 return { 'alphabet': best_alphabet
295 , 'letters_to_merge': best_letters_to_merge
296 , 'pad_replace': best_pad_replace
297 , 'padding_letter': best_padding_letter
298 }, best_fitness
# current_alphabet, current_fitness