e81b066809623dae8db28cd89d1e90132b122e83
3 from itertools
import starmap
, zip_longest
4 from szyfrow
.support
.utilities
import *
5 from szyfrow
.support
.language_models
import *
8 def railfence_encipher(message
, height
, fillvalue
=''):
10 Works by splitting the text into sections, then reading across them to
11 generate the rows in the cipher. The rows are then combined to form the
14 Example: the plaintext "hellotherefriends", with a height of four, written
15 out in the railfence as
20 (with the * showing the one character to finish the last section).
21 Each 'section' is two columns, but unfolded. In the example, the first
24 >>> railfence_encipher('hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers', 2, fillvalue='!')
25 'hlohraateerishsslnpeefetotsigaleccpeselteevsmhatetiiaogicotxfretnrifneihr!'
26 >>> railfence_encipher('hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers', 3, fillvalue='!')
27 'horaersslpeeosglcpselteevsmhatetiiaogicotxfretnrifneihr!!lhateihsnefttiaece!'
28 >>> railfence_encipher('hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers', 5, fillvalue='!')
29 'hresleogcseeemhetaocofrnrner!!lhateihsnefttiaece!!ltvsatiigitxetifih!!oarspeslp!'
30 >>> railfence_encipher('hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers', 10, fillvalue='!')
31 'hepisehagitnr!!lernesge!!lmtocerh!!otiletap!!tseaorii!!hassfolc!!evtitffe!!rahsetec!!eixn!'
32 >>> railfence_encipher('hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers', 3)
33 'horaersslpeeosglcpselteevsmhatetiiaogicotxfretnrifneihrlhateihsnefttiaece'
34 >>> railfence_encipher('hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers', 5)
35 'hresleogcseeemhetaocofrnrnerlhateihsnefttiaeceltvsatiigitxetifihoarspeslp'
36 >>> railfence_encipher('hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers', 7)
37 'haspolsevsetgifrifrlatihnettaeelemtiocxernhorersleesgcptehaiaottneihesfic'
39 sections
= chunks(message
, (height
- 1) * 2, fillvalue
=fillvalue
)
40 n_sections
= len(sections
)
42 rows
= [cat([s
[0] for s
in sections
])]
43 # process the middle rows of the grid
44 for r
in range(1, height
-1):
45 rows
+= [cat([s
[r
:r
+1] + s
[height
*2-r
-2:height
*2-r
-1] for s
in sections
])]
46 # process the bottom row
47 rows
+= [cat([s
[height
- 1:height
] for s
in sections
])]
48 # rows += [wcat([s[height - 1] for s in sections])]
51 def railfence_decipher(message
, height
, fillvalue
=''):
52 """Railfence decipher.
53 Works by reconstructing the grid used to generate the ciphertext, then
54 unfolding the sections so the text can be concatenated together.
56 Example: given the ciphertext 'hhieterelorfnsled' and a height of 4, first
57 work out that the second row has a character missing, find the rows of the
58 grid, then split the section into its two columns.
60 'hhieterelorfnsled' is split into
65 (spaces added for clarity), which is stored in 'rows'. This is then split
66 into 'down_rows' and 'up_rows':
78 These are then zipped together (after the up_rows are reversed) to recover
81 Most of the procedure is about finding the correct lengths for each row then
82 splitting the ciphertext into those rows.
84 >>> railfence_decipher('hlohraateerishsslnpeefetotsigaleccpeselteevsmhatetiiaogicotxfretnrifneihr!', 2).strip('!')
85 'hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers'
86 >>> railfence_decipher('horaersslpeeosglcpselteevsmhatetiiaogicotxfretnrifneihr!!lhateihsnefttiaece!', 3).strip('!')
87 'hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers'
88 >>> railfence_decipher('hresleogcseeemhetaocofrnrner!!lhateihsnefttiaece!!ltvsatiigitxetifih!!oarspeslp!', 5).strip('!')
89 'hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers'
90 >>> railfence_decipher('hepisehagitnr!!lernesge!!lmtocerh!!otiletap!!tseaorii!!hassfolc!!evtitffe!!rahsetec!!eixn!', 10).strip('!')
91 'hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers'
92 >>> railfence_decipher('horaersslpeeosglcpselteevsmhatetiiaogicotxfretnrifneihrlhateihsnefttiaece', 3)
93 'hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers'
94 >>> railfence_decipher('hresleogcseeemhetaocofrnrnerlhateihsnefttiaeceltvsatiigitxetifihoarspeslp', 5)
95 'hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers'
96 >>> railfence_decipher('haspolsevsetgifrifrlatihnettaeelemtiocxernhorersleesgcptehaiaottneihesfic', 7)
97 'hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers'
99 # find the number and size of the sections, including how many characters
100 # are missing for a full grid
101 n_sections
= math
.ceil(len(message
) / ((height
- 1) * 2))
102 padding_to_add
= n_sections
* (height
- 1) * 2 - len(message
)
103 # row_lengths are for the both up rows and down rows
104 row_lengths
= [n_sections
] * (height
- 1) * 2
105 for i
in range((height
- 1) * 2 - 1, (height
- 1) * 2 - (padding_to_add
+ 1), -1):
107 # folded_rows are the combined row lengths in the middle of the railfence
108 folded_row_lengths
= [row_lengths
[0]]
109 for i
in range(1, height
-1):
110 folded_row_lengths
+= [row_lengths
[i
] + row_lengths
[-i
]]
111 folded_row_lengths
+= [row_lengths
[height
- 1]]
112 # find the rows that form the railfence grid
115 for i
in folded_row_lengths
:
116 rows
+= [message
[row_start
:row_start
+ i
]]
118 # split the rows into the 'down_rows' (those that form the first column of
119 # a section) and the 'up_rows' (those that ofrm the second column of a
121 down_rows
= [rows
[0]]
123 for i
in range(1, height
-1):
124 down_rows
+= [cat([c
for n
, c
in enumerate(rows
[i
]) if n
% 2 == 0])]
125 up_rows
+= [cat([c
for n
, c
in enumerate(rows
[i
]) if n
% 2 == 1])]
126 down_rows
+= [rows
[-1]]
128 return cat(c
for r
in zip_longest(*(down_rows
+ up_rows
), fillvalue
='') for c
in r
)
131 def railfence_break(message
, max_key_length
=20,
132 fitness
=Pletters
, chunksize
=500):
133 """Breaks a railfence cipher using a matrix of given rank and letter frequencies
138 sanitised_message
= sanitise(message
)
139 results
= starmap(worker
, [(sanitised_message
, i
, fitness
)
140 for i
in range(2, max_key_length
+1)])
141 return max(results
, key
=lambda k
: k
[1])
144 def railfence_break(message
, max_key_length
=20,
145 fitness
=Pbigrams
, chunksize
=500):
146 """Breaks a railfence cipher using a range of lengths and
147 n-gram frequency analysis
149 >>> railfence_break(railfence_encipher(sanitise( \
150 "It is a truth universally acknowledged, that a single man in \
151 possession of a good fortune, must be in want of a wife. However \
152 little known the feelings or views of such a man may be on his \
153 first entering a neighbourhood, this truth is so well fixed in \
154 the minds of the surrounding families, that he is considered the \
155 rightful property of some one or other of their daughters."), \
156 7)) # doctest: +ELLIPSIS
157 (7, -709.46467226...)
158 >>> railfence_break(railfence_encipher(sanitise( \
159 "It is a truth universally acknowledged, that a single man in \
160 possession of a good fortune, must be in want of a wife. However \
161 little known the feelings or views of such a man may be on his \
162 first entering a neighbourhood, this truth is so well fixed in \
163 the minds of the surrounding families, that he is considered the \
164 rightful property of some one or other of their daughters."), \
166 fitness=Ptrigrams) # doctest: +ELLIPSIS
169 def worker(message
, height
, fitness
):
170 plaintext
= railfence_decipher(message
, height
)
171 fit
= fitness(plaintext
)
174 sanitised_message
= sanitise(message
)
175 results
= starmap(worker
, [(sanitised_message
, i
, fitness
)
176 for i
in range(2, max_key_length
+1)])
177 return max(results
, key
=lambda k
: k
[1])