5 <meta name=
"viewport" content=
"width=device-width, initial-scale=1, minimum-scale=1" />
6 <meta name=
"generator" content=
"pdoc 0.9.2" />
7 <title>szyfrow.keyword_cipher API documentation
</title>
8 <meta name=
"description" content=
"Monoalphabetic substitution ciphers, mainly done by keyword. Enciphering
9 and deciphering, and a couple of ways to break these ciphers." />
10 <link rel=
"preload stylesheet" as=
"style" href=
"https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity=
"sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin
>
11 <link rel=
"preload stylesheet" as=
"style" href=
"https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity=
"sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin
>
12 <link rel=
"stylesheet preload" as=
"style" href=
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin
>
13 <style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:
1.5em}#content{padding:
20px}#sidebar{padding:
30px;overflow:hidden}#sidebar
> *:last-child{margin-bottom:
2cm}.http-server-breadcrumbs{font-size:
130%;margin:
0 0 15px
0}#footer{font-size:
.75em;padding:
5px
30px;border-top:
1px solid #ddd;text-align:right}#footer p{margin:
0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:
30px}h1,h2,h3,h4,h5{font-weight:
300}h1{font-size:
2.5em;line-height:
1.1em}h2{font-size:
1.75em;margin:
1em
0 .50em
0}h3{font-size:
1.4em;margin:
25px
0 10px
0}h4{margin:
0;font-size:
105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:
.2em
0}a{color:#
058;text-decoration:none;transition:color
.3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^=
"header-"]{margin-top:
2em}.ident{color:#
900}pre code{background:#f8f8f8;font-size:
.8em;line-height:
1.4em}code{background:#f2f2f1;padding:
1px
4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:
0;border-top:
1px solid #ccc;border-bottom:
1px solid #ccc;margin:
1em
0;padding:
1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:
10%}#http-server-module-list p{margin-top:
0}.toc ul,#index{list-style-type:none;margin:
0;padding:
0}#index code{background:transparent}#index h3{border-bottom:
1px solid #ddd}#index ul{padding:
0}#index h4{margin-top:
.6em;font-weight:bold}@media (min-width:
200ex){#index .two-column{column-count:
2}}@media (min-width:
300ex){#index .two-column{column-count:
3}}dl{margin-bottom:
2em}dl dl:last-child{margin-bottom:
4em}dd{margin:
0 0 1em
3em}#header-classes + dl
> dd{margin-bottom:
3em}dd dd{margin-left:
2em}dd p{margin:
10px
0}.name{background:#eee;font-weight:bold;font-size:
.85em;padding:
5px
10px;display:inline-block;min-width:
40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name
> span:first-child{white-space:nowrap}.name.class
> span:nth-child(
2){margin-left:
.4em}.inherited{color:#
999;border-left:
5px solid #eee;padding-left:
1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:
400;font-size:
1.25em}.desc h3{font-size:
1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#
666;text-align:right;font-weight:
400;font-size:
.8em;text-transform:uppercase}.source summary
> *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:
1em}.source pre{max-height:
500px;overflow:auto;margin:
0}.source pre code{font-size:
12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\
2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:
1em}img{max-width:
100%}td{padding:
0 .5em}.admonition{padding:
.1em
.5em;margin-bottom:
1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}
</style>
14 <style media=
"screen and (min-width: 700px)">@media screen and (min-width:
700px){#sidebar{width:
30%;height:
100vh;overflow:auto;position:sticky;top:
0}#content{width:
70%;max-width:
100ch;padding:
3em
4em;border-left:
1px solid #ddd}pre code{font-size:
1em}.item .name{font-size:
1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:
1.5em}.toc
> ul
> li{margin-top:
.5em}}
</style>
15 <style media=
"print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#
000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:
" (" attr(href)
")";font-size:
90%}a[href][title]:after{content:none}abbr[title]:after{content:
" (" attr(title)
")"}.ir a:after,a[href^=
"javascript:"]:after,a[href^=
"#"]:after{content:
""}pre,blockquote{border:
1px solid #
999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:
100% !important}@page{margin:
0.5cm}p,h2,h3{orphans:
3;widows:
3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}
</style>
16 <script defer
src=
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity=
"sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin
></script>
17 <script>window.addEventListener('DOMContentLoaded', () =
> hljs.initHighlighting())
</script>
21 <article id=
"content">
23 <h1 class=
"title">Module
<code>szyfrow.keyword_cipher
</code></h1>
25 <section id=
"section-intro">
26 <p>Monoalphabetic substitution ciphers, mainly done by keyword. Enciphering
27 and deciphering, and a couple of ways to break these ciphers.
</p>
28 <details class=
"source">
30 <span>Expand source code
</span>
32 <pre><code class=
"python">"""Monoalphabetic substitution ciphers, mainly done by keyword. Enciphering
33 and deciphering, and a couple of ways to break these ciphers.
36 import multiprocessing
38 from szyfrow.support.utilities import *
39 from szyfrow.support.language_models import *
42 class KeywordWrapAlphabet(Enum):
43 """Ways to list the rest of the alphabet after use of a keyword.
45 * `from_a` : continue the alphabet from
'a
': `bayescdfg...`
46 * `from_last`: continue from the last letter of the keyword:
48 * `from_largest`: continue from the
"largest
" letter of the keyword:
56 def keyword_cipher_alphabet_of(keyword, wrap_alphabet=KeywordWrapAlphabet.from_a):
57 """Find the cipher alphabet given a keyword.
59 [`wrap_alphabet`](#szyfrow.keyword_cipher.KeywordWrapAlphabet) controls
60 how the rest of the alphabet is added after the keyword.
62 >>> keyword_cipher_alphabet_of(
'bayes
')
63 'bayescdfghijklmnopqrtuvwxz
'
64 >>> keyword_cipher_alphabet_of(
'bayes
', KeywordWrapAlphabet.from_a)
65 'bayescdfghijklmnopqrtuvwxz
'
66 >>> keyword_cipher_alphabet_of(
'bayes
', KeywordWrapAlphabet.from_last)
67 'bayestuvwxzcdfghijklmnopqr
'
68 >>> keyword_cipher_alphabet_of(
'bayes
', KeywordWrapAlphabet.from_largest)
69 'bayeszcdfghijklmnopqrtuvwx
'
71 if wrap_alphabet == KeywordWrapAlphabet.from_a:
72 cipher_alphabet = cat(deduplicate(sanitise(keyword) +
73 string.ascii_lowercase))
75 if wrap_alphabet == KeywordWrapAlphabet.from_last:
76 last_keyword_letter = deduplicate(sanitise(keyword))[-
1]
78 last_keyword_letter = sorted(sanitise(keyword))[-
1]
79 last_keyword_position = string.ascii_lowercase.find(
80 last_keyword_letter) +
1
81 cipher_alphabet = cat(
82 deduplicate(sanitise(keyword) +
83 string.ascii_lowercase[last_keyword_position:] +
84 string.ascii_lowercase))
85 return cipher_alphabet
88 def keyword_encipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a):
89 """Enciphers a message with a keyword substitution cipher.
90 wrap_alphabet controls how the rest of the alphabet is added
93 1 : from the last letter in the sanitised keyword
94 2 : from the largest letter in the sanitised keyword
96 >>> keyword_encipher(
'test message
',
'bayes
')
97 'rsqr ksqqbds
'
98 >>> keyword_encipher(
'test message
',
'bayes
', KeywordWrapAlphabet.from_a)
99 'rsqr ksqqbds
'
100 >>> keyword_encipher(
'test message
',
'bayes
', KeywordWrapAlphabet.from_last)
101 'lskl dskkbus
'
102 >>> keyword_encipher(
'test message
',
'bayes
', KeywordWrapAlphabet.from_largest)
103 'qspq jsppbcs
'
105 cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
106 cipher_translation =
''.maketrans(string.ascii_lowercase, cipher_alphabet)
107 return unaccent(message).lower().translate(cipher_translation)
109 def keyword_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a):
110 """Deciphers a message with a keyword substitution cipher.
111 wrap_alphabet controls how the rest of the alphabet is added
114 1 : from the last letter in the sanitised keyword
115 2 : from the largest letter in the sanitised keyword
117 >>> keyword_decipher(
'rsqr ksqqbds
',
'bayes
')
118 'test message
'
119 >>> keyword_decipher(
'rsqr ksqqbds
',
'bayes
', KeywordWrapAlphabet.from_a)
120 'test message
'
121 >>> keyword_decipher(
'lskl dskkbus
',
'bayes
', KeywordWrapAlphabet.from_last)
122 'test message
'
123 >>> keyword_decipher(
'qspq jsppbcs
',
'bayes
', KeywordWrapAlphabet.from_largest)
124 'test message
'
126 cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
127 cipher_translation =
''.maketrans(cipher_alphabet, string.ascii_lowercase)
128 return message.lower().translate(cipher_translation)
131 def keyword_break_single_thread(message, wordlist=None, fitness=Pletters):
132 """Breaks a keyword substitution cipher using a dictionary and
135 If `wordlist` is not specified, use
136 [`szyfrow.support.langauge_models.keywords`](support/language_models.html#szyfrow.support.language_models.keywords).
138 >>> keyword_break(keyword_encipher(
'this is a test message for the
' \
139 'keyword decipherment
',
'elephant
', KeywordWrapAlphabet.from_last), \
140 wordlist=[
'cat
',
'elephant
',
'kangaroo
']) # doctest: +ELLIPSIS
141 ((
'elephant
',
<KeywordWrapAlphabet.from_last:
2>), -
52.834575011...)
146 best_keyword =
''
147 best_wrap_alphabet = True
148 best_fit = float(
"-inf
")
149 for wrap_alphabet in KeywordWrapAlphabet:
150 for keyword in wordlist:
151 plaintext = keyword_decipher(message, keyword, wrap_alphabet)
152 fit = fitness(plaintext)
153 if fit
> best_fit:
155 best_keyword = keyword
156 best_wrap_alphabet = wrap_alphabet
157 return (best_keyword, best_wrap_alphabet), best_fit
159 def keyword_break(message, wordlist=None, fitness=Pletters,
160 number_of_solutions=
1, chunksize=
500):
161 """Breaks a keyword substitution cipher using a dictionary and
164 If `wordlist` is not specified, use
165 [`szyfrow.support.langauge_models.keywords`](support/language_models.html#szyfrow.support.language_models.keywords).
168 >>> keyword_break_mp(keyword_encipher(
'this is a test message for the
' \
169 'keyword decipherment
',
'elephant
', KeywordWrapAlphabet.from_last), \
170 wordlist=[
'cat
',
'elephant
',
'kangaroo
']) # doctest: +ELLIPSIS
171 ((
'elephant
',
<KeywordWrapAlphabet.from_last:
2>), -
52.834575011...)
172 >>> keyword_break_mp(keyword_encipher(
'this is a test message for the
' \
173 'keyword decipherment
',
'elephant
', KeywordWrapAlphabet.from_last), \
174 wordlist=[
'cat
',
'elephant
',
'kangaroo
'], \
175 number_of_solutions=
2) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
176 [((
'elephant
',
<KeywordWrapAlphabet.from_last:
2>), -
52.834575011...),
177 ((
'elephant
',
<KeywordWrapAlphabet.from_largest:
3>), -
52.834575011...)]
182 with multiprocessing.Pool() as pool:
183 helper_args = [(message, word, wrap, fitness)
185 for wrap in KeywordWrapAlphabet]
186 # Gotcha: the helper function here needs to be defined at the top level
187 # (limitation of Pool.starmap)
188 breaks = pool.starmap(keyword_break_worker, helper_args, chunksize)
189 if number_of_solutions ==
1:
190 return max(breaks, key=lambda k: k[
1])
192 return sorted(breaks, key=lambda k: k[
1], reverse=True)[:number_of_solutions]
194 def keyword_break_worker(message, keyword, wrap_alphabet, fitness):
195 plaintext = keyword_decipher(message, keyword, wrap_alphabet)
196 fit = fitness(plaintext)
197 return (keyword, wrap_alphabet), fit
200 def monoalphabetic_break_hillclimbing_single(message,
201 max_iterations=
20000,
203 cipher_alphabet=None,
204 swap_index_finder=None,
205 fitness=Pletters, chunksize=
1):
206 """Break a monalphabetic substitution cipher using hillclimbing to
207 guess the keyword. Hillclimbing is done by using the simulated annealing
208 approach with a temperature of zero.
210 This version uses a single worker.
212 return monoalphabetic_sa_break(message,
214 initial_temperature=
0,
215 max_iterations=max_iterations,
216 plain_alphabet=plain_alphabet,
217 cipher_alphabet=cipher_alphabet,
218 swap_index_finder=swap_index_finder,
219 fitness=fitness, chunksize=chunksize)
222 def monoalphabetic_break_hillclimbing(message,
224 max_iterations=
20000,
226 cipher_alphabet=None,
227 swap_index_finder=None,
228 fitness=Pletters, chunksize=
1):
229 """Break a monalphabetic substitution cipher using hillclimbing to
230 guess the keyword. Hillclimbing is done by using the simulated annealing
231 approach with a temperature of zero.
233 This version uses a several workers.
235 return monoalphabetic_sa_break(message,
237 initial_temperature=
0,
238 max_iterations=max_iterations,
239 plain_alphabet=plain_alphabet,
240 cipher_alphabet=cipher_alphabet,
241 swap_index_finder=swap_index_finder,
242 fitness=fitness, chunksize=chunksize)
245 def gaussian_swap_index(a):
246 """Return an index to use as the partner of `a` in a swap. The partners
247 are drawn from a Gaussian distribution.
249 return (a + int(random.gauss(
0,
4))) %
26
251 def uniform_swap_index(a):
252 """Return an index to use as the partner of `a` in a swap. The partners
253 are drawn from a uniform distribution.
255 return random.randrange(
26)
257 def monoalphabetic_sa_break(message, workers=
10,
258 initial_temperature=
200,
259 max_iterations=
20000,
261 cipher_alphabet=None,
262 swap_index_finder=None,
263 fitness=Ptrigrams, chunksize=
1):
264 """Break a monalphabetic substitution cipher using simulated annealing to
265 guess the keyword. This function just sets up a stable of workers who
266 do the actual work, implemented as
267 `szyfrow.keyword_cipher.monoalphabetic_sa_break_worker`.
269 See a [post on simulated annealing](https://work.njae.me.uk/
2019/
07/
08/simulated-annealing-and-breaking-substitution-ciphers/)
270 for detail on how this works.
273 ciphertext = sanitise(message)
274 if swap_index_finder is None:
275 swap_index_finder = gaussian_swap_index
277 for i in range(workers):
278 if plain_alphabet is None:
279 used_plain_alphabet = string.ascii_lowercase
281 used_plain_alphabet = plain_alphabet
282 if cipher_alphabet is None:
283 used_cipher_alphabet = list(string.ascii_lowercase)
284 random.shuffle(used_cipher_alphabet)
285 used_cipher_alphabet = cat(used_cipher_alphabet)
287 used_cipher_alphabet = cipher_alphabet
288 # if not plain_alphabet:
289 # plain_alphabet = string.ascii_lowercase
290 # if not cipher_alphabet:
291 # cipher_alphabet = list(string.ascii_lowercase)
292 # random.shuffle(cipher_alphabet)
293 # cipher_alphabet = cat(cipher_alphabet)
294 worker_args.append((ciphertext, used_plain_alphabet, used_cipher_alphabet,
296 initial_temperature, max_iterations, fitness,
298 with multiprocessing.Pool() as pool:
299 breaks = pool.starmap(monoalphabetic_sa_break_worker,
300 worker_args, chunksize)
301 return max(breaks, key=lambda k: k[
1])
304 def monoalphabetic_sa_break_worker(message, plain_alphabet, cipher_alphabet,
306 t0, max_iterations, fitness,
308 """One thread of a simulated annealing run.
309 See a [post on simulated annealing](https://work.njae.me.uk/
2019/
07/
08/simulated-annealing-and-breaking-substitution-ciphers/)
310 for detail on how this works.
312 def swap(letters, i, j):
318 return (letters[:i] + letters[j] + letters[i+
1:j] + letters[i] +
323 dt = t0 / (
0.9 * max_iterations)
325 current_alphabet = cipher_alphabet
326 alphabet = current_alphabet
327 cipher_translation =
''.maketrans(current_alphabet, plain_alphabet)
328 plaintext = message.translate(cipher_translation)
329 current_fitness = fitness(plaintext)
331 best_alphabet = current_alphabet
332 best_fitness = current_fitness
333 best_plaintext = plaintext
335 # print(
'starting for
', max_iterations)
336 for i in range(max_iterations):
337 swap_a = random.randrange(
26)
338 # swap_b = (swap_a + int(random.gauss(
0,
4))) %
26
339 swap_b = swap_index_finder(swap_a)
340 alphabet = swap(current_alphabet, swap_a, swap_b)
341 cipher_translation =
''.maketrans(alphabet, plain_alphabet)
342 plaintext = message.translate(cipher_translation)
343 new_fitness = fitness(plaintext)
345 sa_chance = math.exp((new_fitness - current_fitness) / temperature)
346 except (OverflowError, ZeroDivisionError):
347 # print(
'exception triggered: new_fit {}, current_fit {}, temp {}
'.format(new_fitness, current_fitness, temperature))
349 if (new_fitness
> current_fitness or random.random()
< sa_chance):
350 current_fitness = new_fitness
351 current_alphabet = alphabet
353 if current_fitness
> best_fitness:
354 best_alphabet = current_alphabet
355 best_fitness = current_fitness
356 best_plaintext = plaintext
358 temperature = max(temperature - dt,
0.001)
360 return best_alphabet, best_fitness # current_alphabet, current_fitness
362 if __name__ ==
"__main__
":
363 import doctest
</code></pre>
371 <h2 class=
"section-title" id=
"header-functions">Functions
</h2>
373 <dt id=
"szyfrow.keyword_cipher.cat"><code class=
"name flex">
374 <span>def
<span class=
"ident">cat
</span></span>(
<span>iterable, /)
</span>
377 <div class=
"desc"><p>Concatenate any number of strings.
</p>
378 <p>The string whose method is called is inserted in between each given string.
379 The result is returned as a new string.
</p>
380 <p>Example: '.'.join(['ab', 'pq', 'rs']) -
> 'ab.pq.rs'
</p></div>
382 <dt id=
"szyfrow.keyword_cipher.gaussian_swap_index"><code class=
"name flex">
383 <span>def
<span class=
"ident">gaussian_swap_index
</span></span>(
<span>a)
</span>
386 <div class=
"desc"><p>Return an index to use as the partner of
<code>a
</code> in a swap. The partners
387 are drawn from a Gaussian distribution.
</p></div>
388 <details class=
"source">
390 <span>Expand source code
</span>
392 <pre><code class=
"python">def gaussian_swap_index(a):
393 """Return an index to use as the partner of `a` in a swap. The partners
394 are drawn from a Gaussian distribution.
396 return (a + int(random.gauss(
0,
4))) %
26</code></pre>
399 <dt id=
"szyfrow.keyword_cipher.keyword_break"><code class=
"name flex">
400 <span>def
<span class=
"ident">keyword_break
</span></span>(
<span>message, wordlist=None, fitness=
<function Pletters
>, number_of_solutions=
1, chunksize=
500)
</span>
403 <div class=
"desc"><p>Breaks a keyword substitution cipher using a dictionary and
404 frequency analysis.
</p>
405 <p>If
<code>wordlist
</code> is not specified, use
406 <a href=
"support/language_models.html#szyfrow.support.language_models.keywords"><code>szyfrow.support.langauge_models.keywords
</code></a>.
</p>
407 <pre><code class=
"language-python-repl">>>> keyword_break_mp(keyword_encipher('this is a test message for the ' 'keyword decipherment', 'elephant', KeywordWrapAlphabet.from_last), wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
408 (('elephant',
<KeywordWrapAlphabet.from_last:
2>), -
52.834575011...)
409 >>> keyword_break_mp(keyword_encipher('this is a test message for the ' 'keyword decipherment', 'elephant', KeywordWrapAlphabet.from_last), wordlist=['cat', 'elephant', 'kangaroo'], number_of_solutions=
2) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
410 [(('elephant',
<KeywordWrapAlphabet.from_last:
2>), -
52.834575011...),
411 (('elephant',
<KeywordWrapAlphabet.from_largest:
3>), -
52.834575011...)]
413 <details class=
"source">
415 <span>Expand source code
</span>
417 <pre><code class=
"python">def keyword_break(message, wordlist=None, fitness=Pletters,
418 number_of_solutions=
1, chunksize=
500):
419 """Breaks a keyword substitution cipher using a dictionary and
422 If `wordlist` is not specified, use
423 [`szyfrow.support.langauge_models.keywords`](support/language_models.html#szyfrow.support.language_models.keywords).
426 >>> keyword_break_mp(keyword_encipher(
'this is a test message for the
' \
427 'keyword decipherment
',
'elephant
', KeywordWrapAlphabet.from_last), \
428 wordlist=[
'cat
',
'elephant
',
'kangaroo
']) # doctest: +ELLIPSIS
429 ((
'elephant
',
<KeywordWrapAlphabet.from_last:
2>), -
52.834575011...)
430 >>> keyword_break_mp(keyword_encipher(
'this is a test message for the
' \
431 'keyword decipherment
',
'elephant
', KeywordWrapAlphabet.from_last), \
432 wordlist=[
'cat
',
'elephant
',
'kangaroo
'], \
433 number_of_solutions=
2) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
434 [((
'elephant
',
<KeywordWrapAlphabet.from_last:
2>), -
52.834575011...),
435 ((
'elephant
',
<KeywordWrapAlphabet.from_largest:
3>), -
52.834575011...)]
440 with multiprocessing.Pool() as pool:
441 helper_args = [(message, word, wrap, fitness)
443 for wrap in KeywordWrapAlphabet]
444 # Gotcha: the helper function here needs to be defined at the top level
445 # (limitation of Pool.starmap)
446 breaks = pool.starmap(keyword_break_worker, helper_args, chunksize)
447 if number_of_solutions ==
1:
448 return max(breaks, key=lambda k: k[
1])
450 return sorted(breaks, key=lambda k: k[
1], reverse=True)[:number_of_solutions]
</code></pre>
453 <dt id=
"szyfrow.keyword_cipher.keyword_break_single_thread"><code class=
"name flex">
454 <span>def
<span class=
"ident">keyword_break_single_thread
</span></span>(
<span>message, wordlist=None, fitness=
<function Pletters
>)
</span>
457 <div class=
"desc"><p>Breaks a keyword substitution cipher using a dictionary and
458 frequency analysis.
</p>
459 <p>If
<code>wordlist
</code> is not specified, use
460 <a href=
"support/language_models.html#szyfrow.support.language_models.keywords"><code>szyfrow.support.langauge_models.keywords
</code></a>.
</p>
461 <pre><code class=
"language-python-repl">>>> keyword_break(keyword_encipher('this is a test message for the ' 'keyword decipherment', 'elephant', KeywordWrapAlphabet.from_last), wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
462 (('elephant',
<KeywordWrapAlphabet.from_last:
2>), -
52.834575011...)
464 <details class=
"source">
466 <span>Expand source code
</span>
468 <pre><code class=
"python">def keyword_break_single_thread(message, wordlist=None, fitness=Pletters):
469 """Breaks a keyword substitution cipher using a dictionary and
472 If `wordlist` is not specified, use
473 [`szyfrow.support.langauge_models.keywords`](support/language_models.html#szyfrow.support.language_models.keywords).
475 >>> keyword_break(keyword_encipher(
'this is a test message for the
' \
476 'keyword decipherment
',
'elephant
', KeywordWrapAlphabet.from_last), \
477 wordlist=[
'cat
',
'elephant
',
'kangaroo
']) # doctest: +ELLIPSIS
478 ((
'elephant
',
<KeywordWrapAlphabet.from_last:
2>), -
52.834575011...)
483 best_keyword =
''
484 best_wrap_alphabet = True
485 best_fit = float(
"-inf
")
486 for wrap_alphabet in KeywordWrapAlphabet:
487 for keyword in wordlist:
488 plaintext = keyword_decipher(message, keyword, wrap_alphabet)
489 fit = fitness(plaintext)
490 if fit
> best_fit:
492 best_keyword = keyword
493 best_wrap_alphabet = wrap_alphabet
494 return (best_keyword, best_wrap_alphabet), best_fit
</code></pre>
497 <dt id=
"szyfrow.keyword_cipher.keyword_break_worker"><code class=
"name flex">
498 <span>def
<span class=
"ident">keyword_break_worker
</span></span>(
<span>message, keyword, wrap_alphabet, fitness)
</span>
501 <div class=
"desc"></div>
502 <details class=
"source">
504 <span>Expand source code
</span>
506 <pre><code class=
"python">def keyword_break_worker(message, keyword, wrap_alphabet, fitness):
507 plaintext = keyword_decipher(message, keyword, wrap_alphabet)
508 fit = fitness(plaintext)
509 return (keyword, wrap_alphabet), fit
</code></pre>
512 <dt id=
"szyfrow.keyword_cipher.keyword_cipher_alphabet_of"><code class=
"name flex">
513 <span>def
<span class=
"ident">keyword_cipher_alphabet_of
</span></span>(
<span>keyword, wrap_alphabet=KeywordWrapAlphabet.from_a)
</span>
516 <div class=
"desc"><p>Find the cipher alphabet given a keyword.
</p>
517 <p><a href=
"#szyfrow.keyword_cipher.KeywordWrapAlphabet"><code>wrap_alphabet
</code></a> controls
518 how the rest of the alphabet is added after the keyword.
</p>
519 <pre><code class=
"language-python-repl">>>> keyword_cipher_alphabet_of('bayes')
520 'bayescdfghijklmnopqrtuvwxz'
521 >>> keyword_cipher_alphabet_of('bayes', KeywordWrapAlphabet.from_a)
522 'bayescdfghijklmnopqrtuvwxz'
523 >>> keyword_cipher_alphabet_of('bayes', KeywordWrapAlphabet.from_last)
524 'bayestuvwxzcdfghijklmnopqr'
525 >>> keyword_cipher_alphabet_of('bayes', KeywordWrapAlphabet.from_largest)
526 'bayeszcdfghijklmnopqrtuvwx'
528 <details class=
"source">
530 <span>Expand source code
</span>
532 <pre><code class=
"python">def keyword_cipher_alphabet_of(keyword, wrap_alphabet=KeywordWrapAlphabet.from_a):
533 """Find the cipher alphabet given a keyword.
535 [`wrap_alphabet`](#szyfrow.keyword_cipher.KeywordWrapAlphabet) controls
536 how the rest of the alphabet is added after the keyword.
538 >>> keyword_cipher_alphabet_of(
'bayes
')
539 'bayescdfghijklmnopqrtuvwxz
'
540 >>> keyword_cipher_alphabet_of(
'bayes
', KeywordWrapAlphabet.from_a)
541 'bayescdfghijklmnopqrtuvwxz
'
542 >>> keyword_cipher_alphabet_of(
'bayes
', KeywordWrapAlphabet.from_last)
543 'bayestuvwxzcdfghijklmnopqr
'
544 >>> keyword_cipher_alphabet_of(
'bayes
', KeywordWrapAlphabet.from_largest)
545 'bayeszcdfghijklmnopqrtuvwx
'
547 if wrap_alphabet == KeywordWrapAlphabet.from_a:
548 cipher_alphabet = cat(deduplicate(sanitise(keyword) +
549 string.ascii_lowercase))
551 if wrap_alphabet == KeywordWrapAlphabet.from_last:
552 last_keyword_letter = deduplicate(sanitise(keyword))[-
1]
554 last_keyword_letter = sorted(sanitise(keyword))[-
1]
555 last_keyword_position = string.ascii_lowercase.find(
556 last_keyword_letter) +
1
557 cipher_alphabet = cat(
558 deduplicate(sanitise(keyword) +
559 string.ascii_lowercase[last_keyword_position:] +
560 string.ascii_lowercase))
561 return cipher_alphabet
</code></pre>
564 <dt id=
"szyfrow.keyword_cipher.keyword_decipher"><code class=
"name flex">
565 <span>def
<span class=
"ident">keyword_decipher
</span></span>(
<span>message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a)
</span>
568 <div class=
"desc"><p>Deciphers a message with a keyword substitution cipher.
569 wrap_alphabet controls how the rest of the alphabet is added
572 1 : from the last letter in the sanitised keyword
573 2 : from the largest letter in the sanitised keyword
</p>
574 <pre><code class=
"language-python-repl">>>> keyword_decipher('rsqr ksqqbds', 'bayes')
576 >>> keyword_decipher('rsqr ksqqbds', 'bayes', KeywordWrapAlphabet.from_a)
578 >>> keyword_decipher('lskl dskkbus', 'bayes', KeywordWrapAlphabet.from_last)
580 >>> keyword_decipher('qspq jsppbcs', 'bayes', KeywordWrapAlphabet.from_largest)
583 <details class=
"source">
585 <span>Expand source code
</span>
587 <pre><code class=
"python">def keyword_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a):
588 """Deciphers a message with a keyword substitution cipher.
589 wrap_alphabet controls how the rest of the alphabet is added
592 1 : from the last letter in the sanitised keyword
593 2 : from the largest letter in the sanitised keyword
595 >>> keyword_decipher(
'rsqr ksqqbds
',
'bayes
')
596 'test message
'
597 >>> keyword_decipher(
'rsqr ksqqbds
',
'bayes
', KeywordWrapAlphabet.from_a)
598 'test message
'
599 >>> keyword_decipher(
'lskl dskkbus
',
'bayes
', KeywordWrapAlphabet.from_last)
600 'test message
'
601 >>> keyword_decipher(
'qspq jsppbcs
',
'bayes
', KeywordWrapAlphabet.from_largest)
602 'test message
'
604 cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
605 cipher_translation =
''.maketrans(cipher_alphabet, string.ascii_lowercase)
606 return message.lower().translate(cipher_translation)
</code></pre>
609 <dt id=
"szyfrow.keyword_cipher.keyword_encipher"><code class=
"name flex">
610 <span>def
<span class=
"ident">keyword_encipher
</span></span>(
<span>message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a)
</span>
613 <div class=
"desc"><p>Enciphers a message with a keyword substitution cipher.
614 wrap_alphabet controls how the rest of the alphabet is added
617 1 : from the last letter in the sanitised keyword
618 2 : from the largest letter in the sanitised keyword
</p>
619 <pre><code class=
"language-python-repl">>>> keyword_encipher('test message', 'bayes')
621 >>> keyword_encipher('test message', 'bayes', KeywordWrapAlphabet.from_a)
623 >>> keyword_encipher('test message', 'bayes', KeywordWrapAlphabet.from_last)
625 >>> keyword_encipher('test message', 'bayes', KeywordWrapAlphabet.from_largest)
628 <details class=
"source">
630 <span>Expand source code
</span>
632 <pre><code class=
"python">def keyword_encipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a):
633 """Enciphers a message with a keyword substitution cipher.
634 wrap_alphabet controls how the rest of the alphabet is added
637 1 : from the last letter in the sanitised keyword
638 2 : from the largest letter in the sanitised keyword
640 >>> keyword_encipher(
'test message
',
'bayes
')
641 'rsqr ksqqbds
'
642 >>> keyword_encipher(
'test message
',
'bayes
', KeywordWrapAlphabet.from_a)
643 'rsqr ksqqbds
'
644 >>> keyword_encipher(
'test message
',
'bayes
', KeywordWrapAlphabet.from_last)
645 'lskl dskkbus
'
646 >>> keyword_encipher(
'test message
',
'bayes
', KeywordWrapAlphabet.from_largest)
647 'qspq jsppbcs
'
649 cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
650 cipher_translation =
''.maketrans(string.ascii_lowercase, cipher_alphabet)
651 return unaccent(message).lower().translate(cipher_translation)
</code></pre>
654 <dt id=
"szyfrow.keyword_cipher.lcat"><code class=
"name flex">
655 <span>def
<span class=
"ident">lcat
</span></span>(
<span>iterable, /)
</span>
658 <div class=
"desc"><p>Concatenate any number of strings.
</p>
659 <p>The string whose method is called is inserted in between each given string.
660 The result is returned as a new string.
</p>
661 <p>Example: '.'.join(['ab', 'pq', 'rs']) -
> 'ab.pq.rs'
</p></div>
663 <dt id=
"szyfrow.keyword_cipher.monoalphabetic_break_hillclimbing"><code class=
"name flex">
664 <span>def
<span class=
"ident">monoalphabetic_break_hillclimbing
</span></span>(
<span>message, workers=
10, max_iterations=
20000, plain_alphabet=None, cipher_alphabet=None, swap_index_finder=None, fitness=
<function Pletters
>, chunksize=
1)
</span>
667 <div class=
"desc"><p>Break a monalphabetic substitution cipher using hillclimbing to
668 guess the keyword. Hillclimbing is done by using the simulated annealing
669 approach with a temperature of zero.
</p>
670 <p>This version uses a several workers.
</p></div>
671 <details class=
"source">
673 <span>Expand source code
</span>
675 <pre><code class=
"python">def monoalphabetic_break_hillclimbing(message,
677 max_iterations=
20000,
679 cipher_alphabet=None,
680 swap_index_finder=None,
681 fitness=Pletters, chunksize=
1):
682 """Break a monalphabetic substitution cipher using hillclimbing to
683 guess the keyword. Hillclimbing is done by using the simulated annealing
684 approach with a temperature of zero.
686 This version uses a several workers.
688 return monoalphabetic_sa_break(message,
690 initial_temperature=
0,
691 max_iterations=max_iterations,
692 plain_alphabet=plain_alphabet,
693 cipher_alphabet=cipher_alphabet,
694 swap_index_finder=swap_index_finder,
695 fitness=fitness, chunksize=chunksize)
</code></pre>
698 <dt id=
"szyfrow.keyword_cipher.monoalphabetic_break_hillclimbing_single"><code class=
"name flex">
699 <span>def
<span class=
"ident">monoalphabetic_break_hillclimbing_single
</span></span>(
<span>message, max_iterations=
20000, plain_alphabet=None, cipher_alphabet=None, swap_index_finder=None, fitness=
<function Pletters
>, chunksize=
1)
</span>
702 <div class=
"desc"><p>Break a monalphabetic substitution cipher using hillclimbing to
703 guess the keyword. Hillclimbing is done by using the simulated annealing
704 approach with a temperature of zero.
</p>
705 <p>This version uses a single worker.
</p></div>
706 <details class=
"source">
708 <span>Expand source code
</span>
710 <pre><code class=
"python">def monoalphabetic_break_hillclimbing_single(message,
711 max_iterations=
20000,
713 cipher_alphabet=None,
714 swap_index_finder=None,
715 fitness=Pletters, chunksize=
1):
716 """Break a monalphabetic substitution cipher using hillclimbing to
717 guess the keyword. Hillclimbing is done by using the simulated annealing
718 approach with a temperature of zero.
720 This version uses a single worker.
722 return monoalphabetic_sa_break(message,
724 initial_temperature=
0,
725 max_iterations=max_iterations,
726 plain_alphabet=plain_alphabet,
727 cipher_alphabet=cipher_alphabet,
728 swap_index_finder=swap_index_finder,
729 fitness=fitness, chunksize=chunksize)
</code></pre>
732 <dt id=
"szyfrow.keyword_cipher.monoalphabetic_sa_break"><code class=
"name flex">
733 <span>def
<span class=
"ident">monoalphabetic_sa_break
</span></span>(
<span>message, workers=
10, initial_temperature=
200, max_iterations=
20000, plain_alphabet=None, cipher_alphabet=None, swap_index_finder=None, fitness=
<function Ptrigrams
>, chunksize=
1)
</span>
736 <div class=
"desc"><p>Break a monalphabetic substitution cipher using simulated annealing to
737 guess the keyword. This function just sets up a stable of workers who
738 do the actual work, implemented as
739 <code><a title=
"szyfrow.keyword_cipher.monoalphabetic_sa_break_worker" href=
"#szyfrow.keyword_cipher.monoalphabetic_sa_break_worker">monoalphabetic_sa_break_worker()
</a></code>.
</p>
740 <p>See a
<a href=
"https://work.njae.me.uk/2019/07/08/simulated-annealing-and-breaking-substitution-ciphers/">post on simulated annealing
</a>
741 for detail on how this works.
</p></div>
742 <details class=
"source">
744 <span>Expand source code
</span>
746 <pre><code class=
"python">def monoalphabetic_sa_break(message, workers=
10,
747 initial_temperature=
200,
748 max_iterations=
20000,
750 cipher_alphabet=None,
751 swap_index_finder=None,
752 fitness=Ptrigrams, chunksize=
1):
753 """Break a monalphabetic substitution cipher using simulated annealing to
754 guess the keyword. This function just sets up a stable of workers who
755 do the actual work, implemented as
756 `szyfrow.keyword_cipher.monoalphabetic_sa_break_worker`.
758 See a [post on simulated annealing](https://work.njae.me.uk/
2019/
07/
08/simulated-annealing-and-breaking-substitution-ciphers/)
759 for detail on how this works.
762 ciphertext = sanitise(message)
763 if swap_index_finder is None:
764 swap_index_finder = gaussian_swap_index
766 for i in range(workers):
767 if plain_alphabet is None:
768 used_plain_alphabet = string.ascii_lowercase
770 used_plain_alphabet = plain_alphabet
771 if cipher_alphabet is None:
772 used_cipher_alphabet = list(string.ascii_lowercase)
773 random.shuffle(used_cipher_alphabet)
774 used_cipher_alphabet = cat(used_cipher_alphabet)
776 used_cipher_alphabet = cipher_alphabet
777 # if not plain_alphabet:
778 # plain_alphabet = string.ascii_lowercase
779 # if not cipher_alphabet:
780 # cipher_alphabet = list(string.ascii_lowercase)
781 # random.shuffle(cipher_alphabet)
782 # cipher_alphabet = cat(cipher_alphabet)
783 worker_args.append((ciphertext, used_plain_alphabet, used_cipher_alphabet,
785 initial_temperature, max_iterations, fitness,
787 with multiprocessing.Pool() as pool:
788 breaks = pool.starmap(monoalphabetic_sa_break_worker,
789 worker_args, chunksize)
790 return max(breaks, key=lambda k: k[
1])
</code></pre>
793 <dt id=
"szyfrow.keyword_cipher.monoalphabetic_sa_break_worker"><code class=
"name flex">
794 <span>def
<span class=
"ident">monoalphabetic_sa_break_worker
</span></span>(
<span>message, plain_alphabet, cipher_alphabet, swap_index_finder, t0, max_iterations, fitness, logID)
</span>
797 <div class=
"desc"><p>One thread of a simulated annealing run.
798 See a
<a href=
"https://work.njae.me.uk/2019/07/08/simulated-annealing-and-breaking-substitution-ciphers/">post on simulated annealing
</a>
799 for detail on how this works.
</p></div>
800 <details class=
"source">
802 <span>Expand source code
</span>
804 <pre><code class=
"python">def monoalphabetic_sa_break_worker(message, plain_alphabet, cipher_alphabet,
806 t0, max_iterations, fitness,
808 """One thread of a simulated annealing run.
809 See a [post on simulated annealing](https://work.njae.me.uk/
2019/
07/
08/simulated-annealing-and-breaking-substitution-ciphers/)
810 for detail on how this works.
812 def swap(letters, i, j):
818 return (letters[:i] + letters[j] + letters[i+
1:j] + letters[i] +
823 dt = t0 / (
0.9 * max_iterations)
825 current_alphabet = cipher_alphabet
826 alphabet = current_alphabet
827 cipher_translation =
''.maketrans(current_alphabet, plain_alphabet)
828 plaintext = message.translate(cipher_translation)
829 current_fitness = fitness(plaintext)
831 best_alphabet = current_alphabet
832 best_fitness = current_fitness
833 best_plaintext = plaintext
835 # print(
'starting for
', max_iterations)
836 for i in range(max_iterations):
837 swap_a = random.randrange(
26)
838 # swap_b = (swap_a + int(random.gauss(
0,
4))) %
26
839 swap_b = swap_index_finder(swap_a)
840 alphabet = swap(current_alphabet, swap_a, swap_b)
841 cipher_translation =
''.maketrans(alphabet, plain_alphabet)
842 plaintext = message.translate(cipher_translation)
843 new_fitness = fitness(plaintext)
845 sa_chance = math.exp((new_fitness - current_fitness) / temperature)
846 except (OverflowError, ZeroDivisionError):
847 # print(
'exception triggered: new_fit {}, current_fit {}, temp {}
'.format(new_fitness, current_fitness, temperature))
849 if (new_fitness
> current_fitness or random.random()
< sa_chance):
850 current_fitness = new_fitness
851 current_alphabet = alphabet
853 if current_fitness
> best_fitness:
854 best_alphabet = current_alphabet
855 best_fitness = current_fitness
856 best_plaintext = plaintext
858 temperature = max(temperature - dt,
0.001)
860 return best_alphabet, best_fitness # current_alphabet, current_fitness
</code></pre>
863 <dt id=
"szyfrow.keyword_cipher.uniform_swap_index"><code class=
"name flex">
864 <span>def
<span class=
"ident">uniform_swap_index
</span></span>(
<span>a)
</span>
867 <div class=
"desc"><p>Return an index to use as the partner of
<code>a
</code> in a swap. The partners
868 are drawn from a uniform distribution.
</p></div>
869 <details class=
"source">
871 <span>Expand source code
</span>
873 <pre><code class=
"python">def uniform_swap_index(a):
874 """Return an index to use as the partner of `a` in a swap. The partners
875 are drawn from a uniform distribution.
877 return random.randrange(
26)
</code></pre>
880 <dt id=
"szyfrow.keyword_cipher.wcat"><code class=
"name flex">
881 <span>def
<span class=
"ident">wcat
</span></span>(
<span>iterable, /)
</span>
884 <div class=
"desc"><p>Concatenate any number of strings.
</p>
885 <p>The string whose method is called is inserted in between each given string.
886 The result is returned as a new string.
</p>
887 <p>Example: '.'.join(['ab', 'pq', 'rs']) -
> 'ab.pq.rs'
</p></div>
892 <h2 class=
"section-title" id=
"header-classes">Classes
</h2>
894 <dt id=
"szyfrow.keyword_cipher.KeywordWrapAlphabet"><code class=
"flex name class">
895 <span>class
<span class=
"ident">KeywordWrapAlphabet
</span></span>
896 <span>(
</span><span>value, names=None, *, module=None, qualname=None, type=None, start=
1)
</span>
899 <div class=
"desc"><p>Ways to list the rest of the alphabet after use of a keyword.
</p>
901 <li><code>from_a
</code> : continue the alphabet from 'a':
<code>bayescdfg
…</code></li>
902 <li><code>from_last
</code>: continue from the last letter of the keyword:
903 <code>bayestuvwxyzcdf
…</code></li>
904 <li><code>from_largest
</code>: continue from the
"largest" letter of the keyword:
905 <code>bayeszcdfg
…</code></li>
907 <details class=
"source">
909 <span>Expand source code
</span>
911 <pre><code class=
"python">class KeywordWrapAlphabet(Enum):
912 """Ways to list the rest of the alphabet after use of a keyword.
914 * `from_a` : continue the alphabet from
'a
': `bayescdfg...`
915 * `from_last`: continue from the last letter of the keyword:
917 * `from_largest`: continue from the
"largest
" letter of the keyword:
922 from_largest =
3</code></pre>
928 <h3>Class variables
</h3>
930 <dt id=
"szyfrow.keyword_cipher.KeywordWrapAlphabet.from_a"><code class=
"name">var
<span class=
"ident">from_a
</span></code></dt>
932 <div class=
"desc"></div>
934 <dt id=
"szyfrow.keyword_cipher.KeywordWrapAlphabet.from_largest"><code class=
"name">var
<span class=
"ident">from_largest
</span></code></dt>
936 <div class=
"desc"></div>
938 <dt id=
"szyfrow.keyword_cipher.KeywordWrapAlphabet.from_last"><code class=
"name">var
<span class=
"ident">from_last
</span></code></dt>
940 <div class=
"desc"></div>
953 <li><h3>Super-module
</h3>
955 <li><code><a title=
"szyfrow" href=
"index.html">szyfrow
</a></code></li>
958 <li><h3><a href=
"#header-functions">Functions
</a></h3>
960 <li><code><a title=
"szyfrow.keyword_cipher.cat" href=
"#szyfrow.keyword_cipher.cat">cat
</a></code></li>
961 <li><code><a title=
"szyfrow.keyword_cipher.gaussian_swap_index" href=
"#szyfrow.keyword_cipher.gaussian_swap_index">gaussian_swap_index
</a></code></li>
962 <li><code><a title=
"szyfrow.keyword_cipher.keyword_break" href=
"#szyfrow.keyword_cipher.keyword_break">keyword_break
</a></code></li>
963 <li><code><a title=
"szyfrow.keyword_cipher.keyword_break_single_thread" href=
"#szyfrow.keyword_cipher.keyword_break_single_thread">keyword_break_single_thread
</a></code></li>
964 <li><code><a title=
"szyfrow.keyword_cipher.keyword_break_worker" href=
"#szyfrow.keyword_cipher.keyword_break_worker">keyword_break_worker
</a></code></li>
965 <li><code><a title=
"szyfrow.keyword_cipher.keyword_cipher_alphabet_of" href=
"#szyfrow.keyword_cipher.keyword_cipher_alphabet_of">keyword_cipher_alphabet_of
</a></code></li>
966 <li><code><a title=
"szyfrow.keyword_cipher.keyword_decipher" href=
"#szyfrow.keyword_cipher.keyword_decipher">keyword_decipher
</a></code></li>
967 <li><code><a title=
"szyfrow.keyword_cipher.keyword_encipher" href=
"#szyfrow.keyword_cipher.keyword_encipher">keyword_encipher
</a></code></li>
968 <li><code><a title=
"szyfrow.keyword_cipher.lcat" href=
"#szyfrow.keyword_cipher.lcat">lcat
</a></code></li>
969 <li><code><a title=
"szyfrow.keyword_cipher.monoalphabetic_break_hillclimbing" href=
"#szyfrow.keyword_cipher.monoalphabetic_break_hillclimbing">monoalphabetic_break_hillclimbing
</a></code></li>
970 <li><code><a title=
"szyfrow.keyword_cipher.monoalphabetic_break_hillclimbing_single" href=
"#szyfrow.keyword_cipher.monoalphabetic_break_hillclimbing_single">monoalphabetic_break_hillclimbing_single
</a></code></li>
971 <li><code><a title=
"szyfrow.keyword_cipher.monoalphabetic_sa_break" href=
"#szyfrow.keyword_cipher.monoalphabetic_sa_break">monoalphabetic_sa_break
</a></code></li>
972 <li><code><a title=
"szyfrow.keyword_cipher.monoalphabetic_sa_break_worker" href=
"#szyfrow.keyword_cipher.monoalphabetic_sa_break_worker">monoalphabetic_sa_break_worker
</a></code></li>
973 <li><code><a title=
"szyfrow.keyword_cipher.uniform_swap_index" href=
"#szyfrow.keyword_cipher.uniform_swap_index">uniform_swap_index
</a></code></li>
974 <li><code><a title=
"szyfrow.keyword_cipher.wcat" href=
"#szyfrow.keyword_cipher.wcat">wcat
</a></code></li>
977 <li><h3><a href=
"#header-classes">Classes
</a></h3>
980 <h4><code><a title=
"szyfrow.keyword_cipher.KeywordWrapAlphabet" href=
"#szyfrow.keyword_cipher.KeywordWrapAlphabet">KeywordWrapAlphabet
</a></code></h4>
982 <li><code><a title=
"szyfrow.keyword_cipher.KeywordWrapAlphabet.from_a" href=
"#szyfrow.keyword_cipher.KeywordWrapAlphabet.from_a">from_a
</a></code></li>
983 <li><code><a title=
"szyfrow.keyword_cipher.KeywordWrapAlphabet.from_largest" href=
"#szyfrow.keyword_cipher.KeywordWrapAlphabet.from_largest">from_largest
</a></code></li>
984 <li><code><a title=
"szyfrow.keyword_cipher.KeywordWrapAlphabet.from_last" href=
"#szyfrow.keyword_cipher.KeywordWrapAlphabet.from_last">from_last
</a></code></li>
993 <p>Generated by
<a href=
"https://pdoc3.github.io/pdoc"><cite>pdoc
</cite> 0.9.2</a>.
</p>