Solitaire encryption algorithm
A small Python implementation of Bruce Schneier's Solitaire encryption algorithm.
For a visual demonstration of the algorithm, check out SolitaireCryptoDemo.
File: SolitaireDeck.py
class SolitaireDeck:
ALPHA="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def __init__(self,deck=None):
"""Initiates the deck of cards. Deck can be a list containing values 1-54."""
if not deck: # If we didn't get a deck...
deck = range(1,55) # ...we initiate an unkeyed deck. [1,2,3, ... ,54]
self.deck = deck # Store the deck-list in our object variable self.deck
def next(self):
"""Returns the next value in the keystream."""
d=self.deck # Use a local reference to the deck object to save typing.
while True: # Loop until we get an output card that is not a Joker.
# --- Move Jokers ---------------------------------------------------------------------
for n in [53,54]: # Loop over both Jokers (JokerA=53, JokerB=54).
i=d.index(n) # Get the index of the current Joker.
d.pop(i) # Remove the Joker from the deck.
i+=n-52 # Move Joker A one step and Joker B two steps.
if i>53: # If the new index is greater than 53...
i-=53 # ...wrap the index to get to the top of the deck.
d.insert(i,n) # Insert the Joker at the new index.
# --- Triple cut ----------------------------------------------------------------------
a,b=d.index(53),d.index(54) # Get the locations of the both jokers.
if a>b: # If b is closer to the top...
a,b=b,a # ...switch the two variables.
d[:]=d[b+1:]+d[a:b+1]+d[:a] # Perform the triple cut using some ninja list slicing.
# --- Count cut -----------------------------------------------------------------------
v=min(d[-1],53) # Get the value of the bottom card in the deck (max 53).
d[:-1] = d[v:-1]+d[:v] # Perform the count cut using some more ninja slicing.
# --- Find output card ----------------------------------------------------------------
v=min(d[0],53) # Get the value of the top card (max 53).
if d[v]<53: # If we have an output value less than 53...
return d[v] # ...return that as the next key in the stream.
def key(self,key):
"""Keys the deck using a keyword (A-Z)."""
d=self.deck # Use a local reference to the deck object to save typing.
key=key.strip().upper() # Remove leading and trailing spaces, make it upper case.
for c in key: # Loop through all characters.
if c in self.ALPHA: # Is it a valid character?
self.next() # Perform a normal keystream round.
v=self.ALPHA.index(c)+1 # Get the characters value 1-26
d[:-1]=d[v:-1]+d[:v] # Perform a count cut using the character value.
def format(self,text):
"""Formats the text in groups of five, padded with X."""
text=text.strip().upper() # Remove leading and trailing spaces, make it upper case.
r=[] # Create a list for the result.
while len(text): # Loop as long as we have any text.
p=text[:5] # Get the first 5 characters.
text = text[5:] # Remove the 5 characters.
if len(p)<5: # Did we get less than 5 characters?
p=p+"X"*(5-len(p)) # Pad out with X
r.append(p) # Add group to result.
return " ".join(r) # Return the result as string.
def _encdec(self,text,mod):
"""The actual encryption/decryption algorithm."""
text=text.strip().upper() # Remove leading and trailing spaces, make it upper case.
r=[] # Create a list for the result.
for c in text: # Loop through all characters.
if c in self.ALPHA: # Is it a valid character?
k=self.next() # Get the next key in stream.
v=self.ALPHA.index(c)+1 # Get the value of character. (1-26)
v=(v+(k*mod))%26 # Add or subtract key from value. Modulus 26 to get 1-26.
r.append(self.ALPHA[v-1]) # Append the encrypted/decrypted character.
# The 4 lines above can also be written as a one-liner.
# r.append(self.ALPHA[(((self.ALPHA.index(c)+1)+(self.next()*mod))%26)-1])
return "".join(r)
def enc(self,text):
"""Encrypts a text."""
return self._encdec(text,1) # Tell algorithm to encrypt (add key to character)
def dec(self,text):
"""Decrypts a text."""
return self._encdec(text,-1) # Tell algorithm to decrypt (subtract key from character).
def main(args):
print ""
print "Test 1: First 10 keys from an unkeyed deck:"
d = SolitaireDeck()
r = []
for i in range(10):
r.append(str(d.next()))
print " ",",".join(r)
print ""
print "Test 2: Encryption/Decryption with an unkeyed deck."
d = SolitaireDeck()
ctext = d.format("AAAAAAAAAA")
etext = d.format(d.enc(ctext))
d = SolitaireDeck()
dtext = d.format(d.dec(etext))
print " Clear text:",ctext
print " Encrypted text:",etext
print " Decrypted text",dtext
print ""
print "Test 3: First 15 keys from a deck keyed with \"FOO\"."
d = SolitaireDeck()
d.key("FOO")
r = []
for i in range(15):
r.append(str(d.next()))
print " ",",".join(r)
print ""
print "Test 4: Encryption/Decryption with a deck keyed with \"FOO\"."
d = SolitaireDeck()
d.key("FOO")
ctext = d.format("AAAAAAAAAAAAAAA")
etext = d.format(d.enc(ctext))
d = SolitaireDeck()
d.key("FOO")
dtext = d.format(d.dec(etext))
print " Clear text:",ctext
print " Encrypted text:",etext
print " Decrypted text",dtext
print ""
print "Test 5: Encryption/Decryption with a deck keyed with \"CRYPTONOMICON\"."
d = SolitaireDeck()
d.key("CRYPTONOMICON")
ctext = d.format("SOLITAIRE")
etext = d.format(d.enc(ctext))
d = SolitaireDeck()
d.key("CRYPTONOMICON")
dtext = d.format(d.dec(etext))
print " Clear text:",ctext
print " Encrypted text:",etext
print " Decrypted text",dtext
print ""
if __name__ == '__main__':
import sys
main(sys.argv[1:])
Related links
Comment this note:
Tim 2012-12-17 20:43:52
Sweet
TY