2 from itertools
import starmap
, cycle
4 from szyfrow
.caesar
import *
5 from szyfrow
.support
.utilities
import *
6 from szyfrow
.support
.language_models
import *
8 def vigenere_encipher(message
, keyword
):
11 >>> vigenere_encipher('hello', 'abc')
14 shifts
= [pos(l
) for l
in sanitise(keyword
)]
15 pairs
= zip(message
, cycle(shifts
))
16 return cat([caesar_encipher_letter(l
, k
) for l
, k
in pairs
])
18 def vigenere_decipher(message
, keyword
):
21 >>> vigenere_decipher('hfnlp', 'abc')
24 shifts
= [pos(l
) for l
in sanitise(keyword
)]
25 pairs
= zip(message
, cycle(shifts
))
26 return cat([caesar_decipher_letter(l
, k
) for l
, k
in pairs
])
29 def beaufort_encipher(message
, keyword
):
32 >>> beaufort_encipher('inhisjournaldatedtheidesofoctober', 'arcanaimperii')
33 'sevsvrusyrrxfayyxuteemazudmpjmmwr'
35 shifts
= [pos(l
) for l
in sanitise(keyword
)]
36 pairs
= zip(message
, cycle(shifts
))
37 return cat([unpos(k
- pos(l
)) for l
, k
in pairs
])
39 beaufort_decipher
= beaufort_encipher
41 beaufort_variant_encipher
=vigenere_decipher
42 beaufort_variant_decipher
=vigenere_encipher
45 def index_of_coincidence_scan(text
, max_key_length
=20):
46 """Finds the index of coincidence of the text, using different chunk sizes."""
47 stext
= sanitise(text
)
49 for i
in range(1, max_key_length
+ 1):
50 splits
= every_nth(stext
, i
)
51 mean_ioc
= sum(index_of_coincidence(s
) for s
in splits
) / i
55 def vigenere_keyword_break_mp(message
, wordlist
=keywords
, fitness
=Pletters
,
57 """Breaks a vigenere cipher using a dictionary and frequency analysis.
59 >>> vigenere_keyword_break_mp(vigenere_encipher(sanitise('this is a test ' \
60 'message for the vigenere decipherment'), 'cat'), \
61 wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
62 ('cat', -52.9472712...)
64 with multiprocessing
.Pool() as pool
:
65 helper_args
= [(message
, word
, fitness
)
67 # Gotcha: the helper function here needs to be defined at the top level
68 # (limitation of Pool.starmap)
69 breaks
= pool
.starmap(vigenere_keyword_break_worker
, helper_args
,
71 return max(breaks
, key
=lambda k
: k
[1])
72 vigenere_keyword_break
= vigenere_keyword_break_mp
74 def vigenere_keyword_break_worker(message
, keyword
, fitness
):
75 plaintext
= vigenere_decipher(message
, keyword
)
76 fit
= fitness(plaintext
)
80 def vigenere_frequency_break(message
, max_key_length
=20, fitness
=Pletters
):
81 """Breaks a Vigenere cipher with frequency analysis
83 >>> vigenere_frequency_break(vigenere_encipher(sanitise("It is time to " \
84 "run. She is ready and so am I. I stole Daniel's pocketbook this " \
85 "afternoon when he left his jacket hanging on the easel in the " \
86 "attic. I jump every time I hear a footstep on the stairs, " \
87 "certain that the theft has been discovered and that I will " \
88 "be caught. The SS officer visits less often now that he is " \
89 "sure"), 'florence')) # doctest: +ELLIPSIS
90 ('florence', -307.5473096...)
92 def worker(message
, key_length
, fitness
):
93 splits
= every_nth(sanitised_message
, key_length
)
94 key
= cat([unpos(caesar_break(s
)[0]) for s
in splits
])
95 plaintext
= vigenere_decipher(message
, key
)
96 fit
= fitness(plaintext
)
98 sanitised_message
= sanitise(message
)
99 results
= starmap(worker
, [(sanitised_message
, i
, fitness
)
100 for i
in range(1, max_key_length
+1)])
101 return max(results
, key
=lambda k
: k
[1])
104 def beaufort_sub_break(message
, fitness
=Pletters
):
105 """Breaks one chunk of a Beaufort cipher with frequency analysis
107 >>> beaufort_sub_break('samwpplggnnmmyaazgympjapopnwiywwomwspgpjmefwmawx' \
108 'jafjhxwwwdigxshnlywiamhyshtasxptwueahhytjwsn') # doctest: +ELLIPSIS
110 >>> beaufort_sub_break('eyprzjjzznxymrygryjqmqhznjrjjapenejznawngnnezgza' \
111 'dgndknaogpdjneadadazlhkhxkryevrronrmdjnndjlo') # doctest: +ELLIPSIS
115 best_fit
= float('-inf')
116 for key
in range(26):
117 plaintext
= [unpos(key
- pos(l
)) for l
in message
]
118 fit
= fitness(plaintext
)
122 return best_key
, best_fit
125 def beaufort_frequency_break(message
, max_key_length
=20, fitness
=Pletters
):
126 """Breaks a Beaufort cipher with frequency analysis
128 >>> beaufort_frequency_break(beaufort_encipher(sanitise("It is time to " \
129 "run. She is ready and so am I. I stole Daniel's pocketbook this " \
130 "afternoon when he left his jacket hanging on the easel in the " \
131 "attic. I jump every time I hear a footstep on the stairs, " \
132 "certain that the theft has been discovered and that I will " \
133 "be caught. The SS officer visits less often now " \
134 "that he is sure"), 'florence')) # doctest: +ELLIPSIS
135 ('florence', -307.5473096791...)
137 def worker(message
, key_length
, fitness
):
138 splits
= every_nth(message
, key_length
)
139 key
= cat([unpos(beaufort_sub_break(s
)[0]) for s
in splits
])
140 plaintext
= beaufort_decipher(message
, key
)
141 fit
= fitness(plaintext
)
143 sanitised_message
= sanitise(message
)
144 results
= starmap(worker
, [(sanitised_message
, i
, fitness
)
145 for i
in range(1, max_key_length
+1)])
146 return max(results
, key
=lambda k
: k
[1])
149 def beaufort_variant_frequency_break(message
, max_key_length
=20, fitness
=Pletters
):
150 """Breaks a Beaufort cipher with frequency analysis
152 >>> beaufort_variant_frequency_break(beaufort_variant_encipher(sanitise("It is time to " \
153 "run. She is ready and so am I. I stole Daniel's pocketbook this " \
154 "afternoon when he left his jacket hanging on the easel in the " \
155 "attic. I jump every time I hear a footstep on the stairs, " \
156 "certain that the theft has been discovered and that I will " \
157 "be caught. The SS officer visits less often now " \
158 "that he is sure"), 'florence')) # doctest: +ELLIPSIS
159 ('florence', -307.5473096791...)
161 def worker(message
, key_length
, fitness
):
162 splits
= every_nth(sanitised_message
, key_length
)
163 key
= cat([unpos(-caesar_break(s
)[0]) for s
in splits
])
164 plaintext
= beaufort_variant_decipher(message
, key
)
165 fit
= fitness(plaintext
)
167 sanitised_message
= sanitise(message
)
168 results
= starmap(worker
, [(sanitised_message
, i
, fitness
)
169 for i
in range(1, max_key_length
+1)])
170 return max(results
, key
=lambda k
: k
[1])
172 if __name__
== "__main__":