aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--[-rwxr-xr-x]lc4.py158
1 files changed, 108 insertions, 50 deletions
diff --git a/lc4.py b/lc4.py
index 10285c6..d126265 100755..100644
--- a/lc4.py
+++ b/lc4.py
@@ -1,27 +1,47 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
# This software is hereby released into public domain. Use it wisely.
-#
+#
# ElsieFour (LC4) and its enhancement LS47
-# version 2.7: 2018-02-13
+# version 2.8: 2018-05-30
+#
+# Original paper by Alan Kaminsky: https://eprint.iacr.org/2017/339.pdf
+# Original LC4 Java source: https://www.cs.rit.edu/~ark/parallelcrypto/elsiefour/
#
-# - Python script for LS47 originally written by Mirek Kratochvil (2017)
+# - Python 2 script for LS47 originally written by Mirek Kratochvil (2017)
# See https://github.com/exaexa/ls47
# - Python 3 port by Bernhard Esslinger / AK (Feb 2018)
# See www.mysterytwisterc3.org
# - New options by CrypTool project (www.cryptool.org) (Feb 2018) in order to
-# support both ciphers LC4 and LS47, both ways to deal with nonces, keyword
-# usage for both LC4 and LS47, reading from file and commandline, and
-# extended test outputs.
-#
+# support both ciphers LC4 and LS47 in one program, to support both ways to
+# deal with nonces and keywords, to support reading from file and commandline,
+# and to extend test outputs (v2.7).
+# - v2.8 extended the testcase and added the fix of the fixpoint-ish problem for ls47 (option -m1):
+# python lc4-ls47.py -6 -ws thisismysecretkey -es ############## -ns igxf5e -m1
+# igxf5egcyhoo#ny#o5i5
+# python lc4-ls47.py -6 -ws thisismysecretkey -es ############## -ns igxf5e -m0
+# igxf5e##############
+#
# Sample calls:
-# - Using cipher LC4, enforced with option -6:
+#
+# Using cipher LC4, enforced with option -6:
+# // encrypt (key, message). Following example returns tk5j23tq94_gw9c#lhzs
+# python lc4-ls47.py -6 -ks s2ferw_nx346ty5odiupq#lmz8ajhgcvk79b -es aaaaaaaaaaaaaaaaaaaa
+# // decrypt (key, message). Following example returns aaaaaaaaaaaaaaaaaaaa
+# python lc4-ls47.py -6 -ks s2ferw_nx346ty5odiupq#lmz8ajhgcvk79b -ds tk5j23tq94_gw9c#lhzs
+# python lc4-ls47.py -6 -ws thisismysecretkey -es its_my_fathers_son_but_not_my_brother
+# python lc4-ls47.py -6 -ws thisismysecretkey -ds 6dudfy3u7omoxy4jbscgn37c2se_d8gx6ogk9
# python lc4-ls47.py -6 -ws thisismysecretkey -es its_my_fathers_son_but_not_my_brother -v -nl 6
# python lc4-ls47.py -6 -ws thisismysecretkey -ds q6xojffkncfyz#f5czs49#3mbsco#2iscvbnm#bymaf -v -nl 6
-# - Using cipher LS47, enforced with option -7:
-# python lc4-ls47.py -7 -ws s3cret_p4ssw0rd/31337 -ds y'zbvvs+d2,ky4sy?w(_wkz*7'90v:./s)kcz?mj+gyu8-'h(y,i+v,z+1ws -v -nl 10
-# python lc4-ls47.py -7 -ws s3cret_p4ssw0rd/31337 -ds y'zbvvs+d2,ky4sy?w(_wkz*7'90v:./s)kcz?mj+gyu8-'h(y,i+v,z+1ws -v -ns 8y(l._4ct'
#
+# Using cipher LS47, enforced with option -7:
+# python lc4-ls47.py -7 -ws s3cret_p4ssw0rd/31337 -es conflagrate_the_rose_bush_at_six!---peace-vector-3 -v -ns 8y(l._4ct' -m0
+# python lc4-ls47.py -7 -ws s3cret_p4ssw0rd/31337 -ds y'zbvvs+d2,ky4sy?w(_wkz*7'90v:./s)kcz?mj+gyu8-'h(y,i+v,z+1ws -v -nl 10 -m0
+# python lc4-ls47.py -7 -ws s3cret_p4ssw0rd/31337 -ds y'zbvvs+d2,ky4sy?w(_wkz*7'90v:./s)kcz?mj+gyu8-'h(y,i+v,z+1ws -v -ns 8y(l._4ct' -m0
+#
+# Using the automatic test (option -t can be combined with options -s and -ws)
+# python lc4-ls47.py -t
+# python lc4-ls47.py -t -ws s3cret_p4sswxyz -s peacevector_34
from __future__ import print_function
@@ -31,7 +51,7 @@ import random
import argparse
-version = "v2.7 (2018-02-13)"
+version = "v2.8 (2018-05-30)"
letters6 = "#_23456789abcdefghijklmnopqrstuvwxyz"
letters7 = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()"
@@ -160,7 +180,7 @@ def encrypt(plaintext):
def decrypt(ciphertext):
global key, mp
-
+
plaintext = ''
for c in ciphertext:
cp = find_pos(key, c)
@@ -212,17 +232,19 @@ def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
-def printinfo(enc=False):
+def printinfo(enc=False): # used by test1() and when option -v
eprint('CIPHER : ' + ("LC4" if size==6 else "LS47"))
eprint('ALPHABET : ' + letters)
- if args.keywordstring:
- eprint('KEYWORD : ' + args.keywordstring)
+ if szkeyword:
+ eprint('KEYWORD : ' + szkeyword)
eprint('KEY : ' + initialkey) # key variable modified during process?
eprint('NONCE : ' + nonce)
eprint('NONCE ENC : ' + nonce_enc)
eprint('NONCEMODE : ' + ("Kaminsky" if nonce_mode==1 else "Kratochvil"))
eprint('MARKER : ' + ("Kaminsky" if marker_mode==1 else "Kratochvil"))
if enc:
+ # if args.signature: print ("args.SIGNA:", args.signature)
+ if szsignature: print ("SIGNATURE: ", szsignature)
eprint('PLAINTEXT : ' + plaintext)
eprint('CIPHERTEXT: ' + ciphertext)
else:
@@ -230,24 +252,32 @@ def printinfo(enc=False):
eprint('PLAINTEXT : ' + plaintext)
-def test1(size):
- global letters, nonce_mode, marker_mode, tiles, noncelen, nonce, nonce_size, nonce_enc, signature
+def test1(size, fixednonce):
+ # Declaration "global" only needed if you like to write to a global variable
+ # [here, this is necessary only if the global var. is used within a fct. called by test1()]
+ # As test1() is called several times don't change args components within this fct.
+ global letters, nonce_mode, marker_mode, tiles, noncelen, nonce, nonce_size, nonce_enc
global ciphertext, plaintext, key, initialkey, mp
+ global szkeyword, szsignature
if size == 7:
CIPHERNAME = "LS47"
- letters = letters7;
+ letters = letters7
nonce_mode = 2
marker_mode = 2
- noncelen=10; nonce = create_random_nonce(noncelen)
- # nonce = 'dr0+:_pij2' # just a sample for testing fixed nonce
+ if fixednonce == 0:
+ noncelen=10; nonce = create_random_nonce(noncelen)
+ else:
+ nonce = 'dr0+:_pij2' # just a sample for testing fixed nonce
else:
CIPHERNAME = "LC4"
- letters = letters6;
+ letters = letters6
nonce_mode = 1
marker_mode = 1
- noncelen=6; nonce = create_random_nonce(noncelen)
- # nonce = 'pjpm5i' # just a sample for testing fixed nonce
+ if fixednonce == 0:
+ noncelen=6; nonce = create_random_nonce(noncelen)
+ else:
+ nonce = 'pjpm5i' # just a sample for testing fixed nonce
tiles = list(zip(letters, [(x // size, x % size) for x in range(size * size)]))
check_nonce(nonce)
@@ -257,7 +287,11 @@ def test1(size):
print('\n' + CIPHERNAME)
if size == 7:
- keyword = 's3cret_p4ssw0rd/31337'; args.keywordstring=keyword
+ if args.keywordstring:
+ keyword = args.keywordstring # Allows to use -ws besides -t
+ else:
+ keyword = 's3cret_p4ssw0rd/31337'
+ szkeyword = keyword # This statement needed to show keyword in printinfo() [don't change args.keywordstring within test1()!]
key = derive_key(keyword)
else:
key = letters
@@ -266,11 +300,13 @@ def test1(size):
mp = (0, 0)
if size == 7:
+ # signature = 'peace-vector-3' # just to name a meaningful value for size=7
plaintext = 'conflagrate_the_rose_bush_at_six!'
- signature = 'peace-vector-3'
+ if szsignature: plaintext += szsignature
else:
+ # signature = '#its_me' # Notice: with size=6 the chars "#", "0", "1", "-" aren't part of the alphabet
plaintext = 'its_my_fathers_son_but_not_my_brother'
- signature = '#its_me' # signature = ''
+ if szsignature: plaintext += szsignature
check_plaintext(plaintext)
ciphertext = encrypt_with_nonce(plaintext)
@@ -280,20 +316,21 @@ def test1(size):
decryptedtext = decrypt_with_nonce(ciphertext)
print('decrypted text: ' + decryptedtext)
- printinfo(True)
- args.keywordstring=''
+ printinfo(True) # Called from test1() with arg TRUE = assume to do decryption
+
+ szkeyword=''
if __name__ == '__main__':
- parser = argparse.ArgumentParser()
+ parser = argparse.ArgumentParser() # how to enforce, that (either no arg), (or only one arg -t, -v), (or give key and enc mode)
- mgroup = parser.add_mutually_exclusive_group()
parser.add_argument("-v", "--verbose", help="output additional information on stderr", action="count", default=0)
- mgroup.add_argument("-6", "--lc4", help="use ElsieFour cipher (6x6 table) (default)", action="store_true")
- mgroup.add_argument("-7", "--ls47", help="use LS47 cipher (7x7 table)", action="store_true")
+ mgroup1 = parser.add_mutually_exclusive_group()
+ mgroup1.add_argument("-6", "--lc4", help="use ElsieFour cipher (6x6 table) (default)", action="store_true")
+ mgroup1.add_argument("-7", "--ls47", help="use LS47 cipher (7x7 table)", action="store_true")
mgroup2 = parser.add_mutually_exclusive_group()
mgroup2.add_argument("-ks", "--keystring", metavar="STRING", help="use STRING as key")
@@ -301,16 +338,16 @@ if __name__ == '__main__':
mgroup2.add_argument("-ws", "--keywordstring", metavar="STRING", help="generate key from keyword STRING", default=None)
mgroup2.add_argument("-wf", "--keywordfile", metavar="FILE", help="read keyword from FILE to generate key", default=None)
- mgroup3 = parser.add_mutually_exclusive_group()
- mgroup3.add_argument("-nl", "--noncelen", metavar="LENGTH", help="use random nonce of length LENGTH", type=int, default=0)
- mgroup3.add_argument("-ns", "--noncestring", metavar="STRING", help="use STRING as nonce")
-
mgroup4 = parser.add_mutually_exclusive_group()
mgroup4.add_argument("-es", "--encryptstring", metavar="STRING", help="encrypt STRING")
mgroup4.add_argument("-ef", "--encryptfile", metavar="FILE", help="read plaintext from FILE and encrypt it")
mgroup4.add_argument("-ds", "--decryptstring", metavar="STRING", help="decrypt STRING")
mgroup4.add_argument("-df", "--decryptfile", metavar="FILE", help="read ciphertext from FILE and decrypt it")
- mgroup4.add_argument("-t", "--test", help="encrypt and decrypt a string with a random nonce and a given key (once with LC4 and once with LS47)", action="store_true")
+ mgroup4.add_argument("-t", "--test", help="encrypt and decrypt a string with a given key (four cases: random and fixed nonce, LC4 and LS47)", action="store_true")
+
+ mgroup3 = parser.add_mutually_exclusive_group()
+ mgroup3.add_argument("-nl", "--noncelen", metavar="LENGTH", help="use random nonce of length LENGTH (default: no nonce)", type=int, default=0)
+ mgroup3.add_argument("-ns", "--noncestring", metavar="STRING", help="use STRING as nonce (default: no nonce)")
mgroup5 = parser.add_mutually_exclusive_group()
mgroup5.add_argument("-n0", "--nKaminsky", help="use nonce in Kaminsky mode (default for LC4)", action="store_true")
@@ -320,7 +357,8 @@ if __name__ == '__main__':
mgroup6.add_argument("-m0", "--mKaminsky", help="use marker in Kaminsky mode (default for LC4)", action="store_true")
mgroup6.add_argument("-m1", "--mKratochvil", help="use marker in Kratochvil mode (default for LS47)", action="store_true")
- parser.add_argument("-s", "--signature", help="append SIGNATURE to plaintext when encrypting")
+
+ parser.add_argument("-s", "--signature", help="append SIGNATURE to plaintext when encrypting (default: no signature)")
args = parser.parse_args()
@@ -372,8 +410,19 @@ if __name__ == '__main__':
key = letters
+ # help variables for verbose printing via printinfo()
+
+ szkeyword = '' # global variable which can be changed in test1() [so it can be used as test condition in printinfo() instead of args.keywordstring and instead of test1() manipulating args.keywordstring]
+ szsignature = '' # global variable which can be changed in test1() [so it can be used as test condition in printinfo() instead of args.keywordstring and instead of test1() manipulating args.keywordstring]. # Allows to use -s besides -t
+
+ if args.signature: # This case here is only necessary if -s should be used together with -t
+ szsignature = args.signature
+
+
if args.keywordfile: args.keywordstring = open(args.keywordfile, 'r').read().rstrip('\r\n')
- if args.keywordstring: key = derive_key(args.keywordstring);
+ if args.keywordstring:
+ szkeyword = args.keywordstring
+ key = derive_key(args.keywordstring)
if args.keyfile: args.keystring = open(args.keyfile, 'r').read().rstrip('\r\n')
if args.keystring: key = args.keystring;
@@ -393,29 +442,38 @@ if __name__ == '__main__':
if args.encryptstring:
plaintext = args.encryptstring
- if args.signature: plaintext += args.signature
+ if args.signature:
+ plaintext += args.signature
check_plaintext(plaintext)
ciphertext = encrypt_with_nonce(plaintext)
- if args.verbose: printinfo(True)
- print(ciphertext)
+ if args.verbose:
+ printinfo(True)
+ else:
+ print(ciphertext)
elif args.decryptstring:
ciphertext = args.decryptstring
check_ciphertext(ciphertext)
plaintext = decrypt_with_nonce(ciphertext)
- if args.verbose: printinfo()
if args.signature and args.verbose:
- eprint("Warning: the given signature is ignored during decryption")
- print(plaintext)
+ eprint("--Warning--: the given signature is ignored during decryption")
+ # Maybe later return result of a check whether given signature equals the end of the decrypted string.
+ if args.verbose:
+ printinfo()
+ else:
+ print(plaintext)
elif args.test:
print('\nVersion: ' + version)
- print('Test: No nonce provided (only its length), so each call will produce a different ciphertext.')
- size = 7; test1(size)
- size = 6; test1(size)
+ print('\n---Test: No nonce provided (only its length), so each call will produce a different ciphertext.')
+ size = 7; test1(size, 0)
+ size = 6; test1(size, 0)
+ print('\n---Test: Nonce provided as string.')
+ size = 7; test1(size, 1)
+ size = 6; test1(size, 1)
sys.exit(1)
- else:
+ else: # you'll never get here (handled via argparse)
# if args.verbose: print('Version : ' + version)
print('ALPHABET : ' + letters)
print('KEY : ' + initialkey)