967bff4838dbfc5772b132de6eacb043b7c8021a
[szyfrow.git] / szyfrow / bifid.py
1 import multiprocessing
2 from szyfrow.support.utilities import *
3 from szyfrow.support.language_models import *
4 from szyfrow.keyword_cipher import KeywordWrapAlphabet, keyword_cipher_alphabet_of
5
6 def bifid_grid(keyword, wrap_alphabet, letter_mapping):
7 """Create the grids for a Bifid cipher
8 """
9 cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
10 if letter_mapping is None:
11 letter_mapping = {'j': 'i'}
12 translation = ''.maketrans(letter_mapping)
13 cipher_alphabet = cat(collections.OrderedDict.fromkeys(cipher_alphabet.translate(translation)))
14 f_grid = {k: ((i // 5) + 1, (i % 5) + 1)
15 for i, k in enumerate(cipher_alphabet)}
16 r_grid = {((i // 5) + 1, (i % 5) + 1): k
17 for i, k in enumerate(cipher_alphabet)}
18 return translation, f_grid, r_grid
19
20 def bifid_encipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a,
21 letter_mapping=None, period=None, fillvalue=None):
22 """Bifid cipher
23
24 >>> bifid_encipher("indiajelly", 'iguana')
25 'ibidonhprm'
26 >>> bifid_encipher("indiacurry", 'iguana', period=4)
27 'ibnhgaqltm'
28 >>> bifid_encipher("indiacurry", 'iguana', period=4, fillvalue='x')
29 'ibnhgaqltzml'
30 """
31
32 if period:
33 if not fillvalue:
34 raise ValueError("fillvalue must be given if period is given")
35 else:
36 p_message = message + pad(len(message), period, fillvalue)
37 else:
38 p_message = message
39
40 translation, f_grid, r_grid = bifid_grid(keyword, wrap_alphabet, letter_mapping)
41
42 t_message = p_message.translate(translation)
43 pairs0 = [f_grid[l] for l in sanitise(t_message)]
44 if period:
45 # chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)]
46 # if len(chunked_pairs[-1]) < period and fillvalue:
47 # chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1]))
48 chunked_pairs = chunks(pairs0, period, fillvalue=None)
49 else:
50 chunked_pairs = [pairs0]
51
52 pairs1 = []
53 for c in chunked_pairs:
54 items = sum(list(list(i) for i in zip(*c)), [])
55 p = [(items[i], items[i+1]) for i in range(0, len(items), 2)]
56 pairs1 += p
57
58 return cat(r_grid[p] for p in pairs1)
59
60
61 def bifid_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a,
62 letter_mapping=None, period=None, fillvalue=None):
63 """Decipher with bifid cipher
64
65 >>> bifid_decipher('ibidonhprm', 'iguana')
66 'indiaielly'
67 >>> bifid_decipher("ibnhgaqltm", 'iguana', period=4)
68 'indiacurry'
69 >>> bifid_decipher("ibnhgaqltzml", 'iguana', period=4)
70 'indiacurryxx'
71 """
72 if period:
73 if not fillvalue:
74 raise ValueError("fillvalue must be given if period is given")
75 else:
76 p_message = message + pad(len(message), period, fillvalue)
77 else:
78 p_message = message
79
80 translation, f_grid, r_grid = bifid_grid(keyword, wrap_alphabet, letter_mapping)
81
82 t_message = message.translate(translation)
83 pairs0 = [f_grid[l] for l in sanitise(t_message)]
84 if period:
85 # chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)]
86 # if len(chunked_pairs[-1]) < period and fillvalue:
87 # chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1]))
88 chunked_pairs = chunks(pairs0, period, fillvalue=None)
89 else:
90 chunked_pairs = [pairs0]
91
92 pairs1 = []
93 for c in chunked_pairs:
94 items = [j for i in c for j in i]
95 gap = len(c)
96 p = [(items[i], items[i+gap]) for i in range(gap)]
97 pairs1 += p
98
99 return cat(r_grid[p] for p in pairs1)
100
101
102 def bifid_break(message, wordlist=keywords, fitness=Pletters, max_period=10,
103 number_of_solutions=1, chunksize=500):
104 """Breaks a keyword substitution cipher using a dictionary and
105 frequency analysis
106
107 >>> bifid_break_mp(bifid_encipher('this is a test message for the ' \
108 'keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last), \
109 wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
110 (('elephant', <KeywordWrapAlphabet.from_last: 2>, 0), -52.834575011...)
111 >>> bifid_break_mp(bifid_encipher('this is a test message for the ' \
112 'keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last), \
113 wordlist=['cat', 'elephant', 'kangaroo'], \
114 number_of_solutions=2) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
115 [(('elephant', <KeywordWrapAlphabet.from_last: 2>, 0), -52.834575011...),
116 (('elephant', <KeywordWrapAlphabet.from_largest: 3>, 0), -52.834575011...)]
117 """
118 with multiprocessing.Pool() as pool:
119 helper_args = [(message, word, wrap, period, fitness)
120 for word in wordlist
121 for wrap in KeywordWrapAlphabet
122 for period in range(max_period+1)]
123 # Gotcha: the helper function here needs to be defined at the top level
124 # (limitation of Pool.starmap)
125 breaks = pool.starmap(bifid_break_worker, helper_args, chunksize)
126 if number_of_solutions == 1:
127 return max(breaks, key=lambda k: k[1])
128 else:
129 return sorted(breaks, key=lambda k: k[1], reverse=True)[:number_of_solutions]
130
131 def bifid_break_worker(message, keyword, wrap_alphabet, period, fitness):
132 plaintext = bifid_decipher(message, keyword, wrap_alphabet,
133 period=period, fillvalue='e')
134 fit = fitness(plaintext)
135 return (keyword, wrap_alphabet, period), fit
136
137 if __name__ == "__main__":
138 import doctest