{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Solitaire - Deck of Cards Based Strong Encryption\n", "============================\n", "* * *\n", "Introduction\n", "----------------\n", "This is an implementation of the Solitaire(aka. Pontifex) encryption system found in the novel *Cryptonomicon* written by Neal Stephensen. This cryptosystem was invented by Bruce Schneier, and this implementation is based on his description found in an appendix of the afformentioned novel. \n", "\n", "Solitaire is intended to use a standard deck of playing cards to generate a sequence of pseudo-random numbers that are moduarly added to the message to create the cyphertext. The receiver of the message can then moduarly subtract the same sequence from the cyphertext to recover the message, provided that the receiver has their deck in the same initial configuration as the sender. This initial configuration is the shared secret. \n", "\n", "Individual cards can be mapped to the numbers 1-52 in any way that is convenient. In Schneier's description, he suggests using the bridge ordering of suites(clubs, hearts, diamonds, spades) so that, for example, the ace of clubes is 1, the deuce of diamonds is 28, and so on. Whenever needed, the two jokers are both interpreted as 53. Also, the jokers must be somehow distinguishable. One of them will be referred to as joker 'A', the other as joker 'B'.\n", "\n", "***\n", "\n", "The steps to generate the keystream of pseudo-random numbers are as follows:\n", "1. Find joker A. Move it one card down. If it is at the bottom of the deck, place it beneath the top card.\n", "2. Find joker B. Move it two cards down. Again, if B is at the bottom of the deck, wrap around to the top. A and B should never be the top cards.\n", "3. Do a triple cut of the deck based on the locations of the two jokers. For example, if the deck looks like\n", "```\n", "[Top cards]A[Middle cards]B[Bottom cards]\n", "```\n", "swap the top cards and bottom cards to make it look like:\n", "```\n", "[Bottom cards]A[Middle cards]B[Top cards]\n", "```\n", "If joker B happens to be nearer to the top, just swap A and B in the above diagram.\n", "4. Look at the card on the bottom of the deck. Convert that card to a number and call it n. Now perform a cut after the top n cards, leaving the bottom card at the bottom. For example, if the deck looks like\n", "```\n", "[Top n cards][Remainder of deck][Bottom card]\n", "```\n", "Then after the cut it should look like\n", "```\n", "[Remainder of deck][Top n cards][Bottom card]\n", "```\n", "5. Convert the top card to a number, and look at the card at that position in the deck. Convert that card to a number. This number is your output key. If the card happens to be a joker, sorry, start again from step 1. Note that the deck is unchanged by this step.\n", "6. **Repeat** steps 1-5 to generate as many keys as needed." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "import random\n", "from random import shuffle\n", "#random.seed(0) #enable for testing" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def keystream(deck):\n", " deck = deck[:] #make local copy of deck\n", " while True:\n", " #step 1:\n", " iA = deck.index('A')\n", " deck.pop(iA)\n", " iA = iA\n", " if iA == 53:\n", " iA = 1\n", " else:\n", " iA = iA+1\n", " deck.insert(iA,'A')\n", " #step 2\n", " iB = deck.index('B')\n", " deck.pop(iB)\n", " if iB == 53:\n", " iB = 2\n", " elif iB == 52:\n", " iB = 1\n", " else:\n", " iB = iB + 2\n", " deck.insert(iB,'B')\n", " #step 3\n", " i1 = min(iA, iB)\n", " i2 = max(iA, iB)\n", " deck = deck[i2+1:] + deck[i1:i2+1] + deck[:i1]\n", " #step 4\n", " bottom = deck[-1]\n", " n = bottom if type(bottom) is int else 53\n", " deck = deck[n:-1] + deck[:n] + [bottom,]\n", " #step 5\n", " n = deck[0] if type(deck[0]) is int else 53\n", " out = deck[n-1]\n", " if type(out) is int:\n", " yield out+1" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "To encode or decode the messages, just convert the input python strings to their appropriate numbers sequences eg. (```'aab'``` becomes ```0,0,1```). Then either add or subtract the number from the keystream." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def encode(message, deck):\n", " ks = keystream(deck)\n", " cypher = []\n", " if type(message) is str:\n", " message = message.lower().encode('ascii')\n", " for l in message:\n", " x = next(ks)\n", " cypher.append((l - 97 + x) % 26 + 97)\n", " cypher = bytes(cypher).decode('ascii')\n", " return cypher\n", "\n", "def decode(cypher, deck):\n", " ks = keystream(deck)\n", " message = []\n", " if type(cypher) is str:\n", " cypher = cypher.lower().encode('ascii')\n", " for l in cypher:\n", " message.append((l - 97 - next(ks)) % 26 + 97)\n", " message = bytes(message).decode('ascii')\n", " return message\n", " " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Prepare the deck" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "deck = []\n", "for i in range(52):\n", " deck.append(i+1)\n", "deck.extend(['A','B'])\n", "shuffle(deck)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Let's have a look at some sample output from the keystream" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[16, 11, 19, 14, 49, 8, 13, 32, 14, 47, 6, 35, 38, 13, 31, 22, 46, 37, 52, 10]\n" ] } ], "source": [ "ks = keystream(deck)\n", "keys = [next(ks) for _ in range(20)]\n", "print(keys)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "Now encrypt a short message and look at the cyphertext and the decrypt. Note that the message can only contain lowercase letters. No numbers, no spaces, no punctuation. " ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "message: thisisasecretmessagestoppleasedonttellanyonestop\n", "cyphertext: jsbgfanysxxnfzjomlgoasrvqkjzovnqdrwfvcriuebhtkwd\n", "decrypt: thisisasecretmessagestoppleasedonttellanyonestop\n" ] } ], "source": [ "message = 'thisisasecretmessagestoppleasedonttellanyonestop'\n", "print('message: ' + message)\n", "cyphertext = encode(message, deck)\n", "print('cyphertext: ' + cyphertext)\n", "decrypt = decode(cyphertext, deck)\n", "print('decrypt: ' + decrypt) " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Conclusion\n", "-----------\n", "As a computer based encryption scheme, Solitaire isn't all that useful. However, it is honest to goodness strong encryption that only requires a deck of cards and some patience. " ] } ], "metadata": { "celltoolbar": "", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.4.3" } }, "nbformat": 4, "nbformat_minor": 0 }