Skip to content

Commit 1240004

Browse files
committed
Added new codec: polybius
1 parent 10ccc82 commit 1240004

File tree

5 files changed

+108
-3
lines changed

5 files changed

+108
-3
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,8 @@ This category also contains `ascii85`, `adobe`, `[x]btoa`, `zeromq` with the `ba
285285
- [X] `bacon`: aka Baconian Cipher
286286
- [X] `barbie-N`: aka Barbie Typewriter (*N* belongs to [1, 4])
287287
- [X] `citrix`: aka Citrix CTX1 password encoding
288-
- [X] `railfence`: aka Rail Fence Cipher
288+
- [X] `polybius`: aka Polybius Square Cipher
289+
- [X] `railfence`: aka Rail Fence Cipher
289290
- [X] `rotN`: aka Caesar cipher (*N* belongs to [1,25])
290291
- [X] `scytaleN`: encrypts using the number of letters on the rod (*N* belongs to [1,[)
291292
- [X] `shiftN`: shift ordinals (*N* belongs to [1,255])

docs/pages/enc/crypto.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,25 @@ This implements the Citrix CTX1 password encoding algorithm.
126126

127127
-----
128128

129+
### Polybius Square Cipher
130+
131+
This implements the well-known Polybius Square cipher, using the square with the alphabet in normal order as the default. It can be used dynamically with a custom alphabet.
132+
133+
**Codec** | **Conversions** | **Aliases** | **Comment**
134+
:---: | :---: | --- | ---
135+
`polybius` | text <-> polybius square ciphertext | `polybius-square`, `polybius_BACDEFGHIKLMNOPQRSTUVWXYZ`, ... |
136+
137+
```python
138+
>>> codext.encode("this is a test", "polybius")
139+
'44232443 2443 11 44154344'
140+
>>> codext.encode("this is a test", "polybius_BACDEFGHIKLMNOPQRSTUVWXYZ")
141+
'44232443 2443 12 44154344'
142+
>>> codext.decode("44232443 2443 11 441543445", "polybius-square", errors="replace")
143+
'THIS IS A TEST?'
144+
```
145+
146+
-----
147+
129148
### Rail Fence Cipher
130149

131150
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.

src/codext/crypto/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .bacon import *
55
from .barbie import *
66
from .citrix import *
7+
from .polybius import *
78
from .railfence import *
89
from .rot import *
910
from .scytale import *

src/codext/crypto/polybius.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# -*- coding: UTF-8 -*-
2+
"""Polybius Square Codec - polybius-square content encoding.
3+
4+
This codec:
5+
- en/decodes strings from str to str
6+
- en/decodes strings from bytes to bytes
7+
- decodes file content to str (read)
8+
- encodes file content from str to bytes (write)
9+
"""
10+
from ..__common__ import *
11+
12+
13+
__examples__ = {
14+
'enc(polybius|polybius-square|polybius_square)': {'this is a test': "44232443 2443 11 44154344"},
15+
'enc(polybius-ABCDEFGHIKLMNOPQRSTUVWXYZ)': {'this is a test': "44232443 2443 11 44154344"},
16+
'dec(polybius)': {'44232443 2443 11 44154344': "THIS IS A TEST"},
17+
}
18+
__guess__ = ["polybius"]
19+
20+
21+
# Standard 5×5 Polybius square (I and J share the same cell):
22+
# 1 2 3 4 5
23+
# 1 A B C D E
24+
# 2 F G H I K
25+
# 3 L M N O P
26+
# 4 Q R S T U
27+
# 5 V W X Y Z
28+
_DEFAULT_ALPHABET = "ABCDEFGHIKLMNOPQRSTUVWXYZ"
29+
30+
31+
def __make_maps(alphabet):
32+
""" Build the encoding and decoding maps for the given 25-character alphabet. """
33+
alph = alphabet.upper() if alphabet else _DEFAULT_ALPHABET
34+
if len(alph) != 25 or len(set(alph)) != 25:
35+
raise LookupError("Polybius square requires exactly 25 distinct characters; "
36+
f"got {len(alph)} character(s) with {len(set(alph))} unique: {alph}")
37+
encmap = {alph[i]: str(i // 5 + 1) + str(i % 5 + 1) for i in range(25)}
38+
decmap = {v: k for k, v in encmap.items()}
39+
if 'J' not in encmap and 'I' in encmap:
40+
encmap['J'] = encmap['I']
41+
encmap[' '] = ' '
42+
return encmap, decmap
43+
44+
45+
def polybius_encode(alphabet=_DEFAULT_ALPHABET):
46+
encmap, _ = __make_maps(alphabet)
47+
def encode(text, errors="strict"):
48+
_h = handle_error("polybius", errors)
49+
r = ""
50+
for pos, c in enumerate(ensure_str(text).upper()):
51+
r += encmap[c] if c in encmap else _h(c, pos, r)
52+
return r, len(text)
53+
return encode
54+
55+
56+
def polybius_decode(alphabet=_DEFAULT_ALPHABET):
57+
_, decmap = __make_maps(alphabet)
58+
def decode(text, errors="strict"):
59+
_h = handle_error("polybius", errors, decode=True)
60+
r, t, i = "", ensure_str(text), 0
61+
while i < len(t):
62+
if t[i] == " ":
63+
r += " "
64+
i += 1
65+
elif i + 1 < len(t):
66+
r += decmap.get(t[i:i+2]) or _h(t[i:i+2], i, r)
67+
i += 2
68+
else:
69+
r += _h(t[i], i, r)
70+
i += 1
71+
return r, len(t)
72+
return decode
73+
74+
75+
add("polybius", polybius_encode, polybius_decode, r"^polybius(?:[-_]square)?(?:[-_]([A-Za-z]{25}))?$",
76+
printables_rate=1., expansion_factor=(1.7, .3))
77+

tests/test_manual.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@
88
from unittest import TestCase
99

1010
from codext.__common__ import *
11-
from codext.binary.baudot import _check_alphabet
12-
from codext.checksums.crc import CRC
1311

1412

1513
class ComplementaryTestCase(TestCase):
1614
def test_codec_baudot(self):
15+
from codext.binary.baudot import _check_alphabet
1716
self.assertRaises(ValueError, _check_alphabet, ["BAD_ALPHABET"])
1817

1918
def test_codec_dna(self):
@@ -23,6 +22,13 @@ def test_codec_dna(self):
2322
def test_codec_morse(self):
2423
self.assertRaises(LookupError, codecs.encode, "test", "morse-AAB")
2524

25+
def test_codec_polybius(self):
26+
from codext.crypto.polybius import polybius_encode, polybius_decode
27+
self.assertRaises(LookupError, polybius_encode, "ABC")
28+
self.assertRaises(ValueError, polybius_decode(), "BAD_")
29+
self.assertRaises(ValueError, polybius_decode(), "441543441")
30+
self.assertEqual(codecs.decode("441543445", "polybius", "ignore"), "TEST")
31+
2632
def test_codec_sms(self):
2733
self.assertEqual(codecs.decode("A-B-222-3-4-5", "sms", "leave"), "ABcdgj")
2834

@@ -103,6 +109,7 @@ def test_codec_dummy_str_manips(self):
103109
self.assertRaises(LookupError, codecs.encode, STR, "tokenize-200")
104110

105111
def test_codec_hash_functions(self):
112+
from codext.checksums.crc import CRC
106113
STR = b"This is a test string!"
107114
for h in ["adler32", "md2", "md5", "sha1", "sha224", "sha256", "sha384", "sha512"]:
108115
self.assertIsNotNone(codecs.encode(STR, h))

0 commit comments

Comments
 (0)