Elsie Four
March 27, 2018
It’s been a while since we did any cryptography. Alan Kaminsky developed an algorithm that he claims is suitable for hand operation but is also quite secure. You can read about the cipher and Kaminsky’s cryptanalysis of it at the link.
Your task is to implement Kaminsky’s Elsie Four cipher. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.
Here is a rather messy and verbose, but working, implementation in
standard Scheme. It tries to generalize to table-sizes other than
6×6, but I haven’t tested that part yet.
Here is a Forth implementation suitable for Gforth 0.7.3:
\ Elsie-Four (LC4), Copyright 2018 etb, License: GPLv3+
CREATE K 36 ALLOT \ Key buffer
CREATE S 36 ALLOT \ State buffer
CREATE M 0 , \ Marker
->INT ( c -- n)
DUP [CHAR] # = IF DROP 0 ELSE
DUP [CHAR] _ = IF DROP 1 ELSE
DUP [CHAR] 2 >= OVER [CHAR] 9 <= AND IF [ CHAR 2 2 - ]L - ELSE
DUP [CHAR] a >= OVER [CHAR] z <= AND IF [ CHAR a 10 - ]L - ELSE
DUP [CHAR] A >= OVER [CHAR] Z <= AND IF [ CHAR A 10 - ]L - ELSE
." Warning: mapping '" EMIT ." ' to '_'" CR 1
THEN THEN THEN THEN THEN ;
->CHAR ( n -- c) >R
S" #_23456789abcdefghijklmnopqrstuvwxyz" R@ <
IF [CHAR] * ELSE R@ + C@ THEN R> DROP ;
CREATE BUF 256 ALLOT CREATE B BUF ,
B0 BUF B ! ; : .B BUF B @ BUF - TYPE ;
B, B @ C! [ 1 CHARS ]L B +! ;
CREATE A 0 ,
C@A+ ( -- c) A @ C@ 1 CHARS A +! ;
A-C! ( c --) A @ 1 CHARS - TUCK C! A ! ;
RIGHT-ROTATE ( row --) 6 * S + A !
C@A+ C@A+ C@A+ C@A+ C@A+ C@A+ >R
A-C! A-C! A-C! A-C! A-C! R> A-C! ;
C@A6+ ( -- c) A @ C@ 6 CHARS A +! ;
A6-C! ( c --) A @ 6 CHARS - TUCK C! A ! ;
DOWN-ROTATE ( col --) S + A !
C@A6+ C@A6+ C@A6+ C@A6+ C@A6+ C@A6+ >R
A6-C! A6-C! A6-C! A6-C! A6-C! R> A6-C! ;
S[] ( n -- c) S + C@ ;
SFIND ( c -- n) >R
0 BEGIN DUP S[] R@ <> WHILE CHAR+ REPEAT R> DROP ;
+S ( n n' -- n'') \ Add indices within the state matrix
6 /MOD ROT 6 /MOD ROT
+ 6 MOD 6 * >R + 6 MOD R> + ;
-S ( n n' -- n'') \ Subtract indices within the state matrix
6 /MOD ROT 6 /MOD ROT
- 6 MOD 6 * >R SWAP - 6 MOD R> + ;
\ Rather than maintain row and column indices for various markers,
\ just keep track of the character, and search, via SFIND, for the
\ index in S when needed.
UPDATE ( C P --)
SFIND 6 / RIGHT-ROTATE \ rotate row of P
DUP SFIND 6 MOD DOWN-ROTATE \ rotate column of C
M @ SFIND +S S[] M ! ; \ adjust marker
CIPHER ( c --) ->INT
DUP SFIND M @ ( P P' M)
+S ( P C') S[] TUCK ( C P C)
->CHAR B, UPDATE ;
PLAIN ( c --) ->INT
DUP SFIND M @ ( C C' M)
-S ( C P') S[] DUP ( C P P)
->CHAR B, UPDATE ;
(ENCRYPT) ( c-addr u) BOUNDS ?DO I C@ CIPHER LOOP ;
(DECRYPT) ( c-addr u) BOUNDS ?DO I C@ PLAIN LOOP ;
RESET K S 36 CMOVE 0 S[] M ! ; \ Reset the state matrix and marker
ENCRYPT ( nonce u1 header u2 plaintext u3 sig u4)
RESET
2>R 2>R 2SWAP \ save plaintext/sig for later, setup nonce
(ENCRYPT) B0 \ encrypt the nonce and ignore
(ENCRYPT) B0 \ encrypt header, if any, and ignore
2R> 2R> 2SWAP \ restore sig/plaintext
(ENCRYPT) \ encrypt the plaintext
(ENCRYPT) \ append the encrypted sig
CR ." Ciphertext: " .B CR ;
DECRYPT ( nonce u1 header u2 ciphertext u3)
RESET
2SWAP 2ROT \ save ciphertext for later, setup nonce
(ENCRYPT) B0 \ encrypt the nonce and ignore
(ENCRYPT) B0 \ encrypt header, if any, and ignore
(DECRYPT) \ decrypt the ciphertext
CR ." Plaintext: " .B CR ;
S. S A ! 6 0 DO 6 0 DO C@A+ ->CHAR EMIT LOOP SPACE LOOP ;
M. SPACE M @ SFIND 6 /MOD . SPACE . SPACE ;
TRACE ( c-addr u) CR RESET
." State" 38 SPACES ." i j pt ct" CR
S. M. CR BOUNDS ?DO
I C@ CIPHER
S. M. I C@ EMIT 2 SPACES B @ 1 CHARS - C@ EMIT CR
LOOP ;
\ Convenience syntax
SEND
BL PARSE \ nonce
BL PARSE \ header
BL PARSE \ plaintext
BL PARSE \ sig
ENCRYPT ;
RECEIVE
BL PARSE \ nonce
BL PARSE \ header
BL PARSE \ ciphertext
DECRYPT ;
PRIV-KEY ( " ccc" --)
BL WORD COUNT
36 <> IF DROP ." ERROR: Key must have length 36" CR QUIT THEN
A ! 36 0 DO C@A+ ->INT K I + C! LOOP ; IMMEDIATE
An example of use:
$ gforth elsie-four.fth
PRIV-KEY XV7YDQ#OPAJ_39RZUT8B45WCSGEHMIKNF26L
s" solwbf" s" " s" im_about_to_put_the_hammer_down" s" #rubberduck" ENCRYPT
Ciphertext: i2zqpilr2yqgptltrzx2_9fzlmbo3y8_9pyssx8nf2
ok
s" solwbf" s" " s" i2zqpilr2yqgptltrzx2_9fzlmbo3y8_9pyssx8nf2" DECRYPT
Plaintext: im_about_to_put_the_hammer_down#rubberduck
ok
SEND nonce header this_is_an_amazing_message #theartist
Ciphertext: wn9pfizu9c3cjc8ul3bkwazx#xhcm8ubj_2p8d
ok
RECEIVE nonce header wn9pfizu9c3cjc8ul3bkwazx#xhcm8ubj_2p8d
Plaintext: this_is_an_amazing_message#theartist
ok
And of course the formatting comes out less than desirable. So here’s the same code in a git repo: https://notabug.org/bavier/elsie-four/src/master/elsie-four.fth
At https://github.com/exaexa/ls47 you can find according Python 3 code (LS47.py) with many command line options.
They allow to switch between LC4 and its enhancement LS47, and to use different paddings.
Here’s a solution in Python using NumPy.
The program omits unused calculations from the paper’s right-rotation and down-rotation subroutines.
Output: