aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md82
-rw-r--r--card-tiles.jpgbin0 -> 172391 bytes
-rw-r--r--lc4.py26
3 files changed, 100 insertions, 8 deletions
diff --git a/README.md b/README.md
index 94e33e4..86cda97 100644
--- a/README.md
+++ b/README.md
@@ -142,6 +142,8 @@ stays the same.
Grab a bag full of tiles and randomly draw them one by one. Key is the 49-item permutation of them.
+## Modifications
+
### Key expansion from a password
Remembering 49-position random permutation that includes weird characters is
@@ -196,6 +198,84 @@ This works because the cipher output is message-dependent: Having a wrong bit
somewhere in the middle causes avalanche effect and erases any meaning from the
text after several characters.
+### Alternative playing-card-compatible board
+
+The following board can be used so that characters and offsets can be easily
+mapped to playing cards, similar to the Solitaire cipher [2]. Using playing
+cards could be more innocuous and easily explainable to the secret police than
+a set of peculiar numbered tiles.
+
+```
+a b c d e f g
+h i j k l m n
+o p q r s t u
+v w x y z _ .
+, - + * / : ?
+! ' ( ) 1 2 3
+4 5 6 7 8 9 0
+```
+
+**This board uses a 1-based index**, so `a=1`, `b=2`, and so on. With this layout,
+the following mapping to playing cards is used:
+
+| **Character** | Card | Index | **Character** | Card | Index | **Character** | Card | Index | **Character** | Card | Index |
+|-----------|------|-------|-----------|------|-------|-----------|------|-------|-----------|------|--------|
+| a | A♦ | 1 | n | A♣ | 14 | _ | A♥ | 27 | 1 | A♠ | 40 |
+| b | 2♦ | 2 | o | 2♣ | 15 | . | 2♥ | 28 | 2 | 2♠ | 41 |
+| c | 3♦ | 3 | p | 3♣ | 16 | , | 3♥ | 29 | 3 | 3♠ | 42 |
+| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
+| j | 10♦ | 10 | w | 10♣ | 23 | ! | 10♥ | 39 | 7 | 7♠ | 46 |
+| k | J♦ | 11 | x | J♣ | 24 | ' | J♥ | 39 | 8 | 8♠ | 47 |
+| l | Q♦ | 12 | y | Q♣ | 25 | ( | Q♥ | 39 | 9 | 9♠ | 48 |
+| m | K♦ | 13 | z | K♣ | 26 | ) | K♥ | 39 | 0 | 10♠ | 49 (0) |
+
+The (x, y) offset can be calculated by first determining the index: take the
+number of the suit (0-3), multiply by 13, and add the card number (using 1 for
+the Ace, 11-13 for face cards). Then, determine the offset from the index
+using the usual modular arithmetic: `x = index % 7` and `y = index / 7`.
+
+Alphanumeric characters can be easily mapped to cards, with letters mapping to
+Diamonds or Clubs and numbers mapping to Spades (note that Spades face cards
+are ommitted). All of the special characters are mapped to the Hearts, though
+coming up with a mnemonic for the mapping of each special character to each
+Heart is left as an exercise for the reader.
+
+For an example, view [this image](card-tiles.jpg). In this example, the marker
+is on J♣ (at the top left), and we want to encrypt the plaintext character `e`.
+The image includes the character, index, and offsets for the relevant cards.
+
+1. From the card mapping, `e` maps to 5♦ (letters `a` through `m` map to Diamonds), which is on the second row.
+1. The marker card, J♣, has:
+ - an index of 24 (Clubs are suit 1, and the Jack is the 11th card in the suit): `i = 1 * 13 + 11 = 24`
+ - an x-offset of 3: `x = i % 7 = 24 % 7 = 3`
+ - a y-offset of 3: `y = i / 7 = 24 / 7 = 3`
+1. Using the marker offsets, the ciphertext card is three rows down and three columns to the right of 5♦, which is 6♠.
+1. From the card mapping, 6♠ maps to `6` (Spades map directly to digits), and has:
+ - an index of 45 (Spades are suit 3, and 6 is the 6th card in the suit): `i = 3 * 13 + 6 = 45`
+ - an x-offset of 3: `x = i % 7 = 45 % 7 = 3`
+ - a y-offset of 6: `y = i / 7 = 45 / 7 = 6`
+1. We then complete the cipher as normal:
+ - Output `6` as the ciphertext
+ - Rotate the row containing the plaintext card, 5♦
+ - Rotate the column containing the ciphertext card, 6♠
+ - Move the marker according to the offsets of the ciphertext card, to the right 3 and down 6
+
+For LC4, the following board could be used:
+
+```
+a b c d e f
+g h i j k l
+m n o p q r
+s t u v w x
+y z _ 2 3 4
+5 6 7 8 9 #
+```
+
+The "Hearts" suit would be ommitted from the card mapping table, and the index
+of each of the Spades would be decreased by 13 to compensate. `_` and `#`
+would map to A♠ and 10♠, respectively.
+
## References
-[1] *Kaminsky, Alan. "ElsieFour: A Low-Tech Authenticated Encryption Algorithm For Human-to-Human Communication." IACR Cryptology ePrint Archive 2017 (2017): 339.*
+[1] *Kaminsky, Alan. "ElsieFour: A Low-Tech Authenticated Encryption Algorithm For Human-to-Human Communication." IACR Cryptology ePrint Archive 2017 (2017): 339.*
+[2] *Schneier, Bruce. ["The Solitaire Encryption Algorithm"](https://www.schneier.com/academic/solitaire/).*
diff --git a/card-tiles.jpg b/card-tiles.jpg
new file mode 100644
index 0000000..920a2fc
--- /dev/null
+++ b/card-tiles.jpg
Binary files differ
diff --git a/lc4.py b/lc4.py
index 03043a4..fea2f7a 100644
--- a/lc4.py
+++ b/lc4.py
@@ -54,8 +54,10 @@ import argparse
version = "v2.8.1 (2018-07-24)"
# define alphabet
-letters6 = "#_23456789abcdefghijklmnopqrstuvwxyz"
-letters7 = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()"
+letters6 = "#_23456789abcdefghijklmnopqrstuvwxyz"
+letters6card = "#abcdefghijklmnopqrstuvwxyz_23456789"
+letters7 = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()"
+letters7card = "0abcdefghijklmnopqrstuvwxyz_.,-+*/:?!'()123456789"
def missing_letters(s,t):
@@ -149,9 +151,11 @@ def rotate_marker_down(m, col, n):
return ((m[0] + n) % size, m[1])
-def derive_key(password):
+def derive_key(password, one_indexed):
i = 0
k = letters
+ # if using one-indexed arrays, moves the zero element to the end
+ if one_indexed: k = k[1:]+k[0]
for c in password:
(row, col) = find_ix(c)
k = rotate_down(rotate_right(k, i, col), i, row)
@@ -293,7 +297,7 @@ def test1(size, fixednonce):
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)
+ key = derive_key(keyword, false)
else:
key = letters
initialkey = key
@@ -333,6 +337,8 @@ if __name__ == '__main__':
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")
+ parser.add_argument("-pc", "--playingcard", help="Use the \"playing card\" character tables (default: standard tables)", action="store_true")
+
mgroup2 = parser.add_mutually_exclusive_group()
mgroup2.add_argument("-ks", "--keystring", metavar="STRING", help="use STRING as key")
mgroup2.add_argument("-kf", "--keyfile", metavar="FILE", help="read key from FILE")
@@ -375,10 +381,16 @@ if __name__ == '__main__':
if args.ls47:
size = 7
- letters = letters7
+ if args.playingcard:
+ letters = letters7card
+ else:
+ letters = letters7
else:
size = 6
- letters = letters6
+ if args.playingcard:
+ letters = letters6card
+ else:
+ letters = letters6
tiles = list(zip(letters, [(x // size, x % size) for x in range(size * size)]))
@@ -423,7 +435,7 @@ if __name__ == '__main__':
if args.keywordfile: args.keywordstring = open(args.keywordfile, 'r').read().rstrip('\r\n')
if args.keywordstring:
szkeyword = args.keywordstring
- key = derive_key(args.keywordstring)
+ key = derive_key(args.keywordstring, args.playingcard)
if args.keyfile: args.keystring = open(args.keyfile, 'r').read().rstrip('\r\n')
if args.keystring: key = args.keystring;