Challenge 8a done, 8b not attempted
[cipher-tools.git] / 2015 / Solitaire.py
diff --git a/2015/Solitaire.py b/2015/Solitaire.py
new file mode 100644 (file)
index 0000000..dfa1c5b
--- /dev/null
@@ -0,0 +1,266 @@
+#!/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