spec and Python implementation upload
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
InterPlay-36 cipher
|
||||
===================
|
||||
**InterPlay-36** is a symmetrical cipher that uses a 6x6 key grid for all
|
||||
encryption and decryption operations. It is based upon a historical cipher
|
||||
called Playfair, with several modifications applied to the plaintext and
|
||||
ciphertext.
|
||||
|
||||
InterPlay-36 is easy to implement both in software and manually. Just like
|
||||
the classic Playfair, it is optimized for pen and paper operation, but
|
||||
offers significantly more security at the same time.
|
||||
|
||||
The cipher itself and its reference implementations are public domain.
|
||||
|
||||
Basic alphabet grid
|
||||
-------------------
|
||||
All operations start with the following basic 6x6 grid (Polybius square):
|
||||
|
||||
```
|
||||
_ 1 2 3 4 5
|
||||
6 7 8 9 a b
|
||||
c d e f g h
|
||||
i j k l m n
|
||||
o p q r s t
|
||||
u v w x y z
|
||||
```
|
||||
|
||||
*Note*: The `_` character actually is the whitespace character.
|
||||
|
||||
All incoming plaintext and keys are converted to this alphabet according to
|
||||
the following rules:
|
||||
|
||||
1. The text is converted into lowercase.
|
||||
2. Every occurrence of the `0` (zero) character is replaced with the letter `o`.
|
||||
3. Every occurrence of either of these characters `_-` is replaced with a space.
|
||||
4. Any other characters not belonging to the alphabet are deleted from the text.
|
||||
|
||||
Prior to step 4, the user may apply additional conversions to preserve special
|
||||
characters, such as replacing `%` with `cto`, `$` with `dlr` and so on.
|
||||
|
||||
Key grid preparation
|
||||
--------------------
|
||||
|
||||
### Step 1
|
||||
|
||||
The first step for preparing the key grid is the same as for the Playfair and
|
||||
other classic ciphers based on Polybius squares:
|
||||
|
||||
1. Write the basic alphabet grid into a single string (`_1...9a...z`).
|
||||
2. Prepend the key phrase in front of the basic grid string.
|
||||
3. Strike out all duplicates starting from left to right.
|
||||
4. Rewrite the resulting key grid into the 6x6 square.
|
||||
|
||||
**Example**: Suppose the key phrase is `si vis pacem para bellum`. We can start
|
||||
by eliminating all duplicates in the phrase itself: `si vpacemrblu`. Then we can
|
||||
write the rest of the alphabet: `si vpacemrblu123456789dfghjknoqtwxyz`. After
|
||||
rewriting this string into a 6x6 grid, we can verify that we still have 36
|
||||
unique characters:
|
||||
```
|
||||
s i _ v p a
|
||||
c e m r b l
|
||||
u 1 2 3 4 5
|
||||
6 7 8 9 d f
|
||||
g h j k n o
|
||||
q t w x y z
|
||||
```
|
||||
|
||||
You will also need to note the length KL of the initial keyphrase (the one used
|
||||
before converting it into the key grid) including all internal whitespaces.
|
||||
|
||||
### Step 2
|
||||
|
||||
To finalize the key grid, perform the following steps.
|
||||
|
||||
1. Convert the first **row** of the grid obtained in step 1 into the numeric
|
||||
indices (0-based) from the **base** alphabet.
|
||||
In our example: `si_vpa` => `25, 18, 0, 31, 24, 10`
|
||||
2. Normalize the index list so that it ranges from 0 to 5 according to the
|
||||
order. This will get you the K1 transposition key.
|
||||
In our example: `25, 18, 0, 31, 24, 10` => `4, 2, 0, 5, 3, 1`
|
||||
3. Repeat steps 1 and 2 for the second row of the starting grid. This will get
|
||||
you the K2 transposition key.
|
||||
In our example: `c e m r b l` is the second grid row, further converted to
|
||||
`12, 14, 22, 27, 11, 21` => ` 1, 2, 4, 5, 0, 3`
|
||||
4. Perform a **row** zigzag transposition of the grid according to the K1 key:
|
||||
For each row i taken from the transposition key, take the following values
|
||||
from the key grid G (if i+1 > 5, then use 0 as the value):
|
||||
G(i, 0), G(i+1, 1), G(i, 2), G(i+1, 3), G(i, 4), G(i+1, 5).
|
||||
Write these values as a new row of the intermediate grid.
|
||||
|
||||
In our example, the key is `4, 2, 0, 5, 3, 1` and the grid is:
|
||||
|
||||
```
|
||||
s i _ v p a
|
||||
c e m r b l
|
||||
u 1 2 3 4 5
|
||||
6 7 8 9 d f
|
||||
g h j k n o
|
||||
q t w x y z
|
||||
```
|
||||
|
||||
After the transposition, the grid becomes:
|
||||
|
||||
```
|
||||
g t j x n z
|
||||
u 7 2 9 4 f
|
||||
s e _ r p l
|
||||
q i w v y a
|
||||
6 h 8 k d o
|
||||
c 1 m 3 b 5
|
||||
```
|
||||
|
||||
5. Perform a straight columnar transposition of the step 3 result grid
|
||||
according to the K2 key:
|
||||
For each column i taken from the transposition key, write its values as a
|
||||
new **row** of the final grid.
|
||||
|
||||
In our example, the key is `1, 2, 4, 5, 0, 3` and the grid is:
|
||||
|
||||
```
|
||||
g t j x n z
|
||||
u 7 2 9 4 f
|
||||
s e _ r p l
|
||||
q i w v y a
|
||||
6 h 8 k d o
|
||||
c 1 m 3 b 5
|
||||
```
|
||||
|
||||
After the transposition, the final key grid becomes:
|
||||
|
||||
```
|
||||
t 7 e i h 1
|
||||
j 2 _ w 8 m
|
||||
n 4 p y d b
|
||||
z f l a o 5
|
||||
g u s q 6 c
|
||||
x 9 r v k 3
|
||||
```
|
||||
|
||||
Now we are ready to perform encryption and decryption steps.
|
||||
|
||||
Encryption
|
||||
----------
|
||||
For each plaintext character P1, do the following:
|
||||
|
||||
1. Select a **random** character P2 on the key grid such as P2 != P1.
|
||||
2. Find the positions of P1 and P2 within the key grid.
|
||||
3. If P1 and P2 are not on the same row and not on the same column, find the
|
||||
opposite corners of the imaginary rectangle on the grid. The ciphertext
|
||||
character C1 will be on the same row as P1, and the ciphertext character C2
|
||||
will be on the same row as P2.
|
||||
4. If P1 and P2 are on the same row, C1 and C2 will be to the immediate right
|
||||
of P1 and P2 respectively. If a character is in the rightmost column, the
|
||||
resulting character will be on the first one of the same row.
|
||||
5. If P1 and P2 are on the same column, C1 and C2 will be to the immediate down
|
||||
of P1 and P2 respectively. If a character is in the bottommost row, the
|
||||
resulting character will be on the first one of the same column.
|
||||
6. Write down C1 and C2 as the ciphertext digraph.
|
||||
|
||||
After all plaintext characters are processed, add PL random characters in front
|
||||
of the ciphertext, where PL = KL mod 10 + 1, where KL is the initial key phrase
|
||||
length. None of the prepended characters must be a whitespace.
|
||||
|
||||
Decryption
|
||||
----------
|
||||
Remove first PL characters from the ciphertext, where PL = KL mod 10 + 1, where
|
||||
KL is the initial key phrase length.
|
||||
|
||||
Then, for each ciphertext digraph C1C2, do the following:
|
||||
|
||||
1. Find the first and the second character C1 and C2 within the key grid.
|
||||
2. If C1 and C2 are not on the same row and not on the same column, find the
|
||||
opposite corners of the imaginary rectangle on the grid. The plaintext
|
||||
character P1 will be on the same row as C1.
|
||||
3. If C1 and C2 are on the same row, P1 will be to the immediate left of C1.
|
||||
If C1 is in the leftmost column, P1 will be on the last one of the same row.
|
||||
4. If C1 and C2 are on the same column, P1 will be to the immediate up of C1.
|
||||
If C1 is in the topmost row, P1 will be on the last one of the same column.
|
||||
5. Write down P1 as the next plaintext character.
|
||||
|
||||
Design rationale
|
||||
----------------
|
||||
Playfair cipher had been chosen as the basis for InterPlay-36 because:
|
||||
|
||||
1. It allows to easily reconstruct the basic alphabet and the key grid from
|
||||
the memory.
|
||||
2. In case of manual encryption, the only thing that needs to be written down
|
||||
besides the final ciphertext is the key grid. Obviously, any notes of the
|
||||
key grid preparation must be destroyed.
|
||||
|
||||
InterPlay-36 improves over the classic Playfair in several ways:
|
||||
|
||||
1. Increases the keyspace by increasing the grid size: the theoretical amount
|
||||
of permutations is equivalent to |log2(36!)| = 132 bits of key material, as
|
||||
opposed to 79 key bits in case of the 5x5 grid.
|
||||
2. Adds a plaintext interleaving step into the encryption phase (see below).
|
||||
3. Allows to preserve whitespace in the plaintext while making sure the end
|
||||
ciphertext will never start with whitespace.
|
||||
|
||||
The interleaving step is important to break several negative properties of the
|
||||
Playfair algorithm that make its cryptanalysis easier, like its susceptibility
|
||||
to digraph frequency analysis or the fact that reverse plaintext digraphs are
|
||||
always encrypted to reverse ciphertext digraphs. Interleaving also makes sure
|
||||
that no digraph contains a repeated letter, without complicating the logic.
|
||||
|
||||
### Strengthening
|
||||
|
||||
InterPlay-36 was designed to be used "as is", however one can easily combine it
|
||||
with other popular encryption methods such as transposition ciphers (using the
|
||||
key grid, a transposed key grid and/or the keyphrase length as the key sources).
|
||||
In any case, it is advised to apply InterPlay-36 first in the chain when you are
|
||||
encrypting the messages, and last when decrypting. The reference implementation
|
||||
of InterPlay-36 in HTML5/JS also contains a flag to apply a DCT (double columnar
|
||||
transposition) to the InterPlay-36 ciphertext, where two transposition keys with
|
||||
coprime lengths are derived from the transposed key grid. This feature is now
|
||||
considered experimental and should not be relied upon.
|
||||
|
||||
Reference implementations of InterPlay-36
|
||||
-----------------------------------------
|
||||
|
||||
* [Simple CLI implementation in Python 3](ip36.py)
|
||||
* [Web version in HTML5](https://pf.hoi.st), fully client-side
|
||||
|
||||
Credits
|
||||
-------
|
||||
Created by Luxferre in 2025. Released into public domain with no warranties.
|
||||
@@ -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