spec and Python implementation upload
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
# InterPlay-36: reference Python 3 implementation for the
|
||||
# Interleaved 6x6 Playfair crypto algorithm
|
||||
#
|
||||
# Features:
|
||||
# * fixed 6x6 alphabet (whitespace, 1-9, a-z; 0 is replaced with o)
|
||||
# * normal Playfair rules for table keying and encryption/decryption
|
||||
# * the plaintext is interleaved with random chars
|
||||
# before encryption and they are removed after decryption
|
||||
# * since the cryptogram may begin with a space, a random prefix is prepended
|
||||
# * (its length depends on the key length)
|
||||
#
|
||||
# Created by Luxferre in 2025, released into public domain
|
||||
|
||||
import sys, math, secrets
|
||||
from collections import OrderedDict
|
||||
|
||||
pfgrid = ' 123456789abcdefghijklmnopqrstuvwxyz'
|
||||
pfsize = int(math.sqrt(len(pfgrid)))
|
||||
|
||||
# message/keystring preparation step: replace 0 with o then filter out all invalid chars
|
||||
def filtermsg(msg:str):
|
||||
return ''.join(list(filter(lambda i: i in pfgrid, msg.lower().replace('0', 'o'))))
|
||||
|
||||
# grid keying algorithm
|
||||
def keygrid(key:str):
|
||||
# start with the standard fill-in
|
||||
startgrid = list(OrderedDict.fromkeys(filtermsg(key) + pfgrid).keys())
|
||||
# extract the transposition keys
|
||||
tkey1 = []
|
||||
tkey2 = []
|
||||
rkey = list(map(lambda x: pfgrid.index(x), startgrid[0:pfsize])) # first row
|
||||
srkey = sorted(rkey)
|
||||
for c in rkey:
|
||||
tkey1.append(srkey.index(c))
|
||||
rkey = list(map(lambda x: pfgrid.index(x), startgrid[pfsize:pfsize*2])) # second row
|
||||
srkey = sorted(rkey)
|
||||
for c in rkey:
|
||||
tkey2.append(srkey.index(c))
|
||||
# perform the zigzag row transposition into the intermediate grid
|
||||
igrid = []
|
||||
for row in tkey1:
|
||||
for i in range(0, pfsize):
|
||||
igrid.append(startgrid[((row + (i&1)) % pfsize) * pfsize + i])
|
||||
# perform the straight columnar transposition into the final key grid
|
||||
keygrid = []
|
||||
for col in tkey2:
|
||||
for i in range(0, pfsize):
|
||||
keygrid.append(igrid[i * pfsize + col])
|
||||
return keygrid
|
||||
|
||||
# get character coordinates in the grid
|
||||
def getcoords(grid:list, char:str):
|
||||
pos = grid.index(char)
|
||||
return (pos % pfsize, int(pos // pfsize))
|
||||
|
||||
# get grid character by coordinates
|
||||
def getgridchar(grid:list, x:int, y:int):
|
||||
return grid[y * pfsize + x]
|
||||
|
||||
# Interleaved Playfair encryption method
|
||||
def ipfencrypt(msg:str, key:str):
|
||||
kg = keygrid(key)
|
||||
# prepare the message
|
||||
msg = filtermsg(msg).strip()
|
||||
# perform the encryption
|
||||
enc = []
|
||||
for c in msg:
|
||||
# shape the digraphs by interleaving random characters
|
||||
nc = c
|
||||
while nc == c: # the second char in digraph must differ
|
||||
nc = secrets.choice(pfgrid)
|
||||
# run the algo
|
||||
x1, y1 = getcoords(kg, c)
|
||||
x2, y2 = getcoords(kg, nc)
|
||||
if x1 != x2 and y1 != y2: # no coordinates are equal
|
||||
enc.append(getgridchar(kg, x2, y1))
|
||||
enc.append(getgridchar(kg, x1, y2))
|
||||
else:
|
||||
if x1 == x2: # same column: get the coords below
|
||||
y1 = (y1 + 1) % pfsize
|
||||
y2 = (y2 + 1) % pfsize
|
||||
elif y1 == y2: # same row: get the coords to the right
|
||||
x1 = (x1 + 1) % pfsize
|
||||
x2 = (x2 + 1) % pfsize
|
||||
enc.append(getgridchar(kg, x1, y1))
|
||||
enc.append(getgridchar(kg, x2, y2))
|
||||
# generate a random prefix
|
||||
preflen = (len(key) % 10) + 1
|
||||
prefix = ''
|
||||
for i in range(preflen):
|
||||
prefix += secrets.choice(pfgrid[1:])
|
||||
return prefix + ''.join(enc)
|
||||
|
||||
# Interleaved Playfair decryption method
|
||||
def ipfdecrypt(enc:str, key:str):
|
||||
kg = keygrid(key)
|
||||
# remove the prefix, then add a space if the cryptogram
|
||||
# had been stripped and ended with a space
|
||||
preflen = (len(key) % 10) + 1
|
||||
enc = enc[preflen:].rstrip()
|
||||
if len(enc) & 1 == 1:
|
||||
enc += ' '
|
||||
# split into digraphs
|
||||
digraphs = [enc[i:i+2] for i in range(0, len(enc), 2)]
|
||||
# perform the decryption
|
||||
msg = []
|
||||
for dg in digraphs: # for IPF, we only decrypt the first digraph char
|
||||
x1, y1 = getcoords(kg, dg[0])
|
||||
x2, y2 = getcoords(kg, dg[1])
|
||||
if x1 != x2 and y1 != y2: # no coordinates are equal
|
||||
msg.append(getgridchar(kg, x2, y1))
|
||||
else:
|
||||
if x1 == x2: # same column: get the coords above
|
||||
y1 -= 1
|
||||
if y1 < 0:
|
||||
y1 += pfsize
|
||||
elif y1 == y2: # same row: get the coords to the left
|
||||
x1 -= 1
|
||||
if x1 < 0:
|
||||
x1 += pfsize
|
||||
msg.append(getgridchar(kg, x1, y1))
|
||||
# finalize the decrypted message
|
||||
return ''.join(msg).strip()
|
||||
|
||||
# entrypoint parameters: [mode] [keystring]
|
||||
# modes: e - encrypt with 6x6 IPF, d - decrypt with 6x6 IPF
|
||||
def main():
|
||||
mode = sys.argv[1].lower()
|
||||
key = sys.argv[2]
|
||||
msg = sys.stdin.read()
|
||||
if mode.startswith('e'):
|
||||
print(ipfencrypt(msg, key))
|
||||
else:
|
||||
print(ipfdecrypt(msg, key))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user