Updated for challenge 9
[cipher-tools.git] / 2015 / Solitaire.py
1 #!/usr/local/bin/python
2
3 # Solitaire.py
4 # Copyright (C) 1999 Mordy Ovits <movits@lockstar.com>
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License as
7 # published by the Free Software Foundation; either version 2 of the
8 # License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program (see License.txt); if not, write to the Free
15 # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
16 # 02111-1307 USA
17 # NOTE: the Solitaire encryption algorithm is strong cryptography.
18 # That means the security it affords is based on the secrecy of the
19 # key rather than secrecy of the algorithm itself. That also means
20 # that this program and programs derived from it may be treated as
21 # a munition for the purpose of export regulation in the United States
22 # and other countries. You are encouraged to seek competent legal
23 # counsel before distributing copies of this program.
24 #
25 # Solitaire.py is an implementation of the Solitaire encryption
26 # algorithm, as designed by Bruce Schneier and described at:
27 # http://www.counterpane.com/solitaire.html
28 # as well as in Neil Stephenson's novel Cryptonomicon.
29 # Solitaire is a cryptographically strong stream cipher that can
30 # be implemented using a deck of playing cards. This implementation is
31 # designed to be extremely clean, and a good aid to someone looking to see
32 # how Solitaire works
33
34
35 class Deck:
36 # Sets up the Deck in Bridge format
37 def __init__(self):
38 self.Cards = []
39 for i in range(1,55):
40 self.Cards.append(i)
41
42 def MoveDownOne(self, index):
43 if (index == 53):
44 temp = self.Cards[index]
45 self.Cards[index] = self.Cards[0]
46 self.Cards[0] = temp
47 else:
48 temp = self.Cards[index]
49 self.Cards[index] = self.Cards[index + 1]
50 self.Cards[index + 1] = temp
51
52 def MoveDownTwo(self, index):
53 if (index == 53):
54 self.Cards.insert(2, self.Cards[index])
55 del self.Cards[index + 1]
56 elif (index == 52):
57 self.Cards.insert(1, self.Cards[index])
58 del self.Cards[index + 1]
59 else:
60 self.Cards.insert((index + 3), self.Cards[index])
61 del self.Cards[index]
62
63 def GetValueByIndex(self, index):
64 return self.Cards[index]
65
66 def GetIndexByValue(self, value):
67 for i in range(0,54):
68 if (self.Cards[i] == value):
69 return i
70 return -1
71
72 def TripleCut(self, low, high):
73 self.Cards = self.Cards[high + 1:] + self.Cards[low:high + 1] + self.Cards[:low]
74
75 def CountCutByIndex(self, index):
76 CutBy = index
77 self.Cards = self.Cards[CutBy:-1] + self.Cards[:CutBy] + [self.Cards[53]]
78
79
80
81 class SolitaireCipher:
82 def __init__(self):
83 self.Deck = Deck()
84
85 def GetNextOutput(self):
86 self.Deck.MoveDownOne(self.Deck.GetIndexByValue(53))
87
88 self.Deck.MoveDownTwo(self.Deck.GetIndexByValue(54))
89
90 if (self.Deck.GetIndexByValue(53) > self.Deck.GetIndexByValue(54)):
91 self.Deck.TripleCut(self.Deck.GetIndexByValue(54), self.Deck.GetIndexByValue(53))
92 else:
93 self.Deck.TripleCut(self.Deck.GetIndexByValue(53), self.Deck.GetIndexByValue(54))
94
95 CutBy = self.Deck.Cards[53]
96 if (CutBy == 54):
97 CutBy = 53
98 self.Deck.CountCutByIndex(CutBy)
99
100 TopCard = self.Deck.Cards[0]
101 if (TopCard == 54):
102 TopCard = 53
103 return (self.Deck.Cards[TopCard])
104
105 def KeyDeck(self, s):
106 from string import upper, letters
107 k = ""
108 for i in range(0, len(s)):
109 if s[i] in letters:
110 k = k + s[i]
111 k = upper(k)
112 for i in range(0, len(s)):
113 self.GetNextOutput()
114 # New Step Five
115 self.Deck.CountCutByIndex(ord(k[i]) - 64)
116
117 def Encrypt(self, s):
118 from string import upper, letters
119 c = ""
120 p = ""
121 for i in range(0, len(s)):
122 if s[i] in letters:
123 p = p + s[i]
124 p = upper(p)
125 if not ((len(p) % 5) == 0):
126 p = p + ('X' * (5 - (len(p) % 5)))
127 for j in range(0, len(p)):
128 while(1):
129 output = self.GetNextOutput()
130 if ((output == 53) or (output == 54)):
131 continue
132 else:
133 break
134 if (output > 26):
135 output = output - 26
136 k = (ord(p[j]) - 65) + output
137 if (k > 25):
138 k = k - 26
139 k = k + 65
140 c = c + chr(k)
141 return c
142
143 def Decrypt(self, s):
144 from string import upper, letters
145 c = ""
146 p = ""
147 for i in range(0, len(s)):
148 if s[i] in letters:
149 c = c + s[i]
150 c = upper(c)
151
152 for j in range(0, len(c)):
153 while(1):
154 output = self.GetNextOutput()
155 if ((output == 53) or (output == 54)):
156 continue
157 else:
158 break
159
160 if (output > 26):
161 output = output - 26
162 k = ord(c[j]) - output
163 if (k < 65):
164 k = k + 26
165 p = p + chr(k)
166
167 return p
168
169 def PrintInFives(s):
170 ns = ""
171 for i in range(0,len(s)):
172 ns = ns + s[i]
173 if (len(ns) == 5):
174 print ns,
175 ns = ""
176 print ns
177
178 def test():
179 print "Running Test Vectors"
180 print "--------------------"
181 # (plaintext, key, ciphertext)
182 vectors = [
183 ("AAAAAAAAAAAAAAA", "", "EXKYIZSGEHUNTIQ"),
184 ("AAAAAAAAAAAAAAA", "f", "XYIUQBMHKKJBEGY"),
185 ("AAAAAAAAAAAAAAA", "foo", "ITHZUJIWGRFARMW"),
186 ("AAAAAAAAAAAAAAA", "a", "XODALGSCULIQNSC"),
187 ("AAAAAAAAAAAAAAA", "aa", "OHGWMXXCAIMCIQP"),
188 ("AAAAAAAAAAAAAAA", "aaa", "DCSQYHBQZNGDRUT"),
189 ("AAAAAAAAAAAAAAA", "b", "XQEEMOITLZVDSQS"),
190 ("AAAAAAAAAAAAAAA", "bc", "QNGRKQIHCLGWSCE"),
191 ("AAAAAAAAAAAAAAA", "bcd", "FMUBYBMAXHNQXCJ"),
192 ("AAAAAAAAAAAAAAAAAAAAAAAAA", "cryptonomicon", "SUGSRSXSWQRMXOHIPBFPXARYQ"),
193 ("SOLITAIRE","cryptonomicon","KIRAKSFJAN")
194 ]
195 for i in vectors:
196 s = SolitaireCipher()
197 s.KeyDeck(i[1])
198 ciphertext = s.Encrypt(i[0])
199 if (ciphertext == i[2]):
200 print "passed!"
201 else:
202 print "FAILED!"
203 print "plaintext = " + i[0]
204 print "key = " + i[1]
205 print "expected ciphertext =",
206 PrintInFives(i[2])
207 print "got ciphertext =",
208 PrintInFives(ciphertext)
209 print
210 print "Test bijectivity (i.e. make sure that D(E(m)) == m"
211 print "--------------------------------------------------"
212 from whrandom import choice, randint
213 from string import uppercase
214 for i in range(0,5):
215 p = ""
216 for i in range(0,randint(10,25)):
217 p = p + choice(uppercase)
218 s = SolitaireCipher()
219 s.KeyDeck("SECRETKEY")
220 c = s.Encrypt(p)
221 s = SolitaireCipher()
222 s.KeyDeck("SECRETKEY")
223 r = s.Decrypt(c)
224 if (r[:len(p)] == p):
225 print "passed!"
226 else:
227 print "FAILED!"
228 print "Random plaintext =",
229 PrintInFives(p)
230 print "ciphertext =",
231 PrintInFives(c)
232 print "decrypt =",
233 PrintInFives(r[:len(p)])
234
235 print
236
237
238
239
240
241 if (__name__ == "__main__"):
242 from sys import argv
243 from string import upper
244 usage = "Usage:\nsolitaire.py [-test] | [-e,-d] key filename\n"
245
246
247 if (len(argv) < 2):
248 print usage
249 elif ("-TEST" in map(upper,argv)):
250 test()
251 elif (upper(argv[1]) == "-E"):
252 FileToEncrypt = open(argv[3])
253 Plaintext = FileToEncrypt.read()
254 s = SolitaireCipher()
255 s.KeyDeck(argv[2])
256 Ciphertext = s.Encrypt(Plaintext)
257 PrintInFives(Ciphertext)
258 elif (upper(argv[1]) == "-D"):
259 FileToDecrypt = open(argv[3])
260 Ciphertext = FileToDecrypt.read()
261 s = SolitaireCipher()
262 s.KeyDeck(argv[2])
263 Plaintext = s.Decrypt(Ciphertext)
264 PrintInFives(Plaintext)
265 else:
266 print usage