## 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 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).

Pages: 1 2

### 5 Responses to “Elsie Four”

1. chaw said

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) ;;; ```

2. bavier said

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 ```

3. bavier said

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

4. BE said

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.

5. Daniel said

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
C = np.empty(n, dtype=int)
for idx, pt in enumerate(P):
S, i, j = state.astuple()
where = np.where(S==pt)
r = where
c = where
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
P = np.empty(n, dtype=int)
for idx, ct in enumerate(C):
S, i, j = state.astuple()
where = np.where(S==ct)
x = where
y = where
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

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"
signature = "#rubberduck"

encrypted = encrypt(key, nonce, text, signature=signature)
print("Encrypted: {}".format(encrypted))

decrypted = decrypt(key, nonce, encrypted)
print("Decrypted: {}".format(decrypted))
```

Output:

```Encrypted: i2zqpilr2yqgptltrzx2_9fzlmbo3y8_9pyssx8nf2