Solitaire Cipher

January 18, 2011

Today’s exercise is straight forward but tedious; I will never admit the number of off-by-one errors I made while writing it. We will represent cards by the numbers 1 to 54, in bridge order, and letters by the numbers 1 to 26; it might be easier in both cases to use zero-based indices, because the modular arithmetic is more natural, but we will retain the conventions used by Schneier. A deck is a list of fifty-four card indices in positions 0 to 53. The function that converts a card number to its name is not needed elsewhere, but is given below because it is useful during debugging:

The keystream generator takes a deck and produces a new deck in four steps. There is nothing hard about these functions, but all of the + 1 operations and the counts of the various deck positions are easy to get wrong:

The procedure for keying the deck is given below. Schneier’s description of the operation is confusing. The thing to remember is that the deck is manipulated one time more than the number of characters in the key:

Given what we already have, encrypt and decrypt are simple. The keystream and input text are processed in parallel, except that an extra element of the keystream is generated whenever a joker appears. The arithmetic to add and subtract letters is computed by the k and p variables:

We used several utility functions. Prep eliminates non-alphabetic characters from an input string and converts it to upper case. Numb and letr translate A=1 … Z=26. Fives breaks its input into five-character blocks:

For testing, we use sol-equal? to check if two strings are equal after converting them to upper case and eliminating non-alphabetic characters and trailing nulls, rand-string to generate random strings for keys and plaintext, and a modified version of the assert macro that uses sol-equal? instead of equal?:

Like this:

Related

9 Responses to “Solitaire Cipher”

def movedown(deck,entry,move):
"""Move a card down a number of cards in the deck"""
newindex = deck.index(entry) + move
if newindex >= len(deck):
newindex -= len(deck) - 1
deck.remove(entry)
deck.insert(newindex,entry)
def countcut(deck,count):
"""a counted cut, based on the number of the bottom card in
the deck, moves the top count cards to just above the bottom
card"""
deck[-1:1] = deck[:count]
del(deck[:count])
def joker(card):
"""Is the card a joker?"""
return card in ('A','B')
def step(deck):
"""Perform a step on the deck returning the key"""
# A joker is moved one card down the deck, wrapping around the
# end of the deck if necessary
movedown(deck,'A',1)
# the B joker is moved two cards down the deck, again wrapping
# around the deck if necessary.
movedown(deck,'B',2)
# a triple-cut swaps all the cards above the highest joker in the
# deck with all the cards below the lowest joker in the deck,
# leaving the two jokers and the cards between them in place.
indexa = deck.index('A')
indexb = deck.index('B')
if indexb > indexa:
topindex, botindex = indexa, indexb
else:
topindex, botindex = indexb, indexa
deck.extend(deck[topindex:botindex + 1])
deck.extend(deck[:topindex])
del(deck[:botindex + 1])
# Fourth, a counted cut, based on the number of the bottom card in
# the deck. the cards are numbered 1 to 52 in bridge order with
# ace low to king high in each suit, clubs, diamonds, hearts,
# spades, and either joker counting as 53
count = deck[-1]
if joker(count):
count = 53
countcut(deck,count)
# Then look at the top card in the deck and count down the given
# number to determine the current key card.
count = deck[0]
if joker(count):
count = 53
return deck[count]
def keydeck(deck,key):
# For each character in the key, perform a single step then do a
# counted cut on the number of the current character, with
# A=1...Z=26
for count in [ord(c) - 64 for c in key.upper()]:
step(deck)
countcut(deck,count)
def solitare(plaintext,key="",decrypt=False):
"""encrypt and decrypt using the solitare cyper"""
# Make uppercase and remove spaces
plaintext = plaintext.upper().replace(' ','')
# The plain-text has nulls (the letter X) added to the end to make
# the message length a multiple of five
pad = 5 - len(plaintext) % 5
if pad != 5:
plaintext += 'X'
# Create a deck in bridge order
deck = range(1,53) + ['A','B']
keydeck(deck,key)
ciphertext = ''
count = 0
for c in plaintext:
# the cipher-text is split into five-character blocks for
# convenience.
if count == 5:
ciphertext += ' '
count = 0
key = step(deck)
# the jokers are skipped
while joker(key):
key = step(deck)
# each character is added (for encryption) or subtracted (for
# decryption) from the current text character, wrapping around
# the alphabet as necessary
e = ord(c)
if (decrypt):
e -= key
while e < ord('A'):
e += 26
else:
e += key
while e > ord('Z'):
e -= 26
count += 1
ciphertext += chr(e)
return ciphertext
print solitare("AAAAAAAAAA")
print solitare("AAAAAAAAAAAAAAA","FOO")
print solitare("SOLITAIRE","CRYPTONOMICON")
print solitare("EXKYI ZSGEH",decrypt=True)
print solitare("ITHZU JIWGR FARMW","FOO",decrypt=True)
print solitare("KIRAK SFJAN","CRYPTONOMICON",decrypt=True)

Here is v. 0.0.1 of my writing of this algorithm. This is not neat and pretty, merely functional. The first block of code is solitaire.py. It executes when called on the console (only tested on UNIX-like systems) and must be passed arguments to work. There is a class and list of functions in solitaire.py. Deck class handles all the stuff you’d expect a deck in solitaire to handle: pushing cards, popping cards, getting characters, advancing state, and a built-in make. The functions in solitaire.py are mainly for loading and unloading deck states and to operate the command-line form.

Advpass option for command line operation is a demonstration of nested decks, basically a deck of decks. The deck class is flexible enough to take characters, strings, binary blobs, functions, or anything else that counts as an object as a card’s representation.

I wrote pyrand.py to interface to /dev/urandom and serve me up tasty 32-bit unsigned random integers. Replace in code with your own solution as I suspect pyrand is the slow spot in the shuffle routines in solitaire.py…

Then open soldeck001.deck in a text editor. The second and third values are the count values (the number the card represents, used for counting) and face values (characters). Model your real deck after this one and check for yourself!

I’m sure many of those who read this thread have heard of Pontifex, or the Solitaire cipher. This is my attempt to implement a general form of that algorithm which can accept as face values any type of data, including more decks. This python module even allows for a deck of decks! As written, it is meant to operate just like the card algorithm, so if you wanted to you could print up code books of what your ‘field agents’ are carrying decks of cards to generate! This module also has two password generating routines, one which uses the solitaire deck simply, and another that is a play on the deck of decks idea.

Here is v. 0.0.3 of my writing of this algorithm. This is not neat and pretty, merely functional. The first block of code is solitaire.py. It executes when called on the console (only tested on UNIX-like systems) and must be passed arguments to work. There is a class and list of functions in solitaire.py. Deck class handles all the stuff you’d expect a deck in solitaire to handle: pushing cards, popping cards, getting characters, advancing state, and a built-in make. The functions in solitaire.py are mainly for loading and unloading deck states and to operate the command-line form.

Advpass option for command line operation is a demonstration of nested decks, basically a deck of decks. The deck class is flexible enough to take characters, strings, binary blobs, functions, or anything else that counts as an object as a card’s representation.

I wrote pyrand.py to interface to /dev/urandom and serve me up tasty 32-bit unsigned random integers. Replace in code with your own solution as I suspect pyrand is the slow spot in the shuffle routines in solitaire.py.

I decided to write this because I wanted to start writing python code where I implement quantum computing proof cryptographic algorithms and protocols. I think OpenPGP is sorely lacking in post-quantum computing foresight at this point and warez has to get out there to handle this threat.

Yea yea, I know it’s sloppy code. I wrote it to work, and I may work on cleaning it up and making it faster and more general. In the event I don’t, here’s a little present for y’all: