2 from itertools
import starmap
, cycle
4 from cipher
.caesar
import *
5 from support
.utilities
import *
6 from support
.language_models
import *
8 from logger
import logger
10 def vigenere_encipher(message
, keyword
):
13 >>> vigenere_encipher('hello', 'abc')
16 shifts
= [pos(l
) for l
in sanitise(keyword
)]
17 pairs
= zip(message
, cycle(shifts
))
18 return cat([caesar_encipher_letter(l
, k
) for l
, k
in pairs
])
20 def vigenere_decipher(message
, keyword
):
23 >>> vigenere_decipher('hfnlp', 'abc')
26 shifts
= [pos(l
) for l
in sanitise(keyword
)]
27 pairs
= zip(message
, cycle(shifts
))
28 return cat([caesar_decipher_letter(l
, k
) for l
, k
in pairs
])
31 def beaufort_encipher(message
, keyword
):
34 >>> beaufort_encipher('inhisjournaldatedtheidesofoctober', 'arcanaimperii')
35 'sevsvrusyrrxfayyxuteemazudmpjmmwr'
37 shifts
= [pos(l
) for l
in sanitise(keyword
)]
38 pairs
= zip(message
, cycle(shifts
))
39 return cat([unpos(k
- pos(l
)) for l
, k
in pairs
])
41 beaufort_decipher
= beaufort_encipher
43 beaufort_variant_encipher
=vigenere_decipher
44 beaufort_variant_decipher
=vigenere_encipher
47 def index_of_coincidence_scan(text
, max_key_length
=20):
48 """Finds the index of coincidence of the text, using different chunk sizes."""
49 stext
= sanitise(text
)
51 for i
in range(1, max_key_length
+ 1):
52 splits
= every_nth(stext
, i
)
53 mean_ioc
= sum(index_of_coincidence(s
) for s
in splits
) / i
57 def vigenere_keyword_break_mp(message
, wordlist
=keywords
, fitness
=Pletters
,
59 """Breaks a vigenere cipher using a dictionary and frequency analysis.
61 >>> vigenere_keyword_break_mp(vigenere_encipher(sanitise('this is a test ' \
62 'message for the vigenere decipherment'), 'cat'), \
63 wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
64 ('cat', -52.9472712...)
66 with multiprocessing
.Pool() as pool
:
67 helper_args
= [(message
, word
, fitness
)
69 # Gotcha: the helper function here needs to be defined at the top level
70 # (limitation of Pool.starmap)
71 breaks
= pool
.starmap(vigenere_keyword_break_worker
, helper_args
,
73 return max(breaks
, key
=lambda k
: k
[1])
74 vigenere_keyword_break
= vigenere_keyword_break_mp
76 def vigenere_keyword_break_worker(message
, keyword
, fitness
):
77 plaintext
= vigenere_decipher(message
, keyword
)
78 fit
= fitness(plaintext
)
79 logger
.debug('Vigenere keyword break attempt using key {0} gives fit of '
80 '{1} and decrypt starting: {2}'.format(keyword
,
81 fit
, sanitise(plaintext
)[:50]))
85 def vigenere_frequency_break(message
, max_key_length
=20, fitness
=Pletters
):
86 """Breaks a Vigenere cipher with frequency analysis
88 >>> vigenere_frequency_break(vigenere_encipher(sanitise("It is time to " \
89 "run. She is ready and so am I. I stole Daniel's pocketbook this " \
90 "afternoon when he left his jacket hanging on the easel in the " \
91 "attic. I jump every time I hear a footstep on the stairs, " \
92 "certain that the theft has been discovered and that I will " \
93 "be caught. The SS officer visits less often now that he is " \
94 "sure"), 'florence')) # doctest: +ELLIPSIS
95 ('florence', -307.5473096...)
97 def worker(message
, key_length
, fitness
):
98 splits
= every_nth(sanitised_message
, key_length
)
99 key
= cat([unpos(caesar_break(s
)[0]) for s
in splits
])
100 plaintext
= vigenere_decipher(message
, key
)
101 fit
= fitness(plaintext
)
103 sanitised_message
= sanitise(message
)
104 results
= starmap(worker
, [(sanitised_message
, i
, fitness
)
105 for i
in range(1, max_key_length
+1)])
106 return max(results
, key
=lambda k
: k
[1])
109 def beaufort_sub_break(message
, fitness
=Pletters
):
110 """Breaks one chunk of a Beaufort cipher with frequency analysis
112 >>> beaufort_sub_break('samwpplggnnmmyaazgympjapopnwiywwomwspgpjmefwmawx' \
113 'jafjhxwwwdigxshnlywiamhyshtasxptwueahhytjwsn') # doctest: +ELLIPSIS
115 >>> beaufort_sub_break('eyprzjjzznxymrygryjqmqhznjrjjapenejznawngnnezgza' \
116 'dgndknaogpdjneadadazlhkhxkryevrronrmdjnndjlo') # doctest: +ELLIPSIS
120 best_fit
= float('-inf')
121 for key
in range(26):
122 plaintext
= [unpos(key
- pos(l
)) for l
in message
]
123 fit
= fitness(plaintext
)
124 logger
.debug('Beaufort sub break attempt using key {0} gives fit of {1} '
125 'and decrypt starting: {2}'.format(key
, fit
,
130 logger
.info('Beaufort sub break best fit: key {0} gives fit of {1} and '
131 'decrypt starting: {2}'.format(best_key
, best_fit
,
132 cat([unpos(best_key
- pos(l
)) for l
in message
[:50]])))
133 return best_key
, best_fit
136 def beaufort_frequency_break(message
, max_key_length
=20, fitness
=Pletters
):
137 """Breaks a Beaufort cipher with frequency analysis
139 >>> beaufort_frequency_break(beaufort_encipher(sanitise("It is time to " \
140 "run. She is ready and so am I. I stole Daniel's pocketbook this " \
141 "afternoon when he left his jacket hanging on the easel in the " \
142 "attic. I jump every time I hear a footstep on the stairs, " \
143 "certain that the theft has been discovered and that I will " \
144 "be caught. The SS officer visits less often now " \
145 "that he is sure"), 'florence')) # doctest: +ELLIPSIS
146 ('florence', -307.5473096791...)
148 def worker(message
, key_length
, fitness
):
149 splits
= every_nth(message
, key_length
)
150 key
= cat([unpos(beaufort_sub_break(s
)[0]) for s
in splits
])
151 plaintext
= beaufort_decipher(message
, key
)
152 fit
= fitness(plaintext
)
154 sanitised_message
= sanitise(message
)
155 results
= starmap(worker
, [(sanitised_message
, i
, fitness
)
156 for i
in range(1, max_key_length
+1)])
157 return max(results
, key
=lambda k
: k
[1])
160 def beaufort_variant_frequency_break(message
, max_key_length
=20, fitness
=Pletters
):
161 """Breaks a Beaufort cipher with frequency analysis
163 >>> beaufort_variant_frequency_break(beaufort_variant_encipher(sanitise("It is time to " \
164 "run. She is ready and so am I. I stole Daniel's pocketbook this " \
165 "afternoon when he left his jacket hanging on the easel in the " \
166 "attic. I jump every time I hear a footstep on the stairs, " \
167 "certain that the theft has been discovered and that I will " \
168 "be caught. The SS officer visits less often now " \
169 "that he is sure"), 'florence')) # doctest: +ELLIPSIS
170 ('florence', -307.5473096791...)
172 def worker(message
, key_length
, fitness
):
173 splits
= every_nth(sanitised_message
, key_length
)
174 key
= cat([unpos(-caesar_break(s
)[0]) for s
in splits
])
175 plaintext
= beaufort_variant_decipher(message
, key
)
176 fit
= fitness(plaintext
)
178 sanitised_message
= sanitise(message
)
179 results
= starmap(worker
, [(sanitised_message
, i
, fitness
)
180 for i
in range(1, max_key_length
+1)])
181 return max(results
, key
=lambda k
: k
[1])
183 if __name__
== "__main__":