1 #!/usr/local/bin/python
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
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.
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
36 # Sets up the Deck in Bridge format
42 def MoveDownOne(self
, index
):
44 temp
= self
.Cards
[index
]
45 self
.Cards
[index
] = self
.Cards
[0]
48 temp
= self
.Cards
[index
]
49 self
.Cards
[index
] = self
.Cards
[index
+ 1]
50 self
.Cards
[index
+ 1] = temp
52 def MoveDownTwo(self
, index
):
54 self
.Cards
.insert(2, self
.Cards
[index
])
55 del self
.Cards
[index
+ 1]
57 self
.Cards
.insert(1, self
.Cards
[index
])
58 del self
.Cards
[index
+ 1]
60 self
.Cards
.insert((index
+ 3), self
.Cards
[index
])
63 def GetValueByIndex(self
, index
):
64 return self
.Cards
[index
]
66 def GetIndexByValue(self
, value
):
68 if (self
.Cards
[i
] == value
):
72 def TripleCut(self
, low
, high
):
73 self
.Cards
= self
.Cards
[high
+ 1:] + self
.Cards
[low
:high
+ 1] + self
.Cards
[:low
]
75 def CountCutByIndex(self
, index
):
77 self
.Cards
= self
.Cards
[CutBy
:-1] + self
.Cards
[:CutBy
] + [self
.Cards
[53]]
81 class SolitaireCipher
:
85 def GetNextOutput(self
):
86 self
.Deck
.MoveDownOne(self
.Deck
.GetIndexByValue(53))
88 self
.Deck
.MoveDownTwo(self
.Deck
.GetIndexByValue(54))
90 if (self
.Deck
.GetIndexByValue(53) > self
.Deck
.GetIndexByValue(54)):
91 self
.Deck
.TripleCut(self
.Deck
.GetIndexByValue(54), self
.Deck
.GetIndexByValue(53))
93 self
.Deck
.TripleCut(self
.Deck
.GetIndexByValue(53), self
.Deck
.GetIndexByValue(54))
95 CutBy
= self
.Deck
.Cards
[53]
98 self
.Deck
.CountCutByIndex(CutBy
)
100 TopCard
= self
.Deck
.Cards
[0]
103 return (self
.Deck
.Cards
[TopCard
])
105 def KeyDeck(self
, s
):
106 from string
import upper
, letters
108 for i
in range(0, len(s
)):
112 for i
in range(0, len(s
)):
115 self
.Deck
.CountCutByIndex(ord(k
[i
]) - 64)
117 def Encrypt(self
, s
):
118 from string
import upper
, letters
121 for i
in range(0, len(s
)):
125 if not ((len(p
) % 5) == 0):
126 p
= p
+ ('X' * (5 - (len(p
) % 5)))
127 for j
in range(0, len(p
)):
129 output
= self
.GetNextOutput()
130 if ((output
== 53) or (output
== 54)):
136 k
= (ord(p
[j
]) - 65) + output
143 def Decrypt(self
, s
):
144 from string
import upper
, letters
147 for i
in range(0, len(s
)):
152 for j
in range(0, len(c
)):
154 output
= self
.GetNextOutput()
155 if ((output
== 53) or (output
== 54)):
162 k
= ord(c
[j
]) - output
171 for i
in range(0,len(s
)):
179 print "Running Test Vectors"
180 print "--------------------"
181 # (plaintext, key, ciphertext)
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")
196 s
= SolitaireCipher()
198 ciphertext
= s
.Encrypt(i
[0])
199 if (ciphertext
== i
[2]):
203 print "plaintext = " + i
[0]
204 print "key = " + i
[1]
205 print "expected ciphertext =",
207 print "got ciphertext =",
208 PrintInFives(ciphertext
)
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
216 for i
in range(0,randint(10,25)):
217 p
= p
+ choice(uppercase
)
218 s
= SolitaireCipher()
219 s
.KeyDeck("SECRETKEY")
221 s
= SolitaireCipher()
222 s
.KeyDeck("SECRETKEY")
224 if (r
[:len(p
)] == p
):
228 print "Random plaintext =",
230 print "ciphertext =",
233 PrintInFives(r
[:len(p
)])
241 if (__name__
== "__main__"):
243 from string
import upper
244 usage
= "Usage:\nsolitaire.py [-test] | [-e,-d] key filename\n"
249 elif ("-TEST" in map(upper
,argv
)):
251 elif (upper(argv
[1]) == "-E"):
252 FileToEncrypt
= open(argv
[3])
253 Plaintext
= FileToEncrypt
.read()
254 s
= SolitaireCipher()
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()
263 Plaintext
= s
.Decrypt(Ciphertext
)
264 PrintInFives(Plaintext
)