Elsie Four
March 27, 2018
My solution doesn’t work; it encrypts properly, according to the example in Kaminsky’s appendix, but doesn’t decrypt, and I don’t know why. Here is my code, which ignores the header, just as Kaminsky does:
; elsie four
(define (drop n xs)
(let loop ((n n) (xs xs))
(if (or (zero? n) (null? xs)) xs
(loop (- n 1) (cdr xs)))))
(define (make-matrix rows columns . value)
(do ((m (make-vector rows)) (i 0 (+ i 1)))
((= i rows) m)
(if (null? value)
(vector-set! m i (make-vector columns))
(vector-set! m i (make-vector columns (car value))))))
(define (matrix-ref m i j) (vector-ref (vector-ref m i) j))
(define (matrix-set! m i j x) (vector-set! (vector-ref m i) j x))
(define (string->ints str)
(define (c->i c)
(cond ((char=? c #\#) 0) ((char=? c #\_) 1)
((char<=? #\2 c #\9) (- (char->integer c) (char->integer #\0)))
((char<=? #\a c #\z) (- (char->integer c) (char->integer #\a) -10))
((char<=? #\A c #\Z) (- (char->integer c) (char->integer #\A) -10))
(else (error 'string->ints "unrecognized character"))))
(map c->i (string->list str)))
(define (ints->string ints)
(define (i->c i)
(cond ((or (< i 0) (< 35 i)) (error 'ints->string "unrecognized character"))
((= i 0) #\#) ((= i 1) #\_)
((< i 10) (integer->char (+ (char->integer #\0) i)))
(else (integer->char (+ (char->integer #\A) i -10)))))
(list->string (map i->c ints)))
(define (initialize-state key)
(let ((xs (string->ints key)) (state (make-matrix 6 6)))
(do ((k 0 (+ k 1)) (xs xs (cdr xs))) ((null? xs) state)
(matrix-set! state (quotient k 6) (modulo k 6) (car xs)))))
(define (find s x)
(let loop ((k 0))
(let ((r (quotient k 6)) (c (modulo k 6)))
(if (= (matrix-ref s r c) x) (values r c)
(loop (+ k 1))))))
(define (advance-state state i j r c x y ct)
(let ((t (matrix-ref state r 5)))
(matrix-set! state r 5 (matrix-ref state r 4))
(matrix-set! state r 4 (matrix-ref state r 3))
(matrix-set! state r 3 (matrix-ref state r 2))
(matrix-set! state r 2 (matrix-ref state r 1))
(matrix-set! state r 1 (matrix-ref state r 0))
(matrix-set! state r 0 t))
(set! c (modulo (+ c 1) 6))
(when (= x r) (set! y (modulo (+ y 1) 6)))
(when (= i r) (set! j (modulo (+ j 1) 6)))
(let ((t (matrix-ref state 5 y)))
(matrix-set! state 5 y (matrix-ref state 4 y))
(matrix-set! state 4 y (matrix-ref state 3 y))
(matrix-set! state 3 y (matrix-ref state 2 y))
(matrix-set! state 2 y (matrix-ref state 1 y))
(matrix-set! state 1 y (matrix-ref state 0 y))
(matrix-set! state 0 y t))
(set! x (modulo (+ x 1) 6))
(when (= c y) (set! r (modulo (+ r 1) 6)))
(when (= j y) (set! i (modulo (+ i 1) 6)))
(set! i (modulo (+ i (quotient ct 6)) 6))
(set! j (modulo (+ j (modulo ct 6)) 6))
(values state i j r c x y))
(define (encrypt key nonce plaintext signature)
(let ((state (initialize-state key)) (i 0) (j 0))
(let loop ((ps (string->ints (string-append nonce plaintext signature)))
(state state) (i i) (j j) (cs (list)))
(if (null? ps)
(string-upcase (string-append nonce
(ints->string (drop (string-length nonce) (reverse cs)))))
(call-with-values
(lambda () (find state (car ps)))
(lambda (r c)
(let* ((x (modulo (+ r (quotient (matrix-ref state i j) 6)) 6))
(y (modulo (+ c (modulo (matrix-ref state i j) 6)) 6))
(ct (matrix-ref state x y)))
(call-with-values
(lambda () (advance-state state i j r c x y ct))
(lambda (state i j r c x y)
(loop (cdr ps) state i j (cons ct cs)))))))))))
(define (decrypt key nonce ciphertext)
(let ((state (initialize-state key)) (i 0) (j 0))
; encrypt nonce (to set initial state for decryption)
(let loop ((ps (string->ints nonce)) (state state) (i i) (j j))
(when (pair? ps)
(call-with-values
(lambda () (find state (car ps)))
(lambda (r c)
(let* ((x (modulo (+ r (quotient (matrix-ref state i j) 6)) 6))
(y (modulo (+ c (modulo (matrix-ref state i j) 6)) 6))
(ct (matrix-ref state x y)))
(call-with-values
(lambda () (advance-state state i j r c x y ct))
(lambda (state i j r c x y)
(loop (cdr ps) state i j))))))))
; decrypt message text and signature
(let loop ((cs (string->ints ciphertext)) (state state) (i i) (j j) (ps (list)))
(if (null? cs)
(string-upcase (ints->string (reverse ps)))
(call-with-values
(lambda () (find state (car cs)))
(lambda (x y)
(let* ((r (modulo (- x (quotient (matrix-ref state i j) 6)) 6))
(c (modulo (- y (modulo (matrix-ref state i j) 6)) 6))
(pt (matrix-ref state r c)))
(call-with-values
(lambda () (advance-state state i j r c x y (car cs)))
(lambda (state i j r c x y)
(loop (cdr cs) state i j (cons pt ps)))))))))))
(define key "XV7YDQ#OPAJ_39RZUT8B45WCSGEHMIKNF26L")
(define nonce "SOLWBF")
(define plaintext "IM_ABOUT_TO_PUT_THE_HAMMER_DOWN")
(define signature "#RUBBERDUCK")
> (encrypt key nonce plaintext signature)
"SOLWBFI2ZQPILR2YQGPTLTRZX2_9FZLMBO3Y8_9PYSSX8NF2"
> (decrypt key "SOLWBF" "I2ZQPILR2YQGPTLTRZX2_9FZLMBO3Y8_9PYSSX8NF2")
"ZCIO5NFFSK8GHJ4LR3F8QNNVSK88VJH3JIEKIUQ8_6"
I’ve checked the decryption code several times, carefully, and don’t see a problem. I know that encrypt and advance-state work properly, and advance-state is the same for both encryption and decryption, so the problem is in the decrypt function, but I don’t see the problem. I’ll keep working on it; maybe one of you will spot the problem first. You can run the code at https://ideone.com/DEfZJ8.
By the way, I can never make these “hand-operable” ciphers work by hand. I don’t know if I lack concentration, or get too easily distracted, but my attempts to use manual ciphers always fail. For Elsie Four, I tried to encrypt the sample message three times, and my best result was to get the first fourteen characters right (and six of those were the nonce).
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.
;;;;; ElsieFour (LC4) implementation ;; 2018-03-30 Sudarshan S Chawathe ;;; Dependencies (import (scheme base) (scheme write) (only (srfi 1) iota) (srfi 8) (srfi 25) (srfi 69)) ;;; Setup (define lc4-alphabet "#_23456789abcdefghijklmnopqrstuvwxyz") ;;; Alphabet-number mappings (define lc4-char->number (let ((ht (alist->hash-table (map cons (string->list lc4-alphabet) (iota (string-length lc4-alphabet))) char=?))) (lambda (c) (hash-table-ref ht c)))) (define lc4-number->char (let ((ht (alist->hash-table (map cons (iota (string-length lc4-alphabet)) (string->list lc4-alphabet)) =))) (lambda (n) (hash-table-ref ht n)))) ;;; Array utilities ;; 1-dimensional array with elements drawn from given vector. (define (vector->array vec) (let ((arr (make-array (shape 0 (vector-length vec))))) (let f ((i 0)) (when (< i (vector-length vec)) (array-set! arr i (vector-ref vec i)) (f (+ i 1)))) arr)) (define (array-2d->inline-string arr element->string) (let ((oport (open-output-string))) (let do-rows ((row (array-start arr 0))) (when (< row (array-end arr 0)) (let do-cols ((col (array-start arr 1))) (when (< col (array-end arr 1)) (display (element->string (array-ref arr row col)) oport) (do-cols (+ col 1)))) (display " " oport) (do-rows (+ row 1)))) (get-output-string oport))) ;;; Array row- and column-rotations ;; Consider 2D-array as a table (dimensions 0, 1 correspond to rows, ;; cols) and circular-rotate-right the given row by 1 (in-place). (define (array-2d-right-rotate-row! arr row) (let* ((ncols (array-end arr 1)) (tval (array-ref arr row (- ncols 1)))) (for-each (lambda (col) (array-set! arr row (+ col 1) (array-ref arr row col))) (iota (- ncols 1) (- ncols 2) -1)) (array-set! arr row 0 tval) arr)) ;; Like array-2d-right-rotate-row! but circular-down-rotate the given ;; column. (define (array-2d-down-rotate-column! arr col) (let* ((nrows (array-end arr 0)) (tval (array-ref arr (- nrows 1) col))) (for-each (lambda (row) (array-set! arr (+ row 1) col (array-ref arr row col))) (iota (- nrows 1) (- nrows 2) -1)) (array-set! arr 0 col tval) arr)) ;;; Record type for LC4 state (define-record-type lc4s (make-lc4s i j s s1 x y r c) lc4s? (i lci lci!) ; marker's row (j lcj lcj!) ; marker's column (s lcs lcs!) ; 2-d array of letters/tiles (s1 lcs1 lcs1!) ; 1-d view of above (shared storage) ;; x, y, r, c below are not technically part of the state (rather, ;; working variables) but are included here for convenience. (x lcx lcx!) (y lcy lcy!) (r lcr lcr!) (c lcc lcc!)) ;; String representation of state used by example in ElsieFour paper [Kam17]. (define (lc4s->string s) (string-append (array-2d->inline-string (lcs s) (lambda (n) (string (lc4-number->char n)))) " " (number->string (lci s)) " " (number->string (lcj s)))) ;; LC4 state initialized from key. (define (lc4-make-state key) (let ((arr1d (vector->array key))) (make-lc4s 0 0 (let ((nletters (vector-length key))) (receive (tlen rem) (exact-integer-sqrt nletters) (unless (zero? rem) (error "Key size is not a perfect square")) (share-array arr1d (shape 0 tlen 0 tlen) (lambda (row col) (+ (* tlen row) col))))) arr1d 0 0 0 0))) (define (lc4s-nrows s) (- (array-end (lcs s) 0) (array-start (lcs s) 0))) (define (lc4s-ncols s) (- (array-end (lcs s) 1) (array-start (lcs s) 1))) ;;; LC4 state operations ;; Circular-rotate-right the given row of the state table, moving the ;; marker if it is in the affected row. ;; Input x is the x-coordinate of the encrypted-char tile. ;; Outputs an increment to the y coordinate of the encrypted-char tile. (define (lc4-right-rotate-row! s) (array-2d-right-rotate-row! (lcs s) (lcr s)) (let ((n (lc4s-ncols s))) (lcc! s (modulo (+ (lcc s) 1) n)) (when (= (lcr s) (lcx s)) (lcy! s (modulo (+ (lcy s) 1) n))) (when (= (lcr s) (lci s)) (lcj! s (modulo (+ (lcj s) 1) n)))) s) ;; Like lc4-right-rotate-row!, but for columns. (define (lc4-down-rotate-column! s) (array-2d-down-rotate-column! (lcs s) (lcy s)) (let ((n (lc4s-nrows s))) (lcx! s (modulo (+ (lcx s) 1) n)) (when (= (lcc s) (lcy s)) (lcr! s (modulo (+ (lcr s) 1) n))) (when (= (lcj s) (lcy s)) (lci! s (modulo (+ (lci s) 1) n)))) s) (define (lc4-find! s ch) (let ((arr1d (lcs1 s))) (let f ((k (array-start arr1d 0))) (unless (< k (array-end arr1d 0)) (error "character not found in table" ch)) (if (char=? ch (lc4-number->char (array-ref arr1d k))) (receive (q r) (floor/ k (lc4s-ncols s)) (lcr! s q) (lcc! s r)) (f (+ k 1)))))) ;;; Main encryption procedure (define (lc4s-marked-tile s) (array-ref (lcs s) (lci s) (lcj s))) ;;; Encryption (define (lc4-encrypt-char! state ch) (let ((nrows (lc4s-nrows state)) (ncols (lc4s-ncols state))) (lc4-find! state ch) (receive (dx dy) (floor/ (lc4s-marked-tile state) ncols) (lcx! state (modulo (+ (lcr state) dx) nrows)) (lcy! state (modulo (+ (lcc state) dy) ncols)) (let ((enc (array-ref (lcs state) (lcx state) (lcy state)))) (lc4-right-rotate-row! state) (lc4-down-rotate-column! state) (receive (di dj) (floor/ enc ncols) (lci! state (modulo (+ (lci state) di) nrows)) (lcj! state (modulo (+ (lcj state) dj) ncols))) (lc4-number->char enc))))) (define (lc4-encrypt-string! state str trace?) (let ((oport (open-output-string))) (string-for-each (lambda (c) (let ((e (lc4-encrypt-char! state c))) (display e oport) (when trace? (display (lc4s->string state)) (display " ") (display c) (display " ") (display e) (newline)))) str) (get-output-string oport))) ;;; Main encryption interface (define (lc4-encrypt key nonce msg sig trace?) (let* ((nkey (list->vector (map lc4-char->number (string->list key)))) (state (lc4-make-state nkey))) (when trace? (newline) (display (lc4s->string state)) (newline)) (let ((oport (open-output-string))) (lc4-encrypt-string! state nonce #t) ; output discarded (display (lc4-encrypt-string! state msg #t) oport) (display (lc4-encrypt-string! state sig #t) oport) (let ((ctext (get-output-string oport))) (when trace? (display "Ciphertext: ") (display ctext) (newline)) ctext)))) ;;; Example from ElsieFour paper [Kam17] (define (kam17-example) (lc4-encrypt "xv7ydq#opaj_39rzut8b45wcsgehmiknf26l" "solwbf" "im_about_to_put_the_hammer_down" "#rubberduck" #t)) ;;; Decryption (define (lc4-decrypt-char! state ch) (lc4-find! state ch) (lcx! state (lcr state)) (lcy! state (lcc state)) (receive (dx dy) (floor/ (lc4s-marked-tile state) (lc4s-ncols state)) (lcr! state (modulo (- (lcx state) dx) (lc4s-nrows state))) (lcc! state (modulo (- (lcy state) dy) (lc4s-ncols state))) (let ((dc (array-ref (lcs state) (lcr state) (lcc state)))) (lc4-right-rotate-row! state) (lc4-down-rotate-column! state) (receive (di dj) (floor/ (lc4-char->number ch) (lc4s-ncols state)) (lci! state (modulo (+ (lci state) di) (lc4s-nrows state))) (lcj! state (modulo (+ (lcj state) dj) (lc4s-ncols state)))) (lc4-number->char dc)))) (define (lc4-decrypt-string! state str trace?) (let ((oport (open-output-string))) (string-for-each (lambda (c) (let ((e (lc4-decrypt-char! state c))) (display e oport) (when trace? (display (lc4s->string state)) (display " ") (display c) (display " ") (display e) (newline)))) str) (get-output-string oport))) ;;; Main decryption interface (define (lc4-decrypt key nonce msg sig trace?) (let* ((nkey (list->vector (map lc4-char->number (string->list key)))) (state (lc4-make-state nkey))) (when trace? (newline) (display (lc4s->string state)) (newline)) (let ((oport (open-output-string))) (lc4-encrypt-string! state nonce #t) ; N.B. encrypt; output discarded (display (lc4-decrypt-string! state msg #t) oport) (let ((ctext (get-output-string oport))) (when trace? (display "Plaintext: ") (display ctext) (newline) (display "Signature: ") (display (if (string=? sig (string-copy ctext (- (string-length ctext) (string-length sig)))) "OK" "MISMATCH"))) ctext)))) ;;;Example based on the one in the paper. (define (kam17-example-decrypt) (lc4-decrypt "xv7ydq#opaj_39rzut8b45wcsgehmiknf26l" "solwbf" "i2zqpilr2yqgptltrzx2_9fzlmbo3y8_9pyssx8nf2" "#rubberduck" #t)) ;;; Run example (kam17-example) (newline) (kam17-example-decrypt) (newline) ;;;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.
import numpy as np _ALPHABET = list("#_23456789abcdefghijklmnopqrstuvwxyz") _INDEX_LOOKUP = {val: idx for idx, val in enumerate(_ALPHABET)} class _State: def __init__(self, K): self.S = np.empty((6, 6), dtype=int) for k in range(36): self.S[divmod(k, 6)] = K[k] self.i = 0 self.j = 0 def astuple(self): return (self.S, self.i, self.j) def _update(self, S, i, j): self.S = S self.i = i self.j = j def step(self, r, x, y, ct): S, i, j = self.astuple() S[r, :] = np.roll(S[r, :], 1) y = (y + (x == r)) % 6 j = (j + (i == r)) % 6 S[:,y] = np.roll(S[:, y], 1) i = (i + (j == y)) % 6 i = (i + ct // 6) % 6 j = (j + ct % 6) % 6 self._update(S, i, j) def _encrypt(state, P): n = P.shape[0] C = np.empty(n, dtype=int) for idx, pt in enumerate(P): S, i, j = state.astuple() where = np.where(S==pt) r = where[0][0] c = where[1][0] x = (r + S[i, j] // 6) % 6 y = (c + S[i, j] % 6) % 6 ct = S[x, y] C[idx] = ct state.step(r, x, y, ct) return C def _decrypt(state, C): n = C.shape[0] P = np.empty(n, dtype=int) for idx, ct in enumerate(C): S, i, j = state.astuple() where = np.where(S==ct) x = where[0][0] y = where[1][0] r = (x - S[i, j] // 6) % 6 c = (y - S[i, j] % 6) % 6 pt = S[r, c] P[idx] = pt state.step(r, x, y, ct) return P def encrypt(key, nonce, text, signature="", header=""): K = np.array([_INDEX_LOOKUP[x] for x in key.lower()]) state = _State(K) _encrypt(state, np.array([_INDEX_LOOKUP[x] for x in nonce.lower()])) _encrypt(state, np.array([_INDEX_LOOKUP[x] for x in header.lower()])) P = np.array([_INDEX_LOOKUP[x] for x in (text + signature).lower()]) encrypted = ''.join(_ALPHABET[x] for x in _encrypt(state, P)) return encrypted def decrypt(key, nonce, text, header=""): K = np.array([_INDEX_LOOKUP[x] for x in key.lower()]) state = _State(K) _encrypt(state, np.array([_INDEX_LOOKUP[x] for x in nonce.lower()])) _encrypt(state, np.array([_INDEX_LOOKUP[x] for x in header.lower()])) C = np.array([_INDEX_LOOKUP[x] for x in text.lower()]) decrypted = ''.join(_ALPHABET[x] for x in _decrypt(state, C)) return decrypted if __name__ == "__main__": key = "xv7ydq#opaj_39rzut8b45wcsgehmiknf26l" nonce = "solwbf" text = "im_about_to_put_the_hammer_down" signature = "#rubberduck" encrypted = encrypt(key, nonce, text, signature=signature) print("Encrypted: {}".format(encrypted)) decrypted = decrypt(key, nonce, encrypted) print("Decrypted: {}".format(decrypted))Output: