From ab443e8ef2259290697bcb1b845861e7af742dc2 Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Thu, 12 Oct 2017 12:37:01 +0200 Subject: reference the python code --- README.md | 2 + ls47.py | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ programs/ls47.py | 159 ------------------------------------------------------- 3 files changed, 161 insertions(+), 159 deletions(-) create mode 100755 ls47.py delete mode 100755 programs/ls47.py diff --git a/README.md b/README.md index 6daa967..8c9f009 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ We suggest printing the model using more than one filament color to make the letters easily recognizable. Thanks go to Martin Ptasek for providing the picture. +If you trust your computer, there is now a python implementation in `ls47.py`. + ### Character board We have added some real punctuation, basic stuff for writing expressions, diff --git a/ls47.py b/ls47.py new file mode 100755 index 0000000..41a1405 --- /dev/null +++ b/ls47.py @@ -0,0 +1,159 @@ +#!/usr/bin/python2 +# -*- coding: utf-8 -*- + +import random + +letters = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()" +tiles = 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 = filter(lambda (l, pos): l == letter, tiles) + 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((ax, ay), (bx, by)): + return ((ax + bx) % 7, (ay + by) % 7) + + +def sub_pos((ax, ay), (bx, by)): + return ((ax - bx) % 7, (ay - by) % 7) + + +def find_at_pos(key, (row, col)): + return key[col + row * 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 = map(lambda i: key[i * 7:(i + 1) * 7], range(7)) + lefts = map(lambda l: l[:col], lines) + mids = map(lambda l: l[col], lines) + rights = map(lambda l: l[col + 1:], lines) + n = (7 - n % 7) % 7 + mids = mids[n:] + mids[:n] + return reduce(lambda a, b: a + b, map(lambda i: lefts[i] + mids[i] \ + + rights[i], range(7))) + + +def rotate_marker_right((mrow, mcol), row, n): + if mrow != row: + return (mrow, mcol) + else: + return (mrow, (mcol + n) % 7) + + +def rotate_marker_down((mrow, mcol), col, n): + if mcol != col: + return (mrow, mcol) + else: + return ((mrow + n) % 7, mcol) + + +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) + mp = rotate_marker_right(mp, pp[0], 1) + cp = find_pos(key, c) + key = rotate_down(key, cp[1], 1) + mp = rotate_marker_down(mp, 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) + mp = rotate_marker_right(mp, pp[0], 1) + cp = find_pos(key, c) + key = rotate_down(key, cp[1], 1) + mp = rotate_marker_down(mp, 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! + + key = derive_key('s3cret_p4ssw0rd/31337') + print 'key:', key + enc = encrypt_pad(key, 'conflagrate_the_rose_bush_at_six!', + 'peace-vector-3') + print 'encrypted:', enc + dec = decrypt_pad(key, enc) + print 'decrypted:', dec diff --git a/programs/ls47.py b/programs/ls47.py deleted file mode 100755 index 41a1405..0000000 --- a/programs/ls47.py +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/python2 -# -*- coding: utf-8 -*- - -import random - -letters = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()" -tiles = 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 = filter(lambda (l, pos): l == letter, tiles) - 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((ax, ay), (bx, by)): - return ((ax + bx) % 7, (ay + by) % 7) - - -def sub_pos((ax, ay), (bx, by)): - return ((ax - bx) % 7, (ay - by) % 7) - - -def find_at_pos(key, (row, col)): - return key[col + row * 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 = map(lambda i: key[i * 7:(i + 1) * 7], range(7)) - lefts = map(lambda l: l[:col], lines) - mids = map(lambda l: l[col], lines) - rights = map(lambda l: l[col + 1:], lines) - n = (7 - n % 7) % 7 - mids = mids[n:] + mids[:n] - return reduce(lambda a, b: a + b, map(lambda i: lefts[i] + mids[i] \ - + rights[i], range(7))) - - -def rotate_marker_right((mrow, mcol), row, n): - if mrow != row: - return (mrow, mcol) - else: - return (mrow, (mcol + n) % 7) - - -def rotate_marker_down((mrow, mcol), col, n): - if mcol != col: - return (mrow, mcol) - else: - return ((mrow + n) % 7, mcol) - - -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) - mp = rotate_marker_right(mp, pp[0], 1) - cp = find_pos(key, c) - key = rotate_down(key, cp[1], 1) - mp = rotate_marker_down(mp, 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) - mp = rotate_marker_right(mp, pp[0], 1) - cp = find_pos(key, c) - key = rotate_down(key, cp[1], 1) - mp = rotate_marker_down(mp, 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! - - key = derive_key('s3cret_p4ssw0rd/31337') - print 'key:', key - enc = encrypt_pad(key, 'conflagrate_the_rose_bush_at_six!', - 'peace-vector-3') - print 'encrypted:', enc - dec = decrypt_pad(key, enc) - print 'decrypted:', dec -- cgit v1.2.3