Skip to content

Commit 0164479

Browse files
Copilotdhondta
andcommitted
Add Playfair cipher codec
Co-authored-by: dhondta <9108102+dhondta@users.noreply.github.com> Agent-Logs-Url: https://github.com/dhondta/python-codext/sessions/4b40b433-0112-42bf-ad61-b7d0d380bc9c
1 parent 351b15f commit 0164479

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed

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 .playfair import *
78
from .polybius import *
89
from .railfence import *
910
from .rot import *

src/codext/crypto/playfair.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# -*- coding: UTF-8 -*-
2+
"""Playfair Cipher Codec - playfair content encoding.
3+
4+
The Playfair cipher is a symmetric encryption method using polygram substitution
5+
with bigrams (pairs of letters), invented in 1854 by Charles Wheatstone, but
6+
popularized by his friend Lord Playfair.
7+
8+
This codec:
9+
- en/decodes strings from str to str
10+
- en/decodes strings from bytes to bytes
11+
- decodes file content to str (read)
12+
- encodes file content from str to bytes (write)
13+
14+
Reference: https://www.dcode.fr/playfair-cipher
15+
"""
16+
from ..__common__ import *
17+
18+
19+
__examples__ = {
20+
# Classic example from Wikipedia (key "PLAYFAIR EXAMPLE"):
21+
# the EE in "TREE" is split with an X filler during encoding, so decoding
22+
# exposes the filler: "TREESTUMP" → encoded → decoded as "TREXESTUMP"
23+
'enc(playfair-playfairexample)': {'HIDETHEGOLDINTHETREESTUMP': 'BMODZBXDNABEKUDMUIXMMOUVIF'},
24+
'dec(playfair-playfairexample)': {'BMODZBXDNABEKUDMUIXMMOUVIF': 'HIDETHEGOLDINTHETREXESTUMP'},
25+
'enc-dec(playfair-keyword)': ['INSTRUMENT'],
26+
}
27+
__guess__ = ["playfair"]
28+
29+
30+
# Standard 5×5 Playfair alphabet (I and J share the same cell)
31+
_DEFAULT_ALPHABET = "ABCDEFGHIKLMNOPQRSTUVWXYZ"
32+
33+
34+
def _build_grid(key=None):
35+
"""Build the 5×5 Playfair grid from an optional keyword."""
36+
seen, grid = set(), []
37+
if key:
38+
for c in key.upper():
39+
if c == 'J':
40+
c = 'I'
41+
if c.isalpha() and c not in seen:
42+
seen.add(c)
43+
grid.append(c)
44+
for c in _DEFAULT_ALPHABET:
45+
if c not in seen:
46+
seen.add(c)
47+
grid.append(c)
48+
pos = {grid[i]: (i // 5, i % 5) for i in range(25)}
49+
return grid, pos
50+
51+
52+
def _filler(c):
53+
"""Return the filler character for a given letter (X, or Q when the letter is X)."""
54+
return 'Q' if c == 'X' else 'X'
55+
56+
57+
def _make_bigrams(text):
58+
"""Convert plaintext to bigrams, inserting fillers for repeated-letter pairs."""
59+
chars = []
60+
for c in ensure_str(text).upper():
61+
if c == 'J':
62+
chars.append('I')
63+
elif c.isalpha():
64+
chars.append(c)
65+
bigrams = []
66+
i = 0
67+
while i < len(chars):
68+
a = chars[i]
69+
if i + 1 < len(chars):
70+
b = chars[i + 1]
71+
if a == b:
72+
bigrams.append((a, _filler(a)))
73+
i += 1
74+
else:
75+
bigrams.append((a, b))
76+
i += 2
77+
else:
78+
bigrams.append((a, _filler(a)))
79+
i += 1
80+
return bigrams
81+
82+
83+
def _encode_bigram(grid, pos, a, b):
84+
r_a, c_a = pos[a]
85+
r_b, c_b = pos[b]
86+
if r_a == r_b:
87+
return grid[r_a * 5 + (c_a + 1) % 5], grid[r_b * 5 + (c_b + 1) % 5]
88+
elif c_a == c_b:
89+
return grid[((r_a + 1) % 5) * 5 + c_a], grid[((r_b + 1) % 5) * 5 + c_b]
90+
else:
91+
return grid[r_a * 5 + c_b], grid[r_b * 5 + c_a]
92+
93+
94+
def _decode_bigram(grid, pos, a, b):
95+
r_a, c_a = pos[a]
96+
r_b, c_b = pos[b]
97+
if r_a == r_b:
98+
return grid[r_a * 5 + (c_a - 1) % 5], grid[r_b * 5 + (c_b - 1) % 5]
99+
elif c_a == c_b:
100+
return grid[((r_a - 1) % 5) * 5 + c_a], grid[((r_b - 1) % 5) * 5 + c_b]
101+
else:
102+
return grid[r_a * 5 + c_b], grid[r_b * 5 + c_a]
103+
104+
105+
def playfair_encode(key=None):
106+
grid, pos = _build_grid(key)
107+
def encode(text, errors="strict"):
108+
t = ensure_str(text)
109+
result = []
110+
for a, b in _make_bigrams(t):
111+
ea, eb = _encode_bigram(grid, pos, a, b)
112+
result.extend([ea, eb])
113+
r = "".join(result)
114+
return r, len(t)
115+
return encode
116+
117+
118+
def playfair_decode(key=None):
119+
grid, pos = _build_grid(key)
120+
def decode(text, errors="strict"):
121+
t = ensure_str(text)
122+
chars = []
123+
for c in t.upper():
124+
if c == 'J':
125+
chars.append('I')
126+
elif c.isalpha():
127+
chars.append(c)
128+
result = []
129+
for i in range(0, len(chars) - 1, 2):
130+
da, db = _decode_bigram(grid, pos, chars[i], chars[i + 1])
131+
result.extend([da, db])
132+
r = "".join(result)
133+
return r, len(t)
134+
return decode
135+
136+
137+
add("playfair", playfair_encode, playfair_decode, r"^playfair(?:[-_]cipher)?(?:[-_]([a-zA-Z]+))?$",
138+
printables_rate=1.)

0 commit comments

Comments
 (0)