Skip to content

Commit 3031357

Browse files
committed
Improved codec: railfence
1 parent 6522ed9 commit 3031357

3 files changed

Lines changed: 69 additions & 46 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ o
259259
- [X] `bacon`: aka Baconian Cipher
260260
- [X] `barbie-N`: aka Barbie Typewriter (*N* belongs to [1, 4])
261261
- [X] `citrix`: aka Citrix CTX1 password encoding
262-
- [X] `rail`: aka Rail Fence Cipher
262+
- [X] `railfence`: aka Rail Fence Cipher
263263
- [X] `rotN`: aka Caesar cipher (*N* belongs to [1,25])
264264
- [X] `scytaleN`: encrypts using the number of letters on the rod (*N* belongs to [1,[)
265265
- [X] `shiftN`: shift ordinals (*N* belongs to [1,255])

codext/crypto/railfence.py

Lines changed: 62 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,39 @@
11
# -*- coding: UTF-8 -*-
2-
"""Rail Fence Cipher Codec - rail fence encoding.
2+
"""Rail Fence Cipher Codec - rail fence content encoding.
33
44
This codec:
55
- en/decodes strings from str to str
66
- en/decodes strings from bytes to bytes
77
- decodes file content to str (read)
88
- encodes file content from str to bytes (write)
99
"""
10-
11-
12-
1310
from ..__common__ import *
1411

1512

1613
__examples__ = {
17-
'enc(rail-5-3|rail_5_3)': {'this is a test' : 'it sss etiath '},
18-
'enc(rail-5-3-up|rail_5_3-up)' :{'this is a test': 'h tiats e ssit'},
19-
'dec(rail-7-4|rail_7_4)': {'a stiet shsti': 'this is a test'}
14+
'enc(rail_123|rail-2-123)': {'this is a test': None},
15+
'enc(railfence|zigzag)': {'this is a test': "t ashsi etist"},
16+
'enc(rail-5|zigzag_5)': {'this is a test': "tah istsiet s"},
17+
'enc(rail_5-3|rail_5_3)': {'this is a test': "it sss etiath "},
18+
'enc(rail-5-3-up|rail_5_3-up)': {'this is a test': "h tiats e ssit"},
19+
'enc(rail-7-4|rail_7_4)': {'this is a test': "a stiet shsti"},
20+
'dec(zigzag)': {'': ""},
2021
}
22+
__guess__ = ["railfence-%d" % i for i in range(1, 11)] + ["railfence-%d-up" % i for i in range(1, 11)]
2123

2224

23-
24-
def __buildf(text, rails, offset = 0, up = 0) :
25+
def __build(text, rails, offset, up):
2526
l, rail = len(text), offset
26-
if up != '' :
27+
# set the starting rail and direction
28+
if up:
2729
dr = -1
2830
rail = rails - offset - 1
29-
else :
31+
else:
3032
dr = 1
31-
32-
f = [["#"] * l for i in range(rails)]
33-
34-
for x in range(l) :
33+
# create rails
34+
f = [[None] * l for i in range(rails)]
35+
# now zig-zag between rails
36+
for x in range(l):
3537
f[rail][x] = text[x]
3638
if rail >= rails - 1:
3739
dr = -1
@@ -40,37 +42,55 @@ def __buildf(text, rails, offset = 0, up = 0) :
4042
rail += dr
4143
return f
4244

43-
def railfence_encode(rails = 3, offset = 0, up = 0) :
44-
def encode(text, errors="strict") :
45-
c,l = '', len(text)
46-
f = __buildf(text, rails, offset, up)
47-
for r in range(rails) :
48-
for x in range(l) :
49-
if f[r][x] != '#' :
50-
c += f[r][x]
51-
return c, l
45+
46+
def __check(length, rails, offset):
47+
if rails > length:
48+
raise ParameterError("Bad parameter for encoding 'railfence': rails=%d (should be >%d)" % (rails, length))
49+
if offset > rails:
50+
raise ParameterError("Bad parameter for encoding 'railfence': offset=%d (should be >%d)" % (offset, rails))
51+
52+
53+
def railfence_encode(rails, offset, up):
54+
rails, offset, up = int(rails or 3), int(offset or 0), up is not None and up != ""
55+
def encode(text, errors="strict"):
56+
r, l = "", len(text)
57+
__check(l, rails, offset)
58+
f = __build(text, rails, offset, up)
59+
for rail in range(rails):
60+
for x in range(l):
61+
if f[rail][x] is not None:
62+
r += f[rail][x]
63+
return r, l
5264
return encode
5365

54-
def railfence_decode(rails = 3, offset = 0, up = 0) :
55-
def decode(text, errors = 'strict') :
56-
f = __buildf("x" * len(text), rails, offset, up)
57-
plain, i = '', 0
58-
ra, l = range(rails), range(len(text))
5966

60-
#Put the characters in the right place
61-
for r in ra:
62-
for x in l :
63-
if f[r][x] == "x" :
64-
f[r][x] = text[i]
67+
def railfence_decode(rails, offset, up):
68+
rails, offset, up = int(rails or 3), int(offset or 0), up is not None and up != ""
69+
def decode(text, errors="strict"):
70+
# this if block is particularly useful with Python2 ; see codecs.py at line 492 in comparison with codecs.py
71+
# from Python3 at line 501: in Python2, a last block can be read while empty while in Python3 not
72+
# as a consequence, in Python2, an error is triggered as an empty text cannot be decoded with Rail Fence with
73+
# a rails parameter > 0 (see the __check(length, rails, offset)) function
74+
if text == "":
75+
return "", 0
76+
r, i, l = "", 0, len(text)
77+
__check(l, rails, offset)
78+
f = __build("." * len(text), rails, offset, up)
79+
# put the characters in the right place
80+
for rail in range(rails):
81+
for x in range(l):
82+
if f[rail][x] == ".":
83+
f[rail][x] = text[i]
6584
i += 1
66-
#Read the characters in the right order
67-
for x in l :
68-
for r in ra:
69-
if f[r][x] != '#' :
70-
plain += f[r][x]
85+
# read the characters in the right order
86+
for x in range(l):
87+
for rail in range(rails):
88+
if f[rail][x] is not None:
89+
r += f[rail][x]
90+
return r, len(text)
91+
return decode
7192

72-
return plain, len(plain)
7393

74-
return decode
94+
add("railfence", railfence_encode, railfence_decode,
95+
r"^(?:rail(?:[-_]?fence)?|zigzag)(?:[-_]([1-9]|[1-9]\d+)(?:[-_]([0-9]|[1-9]\d+))?(?:[-_](up))?)?$")
7596

76-
add("rail", railfence_encode, railfence_decode, r"rail[-_](\d+)[-_](\d+)[-_]?(up)?$")

docs/enc/crypto.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,18 @@ This implements the Citrix CTX1 password encoding algorithm.
130130

131131
### Rail Fence Cipher
132132

133-
This implements the Rail Fence encoding algorithm.
133+
This implements the Rail Fence encoding algorithm, using 3 rails and offset 0 as the default parameters. The encoding fence is built from the top ; the `up` flag can be used to build the fence from the bottom. Note that trying parameters that do not fit the input length will trigger a `ValueError` mentioning the bad value.
134134

135135
**Codec** | **Conversions** | **Aliases** | **Comment**
136136
:---: | :---: | --- | ---
137-
`rail` | text <-> rail fence ciphertext, X rails and Y offset | `rail-X-Y`, `rail_X_Y`, `rail-X-Y-up`| The encoding fence is built from the top. Careful to trailing whitespaces. The `up` flag is used to build the fence from the bottom to the top.
137+
`rail` | text <-> rail fence ciphertext, X rails and Y offset | `rail-X-Y`, `rail_X_Y`, `rail-X-Y-up`, `zigzag`, ... |
138+
138139
```python
140+
>>> codext.encode("this is a test", "zigzag")
141+
't ashsi etist'
139142
>>> codext.encode("this is a test", "rail-5-3")
140143
'it sss etiath '
141-
>>> codext.decode("it sss etiath ", "rail-5-3")
144+
>>> codext.decode("it sss etiath ", "zigzag_5-3")
142145
'this is a test'
143146
```
144147

0 commit comments

Comments
 (0)