2 from support
.utilities
import *
3 from support
.language_models
import *
4 from cipher
.keyword_cipher
import KeywordWrapAlphabet
, keyword_cipher_alphabet_of
6 from logger
import logger
8 def bifid_grid(keyword
, wrap_alphabet
, letter_mapping
):
9 """Create the grids for a Bifid cipher
11 cipher_alphabet
= keyword_cipher_alphabet_of(keyword
, wrap_alphabet
)
12 if letter_mapping
is None:
13 letter_mapping
= {'j': 'i'}
14 translation
= ''.maketrans(letter_mapping
)
15 cipher_alphabet
= cat(collections
.OrderedDict
.fromkeys(cipher_alphabet
.translate(translation
)))
16 f_grid
= {k
: ((i
// 5) + 1, (i
% 5) + 1)
17 for i
, k
in enumerate(cipher_alphabet
)}
18 r_grid
= {((i
// 5) + 1, (i
% 5) + 1): k
19 for i
, k
in enumerate(cipher_alphabet
)}
20 return translation
, f_grid
, r_grid
22 def bifid_encipher(message
, keyword
, wrap_alphabet
=KeywordWrapAlphabet
.from_a
,
23 letter_mapping
=None, period
=None, fillvalue
=None):
26 >>> bifid_encipher("indiajelly", 'iguana')
28 >>> bifid_encipher("indiacurry", 'iguana', period=4)
30 >>> bifid_encipher("indiacurry", 'iguana', period=4, fillvalue='x')
33 translation
, f_grid
, r_grid
= bifid_grid(keyword
, wrap_alphabet
, letter_mapping
)
35 t_message
= message
.translate(translation
)
36 pairs0
= [f_grid
[l
] for l
in sanitise(t_message
)]
38 chunked_pairs
= [pairs0
[i
:i
+period
] for i
in range(0, len(pairs0
), period
)]
39 if len(chunked_pairs
[-1]) < period
and fillvalue
:
40 chunked_pairs
[-1] += [f_grid
[fillvalue
]] * (period
- len(chunked_pairs
[-1]))
42 chunked_pairs
= [pairs0
]
45 for c
in chunked_pairs
:
46 items
= sum(list(list(i
) for i
in zip(*c
)), [])
47 p
= [(items
[i
], items
[i
+1]) for i
in range(0, len(items
), 2)]
50 return cat(r_grid
[p
] for p
in pairs1
)
53 def bifid_decipher(message
, keyword
, wrap_alphabet
=KeywordWrapAlphabet
.from_a
,
54 letter_mapping
=None, period
=None, fillvalue
=None):
55 """Decipher with bifid cipher
57 >>> bifid_decipher('ibidonhprm', 'iguana')
59 >>> bifid_decipher("ibnhgaqltm", 'iguana', period=4)
61 >>> bifid_decipher("ibnhgaqltzml", 'iguana', period=4)
64 translation
, f_grid
, r_grid
= bifid_grid(keyword
, wrap_alphabet
, letter_mapping
)
66 t_message
= message
.translate(translation
)
67 pairs0
= [f_grid
[l
] for l
in sanitise(t_message
)]
69 chunked_pairs
= [pairs0
[i
:i
+period
] for i
in range(0, len(pairs0
), period
)]
70 if len(chunked_pairs
[-1]) < period
and fillvalue
:
71 chunked_pairs
[-1] += [f_grid
[fillvalue
]] * (period
- len(chunked_pairs
[-1]))
73 chunked_pairs
= [pairs0
]
76 for c
in chunked_pairs
:
77 items
= [j
for i
in c
for j
in i
]
79 p
= [(items
[i
], items
[i
+gap
]) for i
in range(gap
)]
82 return cat(r_grid
[p
] for p
in pairs1
)
85 def bifid_break_mp(message
, wordlist
=keywords
, fitness
=Pletters
, max_period
=10,
86 number_of_solutions
=1, chunksize
=500):
87 """Breaks a keyword substitution cipher using a dictionary and
90 >>> bifid_break_mp(bifid_encipher('this is a test message for the ' \
91 'keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last), \
92 wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
93 (('elephant', <KeywordWrapAlphabet.from_last: 2>, 0), -52.834575011...)
94 >>> bifid_break_mp(bifid_encipher('this is a test message for the ' \
95 'keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last), \
96 wordlist=['cat', 'elephant', 'kangaroo'], \
97 number_of_solutions=2) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
98 [(('elephant', <KeywordWrapAlphabet.from_last: 2>, 0), -52.834575011...),
99 (('elephant', <KeywordWrapAlphabet.from_largest: 3>, 0), -52.834575011...)]
101 with multiprocessing
.Pool() as pool
:
102 helper_args
= [(message
, word
, wrap
, period
, fitness
)
104 for wrap
in KeywordWrapAlphabet
105 for period
in range(max_period
+1)]
106 # Gotcha: the helper function here needs to be defined at the top level
107 # (limitation of Pool.starmap)
108 breaks
= pool
.starmap(bifid_break_worker
, helper_args
, chunksize
)
109 if number_of_solutions
== 1:
110 return max(breaks
, key
=lambda k
: k
[1])
112 return sorted(breaks
, key
=lambda k
: k
[1], reverse
=True)[:number_of_solutions
]
114 def bifid_break_worker(message
, keyword
, wrap_alphabet
, period
, fitness
):
115 plaintext
= bifid_decipher(message
, keyword
, wrap_alphabet
, period
=period
)
116 fit
= fitness(plaintext
)
117 logger
.debug('Keyword break attempt using key {0} (wrap={1}) gives fit of '
118 '{2} and decrypt starting: {3}'.format(keyword
,
119 wrap_alphabet
, fit
, sanitise(plaintext
)[:50]))
120 return (keyword
, wrap_alphabet
, period
), fit
122 if __name__
== "__main__":