67 lines
2.9 KiB
Python
67 lines
2.9 KiB
Python
"""
|
|
The ADFGVX cipher implemented as a Python class
|
|
See also eg. https://www.nku.edu/~christensen/092hnr304%20ADFGVX.pdf
|
|
"""
|
|
|
|
from random import shuffle, choice
|
|
from itertools import product, accumulate
|
|
from numpy import floor, sqrt
|
|
|
|
class ADFGVX:
|
|
""" The WWI German ADFGVX cipher. """
|
|
def __init__(self, spoly, k, alph='ADFGVX'):
|
|
self.polybius = list(spoly.upper())
|
|
self.pdim = int(floor(sqrt(len(self.polybius))))
|
|
self.key = list(k.upper())
|
|
self.keylen = len(self.key)
|
|
self.alphabet = list(alph)
|
|
pairs = [p[0] + p[1] for p in product(self.alphabet, self.alphabet)]
|
|
self.encode = dict(zip(self.polybius, pairs))
|
|
self.decode = dict((v, k) for (k, v) in self.encode.items())
|
|
|
|
def encrypt(self, msg):
|
|
""" Encrypt with the ADFGVX cipher. """
|
|
chars = list(''.join([self.encode[c] for c in msg.upper() if c in self.polybius]))
|
|
colvecs = [(lett, chars[i:len(chars):self.keylen]) \
|
|
for (i, lett) in enumerate(self.key)]
|
|
colvecs.sort(key=lambda x: x[0])
|
|
return ''.join([''.join(a[1]) for a in colvecs])
|
|
|
|
def decrypt(self, cod):
|
|
""" Decrypt with the ADFGVX cipher. Does not depend on spacing of encoded text """
|
|
chars = [c for c in cod if c in self.alphabet]
|
|
sortedkey = sorted(self.key)
|
|
order = [self.key.index(ch) for ch in sortedkey]
|
|
originalorder = [sortedkey.index(ch) for ch in self.key]
|
|
base, extra = divmod(len(chars), self.keylen)
|
|
strides = [base + (1 if extra > i else 0) for i in order] # shuffled column lengths
|
|
starts = list(accumulate(strides[:-1], lambda x, y: x + y)) # shuffled starts of columns
|
|
starts = [0] + starts # starting index
|
|
ends = [starts[i] + strides[i] for i in range(self.keylen)] # shuffled ends of columns
|
|
cols = [chars[starts[i]:ends[i]] for i in originalorder] # get reordered columns
|
|
pairs = [] # recover the rows
|
|
for i in range((len(chars) - 1) // self.keylen + 1):
|
|
for j in range(self.keylen):
|
|
if i * self.keylen + j < len(chars):
|
|
pairs.append(cols[j][i])
|
|
|
|
return ''.join([self.decode[pairs[i] + pairs[i + 1]] for i in range(0, len(pairs), 2)])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
PCHARS = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
|
|
shuffle(PCHARS)
|
|
POLYBIUS = ''.join(PCHARS)
|
|
with open('unixdict.txt') as fh:
|
|
WORDS = [w for w in (fh.read()).split() \
|
|
if len(w) == 9 and len(w) == len(set(list(w)))]
|
|
KEY = choice(WORDS)
|
|
|
|
SECRET, MESSAGE = ADFGVX(POLYBIUS, KEY), 'ATTACKAT1200AM'
|
|
print(f'Polybius: {POLYBIUS}, key: {KEY}')
|
|
print('Message: ', MESSAGE)
|
|
ENCODED = SECRET.encrypt(MESSAGE)
|
|
DECODED = SECRET.decrypt(ENCODED)
|
|
print('Encoded: ', ENCODED)
|
|
print('Decoded: ', DECODED)
|