+#!/usr/local/bin/python\r
+\r
+# Solitaire.py\r
+# Copyright (C) 1999 Mordy Ovits <movits@lockstar.com>\r
+# This program is free software; you can redistribute it and/or \r
+# modify it under the terms of the GNU General Public License as \r
+# published by the Free Software Foundation; either version 2 of the \r
+# License, or (at your option) any later version.\r
+# This program is distributed in the hope that it will be useful,\r
+# but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+# GNU General Public License for more details.\r
+# You should have received a copy of the GNU General Public License\r
+# along with this program (see License.txt); if not, write to the Free \r
+# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA \r
+# 02111-1307 USA\r
+# NOTE: the Solitaire encryption algorithm is strong cryptography.\r
+# That means the security it affords is based on the secrecy of the \r
+# key rather than secrecy of the algorithm itself. That also means \r
+# that this program and programs derived from it may be treated as \r
+# a munition for the purpose of export regulation in the United States \r
+# and other countries. You are encouraged to seek competent legal \r
+# counsel before distributing copies of this program.\r
+#\r
+# Solitaire.py is an implementation of the Solitaire encryption \r
+# algorithm, as designed by Bruce Schneier and described at: \r
+# http://www.counterpane.com/solitaire.html\r
+# as well as in Neil Stephenson's novel Cryptonomicon.\r
+# Solitaire is a cryptographically strong stream cipher that can\r
+# be implemented using a deck of playing cards. This implementation is\r
+# designed to be extremely clean, and a good aid to someone looking to see\r
+# how Solitaire works \r
+\r
+\r
+class Deck:\r
+ # Sets up the Deck in Bridge format\r
+ def __init__(self):\r
+ self.Cards = []\r
+ for i in range(1,55):\r
+ self.Cards.append(i)\r
+\r
+ def MoveDownOne(self, index):\r
+ if (index == 53):\r
+ temp = self.Cards[index]\r
+ self.Cards[index] = self.Cards[0]\r
+ self.Cards[0] = temp\r
+ else:\r
+ temp = self.Cards[index]\r
+ self.Cards[index] = self.Cards[index + 1]\r
+ self.Cards[index + 1] = temp\r
+\r
+ def MoveDownTwo(self, index):\r
+ if (index == 53):\r
+ self.Cards.insert(2, self.Cards[index])\r
+ del self.Cards[index + 1]\r
+ elif (index == 52):\r
+ self.Cards.insert(1, self.Cards[index])\r
+ del self.Cards[index + 1]\r
+ else:\r
+ self.Cards.insert((index + 3), self.Cards[index])\r
+ del self.Cards[index]\r
+\r
+ def GetValueByIndex(self, index):\r
+ return self.Cards[index]\r
+\r
+ def GetIndexByValue(self, value):\r
+ for i in range(0,54):\r
+ if (self.Cards[i] == value):\r
+ return i\r
+ return -1\r
+\r
+ def TripleCut(self, low, high):\r
+ self.Cards = self.Cards[high + 1:] + self.Cards[low:high + 1] + self.Cards[:low]\r
+\r
+ def CountCutByIndex(self, index):\r
+ CutBy = index\r
+ self.Cards = self.Cards[CutBy:-1] + self.Cards[:CutBy] + [self.Cards[53]]\r
+\r
+\r
+\r
+class SolitaireCipher:\r
+ def __init__(self):\r
+ self.Deck = Deck()\r
+ \r
+ def GetNextOutput(self):\r
+ self.Deck.MoveDownOne(self.Deck.GetIndexByValue(53))\r
+\r
+ self.Deck.MoveDownTwo(self.Deck.GetIndexByValue(54))\r
+\r
+ if (self.Deck.GetIndexByValue(53) > self.Deck.GetIndexByValue(54)):\r
+ self.Deck.TripleCut(self.Deck.GetIndexByValue(54), self.Deck.GetIndexByValue(53))\r
+ else:\r
+ self.Deck.TripleCut(self.Deck.GetIndexByValue(53), self.Deck.GetIndexByValue(54))\r
+\r
+ CutBy = self.Deck.Cards[53]\r
+ if (CutBy == 54):\r
+ CutBy = 53\r
+ self.Deck.CountCutByIndex(CutBy)\r
+ \r
+ TopCard = self.Deck.Cards[0]\r
+ if (TopCard == 54):\r
+ TopCard = 53\r
+ return (self.Deck.Cards[TopCard])\r
+\r
+ def KeyDeck(self, s):\r
+ from string import upper, letters\r
+ k = ""\r
+ for i in range(0, len(s)):\r
+ if s[i] in letters:\r
+ k = k + s[i]\r
+ k = upper(k)\r
+ for i in range(0, len(s)):\r
+ self.GetNextOutput()\r
+ # New Step Five\r
+ self.Deck.CountCutByIndex(ord(k[i]) - 64)\r
+ \r
+ def Encrypt(self, s):\r
+ from string import upper, letters\r
+ c = ""\r
+ p = ""\r
+ for i in range(0, len(s)):\r
+ if s[i] in letters:\r
+ p = p + s[i]\r
+ p = upper(p)\r
+ if not ((len(p) % 5) == 0):\r
+ p = p + ('X' * (5 - (len(p) % 5)))\r
+ for j in range(0, len(p)):\r
+ while(1):\r
+ output = self.GetNextOutput()\r
+ if ((output == 53) or (output == 54)):\r
+ continue\r
+ else:\r
+ break\r
+ if (output > 26):\r
+ output = output - 26\r
+ k = (ord(p[j]) - 65) + output\r
+ if (k > 25):\r
+ k = k - 26\r
+ k = k + 65\r
+ c = c + chr(k) \r
+ return c\r
+ \r
+ def Decrypt(self, s):\r
+ from string import upper, letters\r
+ c = ""\r
+ p = ""\r
+ for i in range(0, len(s)):\r
+ if s[i] in letters:\r
+ c = c + s[i]\r
+ c = upper(c)\r
+ \r
+ for j in range(0, len(c)):\r
+ while(1):\r
+ output = self.GetNextOutput()\r
+ if ((output == 53) or (output == 54)):\r
+ continue\r
+ else:\r
+ break\r
+ \r
+ if (output > 26):\r
+ output = output - 26\r
+ k = ord(c[j]) - output\r
+ if (k < 65):\r
+ k = k + 26\r
+ p = p + chr(k) \r
+ \r
+ return p\r
+\r
+def PrintInFives(s):\r
+ ns = ""\r
+ for i in range(0,len(s)):\r
+ ns = ns + s[i]\r
+ if (len(ns) == 5):\r
+ print ns,\r
+ ns = ""\r
+ print ns\r
+\r
+def test():\r
+ print "Running Test Vectors"\r
+ print "--------------------"\r
+ # (plaintext, key, ciphertext)\r
+ vectors = [\r
+ ("AAAAAAAAAAAAAAA", "", "EXKYIZSGEHUNTIQ"),\r
+ ("AAAAAAAAAAAAAAA", "f", "XYIUQBMHKKJBEGY"),\r
+ ("AAAAAAAAAAAAAAA", "foo", "ITHZUJIWGRFARMW"),\r
+ ("AAAAAAAAAAAAAAA", "a", "XODALGSCULIQNSC"),\r
+ ("AAAAAAAAAAAAAAA", "aa", "OHGWMXXCAIMCIQP"),\r
+ ("AAAAAAAAAAAAAAA", "aaa", "DCSQYHBQZNGDRUT"),\r
+ ("AAAAAAAAAAAAAAA", "b", "XQEEMOITLZVDSQS"),\r
+ ("AAAAAAAAAAAAAAA", "bc", "QNGRKQIHCLGWSCE"),\r
+ ("AAAAAAAAAAAAAAA", "bcd", "FMUBYBMAXHNQXCJ"),\r
+ ("AAAAAAAAAAAAAAAAAAAAAAAAA", "cryptonomicon", "SUGSRSXSWQRMXOHIPBFPXARYQ"),\r
+ ("SOLITAIRE","cryptonomicon","KIRAKSFJAN")\r
+ ]\r
+ for i in vectors:\r
+ s = SolitaireCipher()\r
+ s.KeyDeck(i[1])\r
+ ciphertext = s.Encrypt(i[0])\r
+ if (ciphertext == i[2]):\r
+ print "passed!"\r
+ else:\r
+ print "FAILED!"\r
+ print "plaintext = " + i[0]\r
+ print "key = " + i[1]\r
+ print "expected ciphertext =",\r
+ PrintInFives(i[2])\r
+ print "got ciphertext =",\r
+ PrintInFives(ciphertext)\r
+ print\r
+ print "Test bijectivity (i.e. make sure that D(E(m)) == m"\r
+ print "--------------------------------------------------"\r
+ from whrandom import choice, randint\r
+ from string import uppercase\r
+ for i in range(0,5):\r
+ p = ""\r
+ for i in range(0,randint(10,25)):\r
+ p = p + choice(uppercase)\r
+ s = SolitaireCipher()\r
+ s.KeyDeck("SECRETKEY")\r
+ c = s.Encrypt(p)\r
+ s = SolitaireCipher()\r
+ s.KeyDeck("SECRETKEY")\r
+ r = s.Decrypt(c)\r
+ if (r[:len(p)] == p):\r
+ print "passed!"\r
+ else:\r
+ print "FAILED!"\r
+ print "Random plaintext =",\r
+ PrintInFives(p)\r
+ print "ciphertext =",\r
+ PrintInFives(c)\r
+ print "decrypt =",\r
+ PrintInFives(r[:len(p)])\r
+ \r
+ print\r
+ \r
+ \r
+\r
+\r
+\r
+if (__name__ == "__main__"):\r
+ from sys import argv\r
+ from string import upper\r
+ usage = "Usage:\nsolitaire.py [-test] | [-e,-d] key filename\n"\r
+ \r
+ \r
+ if (len(argv) < 2):\r
+ print usage\r
+ elif ("-TEST" in map(upper,argv)):\r
+ test()\r
+ elif (upper(argv[1]) == "-E"):\r
+ FileToEncrypt = open(argv[3])\r
+ Plaintext = FileToEncrypt.read()\r
+ s = SolitaireCipher()\r
+ s.KeyDeck(argv[2])\r
+ Ciphertext = s.Encrypt(Plaintext)\r
+ PrintInFives(Ciphertext)\r
+ elif (upper(argv[1]) == "-D"):\r
+ FileToDecrypt = open(argv[3])\r
+ Ciphertext = FileToDecrypt.read()\r
+ s = SolitaireCipher()\r
+ s.KeyDeck(argv[2])\r
+ Plaintext = s.Decrypt(Ciphertext)\r
+ PrintInFives(Plaintext)\r
+ else:\r
+ print usage\r