165 lines
4 KiB
Python
Executable file
165 lines
4 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# This software is hereby released into public domain. Use it wisely.
|
|
#
|
|
# Originally written by Mirek Kratochvil (2017)
|
|
# Python3 port by Bernhard Esslinger (Feb 2018)
|
|
|
|
import random
|
|
|
|
letters = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()"
|
|
tiles = list(zip(letters, map(lambda x: (x // 7, x % 7), range(7 * 7))))
|
|
padding_size = 10
|
|
|
|
|
|
def check_key(key):
|
|
if len(key) != len(letters):
|
|
raise ValueError('Wrong key size')
|
|
cnts = {}
|
|
for c in letters:
|
|
cnts[c] = 0
|
|
for c in key:
|
|
if not c in cnts:
|
|
raise ValueError('Letter ' + c + ' not in LS47!')
|
|
cnts[c] += 1
|
|
if cnts[c] > 1:
|
|
raise ValueError('Letter ' + c + ' duplicated in key!')
|
|
|
|
|
|
def find_ix(letter):
|
|
m = [l for l in tiles if l[0] == letter]
|
|
if len(m) != 1:
|
|
raise ValueError('Letter ' + letter + ' not in LS47!')
|
|
for (l, pos) in m:
|
|
return pos
|
|
|
|
|
|
def find_pos(key, letter):
|
|
p = key.find(letter)
|
|
if p >= 0 and p < 7 * 7:
|
|
return (p // 7, p % 7)
|
|
raise ValueError('Letter ' + letter + ' not in key?!')
|
|
|
|
|
|
def add_pos(a, b):
|
|
return ((a[0] + b[0]) % 7, (a[1] + b[1]) % 7)
|
|
|
|
|
|
def sub_pos(a, b):
|
|
return ((a[0] - b[0]) % 7, (a[1] - b[1]) % 7)
|
|
|
|
|
|
def find_at_pos(key, coord):
|
|
return key[coord[1] + coord[0] * 7]
|
|
|
|
|
|
def rotate_right(key, row, n):
|
|
mid = key[7 * row:7 * (row + 1)]
|
|
n = (7 - n % 7) % 7
|
|
return key[:7 * row] + mid[n:] + mid[:n] + key[7 * (row + 1):]
|
|
|
|
|
|
def rotate_down(key, col, n):
|
|
lines = [key[i * 7:(i + 1) * 7] for i in range(7)]
|
|
lefts = [l[:col] for l in lines]
|
|
mids = [l[col] for l in lines]
|
|
rights = [l[col + 1:] for l in lines]
|
|
n = (7 - n % 7) % 7
|
|
mids = mids[n:] + mids[:n]
|
|
return ''.join(lefts[i] + mids[i] + rights[i] for i in range(7))
|
|
|
|
|
|
def rotate_marker_right(m, row, n):
|
|
if m[0] != row:
|
|
return (m[0], m[1])
|
|
else:
|
|
return (m[0], (m[1] + n) % 7)
|
|
|
|
|
|
def rotate_marker_down(m, col, n):
|
|
if m[1] != col:
|
|
return (m[0], m[1])
|
|
else:
|
|
return ((m[0] + n) % 7, m[1])
|
|
|
|
|
|
def derive_key(password):
|
|
i = 0
|
|
k = letters
|
|
for c in password:
|
|
(row, col) = find_ix(c)
|
|
k = rotate_down(rotate_right(k, i, col), i, row)
|
|
i = (i + 1) % 7
|
|
return k
|
|
|
|
|
|
def encrypt(key, plaintext):
|
|
check_key(key)
|
|
mp = (0, 0)
|
|
ciphertext = ''
|
|
for p in plaintext:
|
|
pp = find_pos(key, p)
|
|
mix = find_ix(find_at_pos(key, mp))
|
|
cp = add_pos(pp, mix)
|
|
c = find_at_pos(key, cp)
|
|
ciphertext += c
|
|
|
|
key = rotate_right(key, pp[0], 1)
|
|
cp = find_pos(key, c)
|
|
key = rotate_down(key, cp[1], 1)
|
|
mp = add_pos(mp, find_ix(c))
|
|
return ciphertext
|
|
|
|
|
|
def decrypt(key, ciphertext):
|
|
check_key(key)
|
|
mp = (0, 0)
|
|
plaintext = ''
|
|
for c in ciphertext:
|
|
cp = find_pos(key, c)
|
|
mix = find_ix(find_at_pos(key, mp))
|
|
pp = sub_pos(cp, mix)
|
|
p = find_at_pos(key, pp)
|
|
plaintext += p
|
|
|
|
key = rotate_right(key, pp[0], 1)
|
|
cp = find_pos(key, c)
|
|
key = rotate_down(key, cp[1], 1)
|
|
mp = add_pos(mp, find_ix(c))
|
|
return plaintext
|
|
|
|
|
|
def encrypt_pad(key, plaintext, signature):
|
|
|
|
# TODO it would also be great to randomize the message length.
|
|
|
|
check_key(key)
|
|
padding = ''.join(map(lambda x: letters[random.randint(0,
|
|
len(letters) - 1)], range(padding_size)))
|
|
|
|
return encrypt(key, padding + plaintext + '---' + signature)
|
|
|
|
|
|
def decrypt_pad(key, ciphertext):
|
|
check_key(key)
|
|
return decrypt(key, ciphertext)[padding_size:]
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
# a bit of test!
|
|
|
|
print('letters in this implementation (line by line):')
|
|
print(letters)
|
|
# print('tiles positions: ' + str(tiles))
|
|
|
|
key = derive_key('s3cret_p4ssw0rd/31337')
|
|
print('test key: ' + key)
|
|
enc = encrypt_pad(key, 'conflagrate_the_rose_bush_at_six!',
|
|
'peace-vector-3')
|
|
|
|
print('encrypted test: ' + enc)
|
|
dec = decrypt_pad(key, enc)
|
|
print('decrypted test: ' + dec)
|