4 Python implementation of Bruce Schneier's Solitaire Encryption
5 Algorithm (http://www.counterpane.com/solitaire.html).
7 John Dell'Aquila <jbd@alum.mit.edu>
14 Convert letter to number: Aa->1, Bb->2, ..., Zz->26.
15 Non-letters are treated as X's.
17 if c
in string
.letters
:
18 return ord(string
.upper(c
)) - 64
23 Convert number to letter: 1->A, 2->B, ..., 26->Z,
24 27->A, 28->B, ... ad infitum
26 return chr((n
-1)%26+65)
30 """ Solitaire Encryption Algorithm
31 http://www.counterpane.com/solitaire.html
34 def _setKey(self
, passphrase
):
36 Order deck according to passphrase.
38 self
.deck
= range(1,55)
40 # 1, 2,...,13 are A,2,...,K of clubs
41 # 14,15,...,26 are A,2,...,K of diamonds
42 # 27,28,...,39 are A,2,...,K of hearts
43 # 40,41,...,52 are A,2,...,K of spades
44 # 53 & 54 are the A & B jokers
47 self
._countCut
(toNumber(c
))
49 def _down1(self
, card
):
51 Move designated card down 1 position, treating
56 if n
< 53: # not last card - swap with successor
57 d
[n
], d
[n
+1] = d
[n
+1], d
[n
]
58 else: # last card - move below first card
59 d
[1:] = d
[-1:] + d
[1:-1]
63 Swap cards above first joker with cards below
67 a
, b
= d
.index(53), d
.index(54)
70 d
[:] = d
[b
+1:] + d
[a
:b
+1] + d
[:a
]
72 def _countCut(self
, n
):
74 Cut after the n-th card, leaving the bottom
78 n
= min(n
, 53) # either joker is 53
79 d
[:-1] = d
[n
:-1] + d
[:n
]
83 Perform one round of keystream generation.
85 self
._down
1(53) # move A joker down 1
86 self
._down
1(54) # move B joker down 2
89 self
._countCut
(self
.deck
[-1])
93 Return next output card.
98 topCard
= min(d
[0], 53) # either joker is 53
99 if d
[topCard
] < 53: # don't return a joker
102 def encrypt(self
, txt
, key
):
104 Return 'txt' encrypted using 'key'.
107 # pad with X's to multiple of 5
108 txt
= txt
+ 'X' * ((5-len(txt
))%5)
109 cipher
= [None] * len(txt
)
110 for n
in range(len(txt
)):
111 cipher
[n
] = toChar(toNumber(txt
[n
]) + self
._output
())
112 # add spaces to make 5 letter blocks
113 for n
in range(len(cipher
)-5, 4, -5):
115 return string
.join(cipher
, '')
117 def decrypt(self
, cipher
, key
):
119 Return 'cipher' decrypted using 'key'.
122 # remove white space between code blocks
123 cipher
= string
.join(string
.split(cipher
), '')
124 txt
= [None] * len(cipher
)
125 for n
in range(len(cipher
)):
126 txt
[n
] = toChar(toNumber(cipher
[n
]) - self
._output
())
127 return string
.join(txt
, '')
129 testCases
= ( # test vectors from Schneier paper
130 ('AAAAAAAAAAAAAAA', '', 'EXKYI ZSGEH UNTIQ'),
131 ('AAAAAAAAAAAAAAA', 'f', 'XYIUQ BMHKK JBEGY'),
132 ('AAAAAAAAAAAAAAA', 'fo', 'TUJYM BERLG XNDIW'),
133 ('AAAAAAAAAAAAAAA', 'foo', 'ITHZU JIWGR FARMW'),
134 ('AAAAAAAAAAAAAAA', 'a', 'XODAL GSCUL IQNSC'),
135 ('AAAAAAAAAAAAAAA', 'aa', 'OHGWM XXCAI MCIQP'),
136 ('AAAAAAAAAAAAAAA', 'aaa', 'DCSQY HBQZN GDRUT'),
137 ('AAAAAAAAAAAAAAA', 'b', 'XQEEM OITLZ VDSQS'),
138 ('AAAAAAAAAAAAAAA', 'bc', 'QNGRK QIHCL GWSCE'),
139 ('AAAAAAAAAAAAAAA', 'bcd', 'FMUBY BMAXH NQXCJ'),
140 ('AAAAAAAAAAAAAAAAAAAAAAAAA', 'cryptonomicon',
141 'SUGSR SXSWQ RMXOH IPBFP XARYQ'),
142 ('SOLITAIRE','cryptonomicon','KIRAK SFJAN')
147 sol.py {-e | -d} _key_ < _file_
150 N.B. WinNT requires "python sol.py ..."
151 for input redirection to work (NT bug).
155 if __name__
== '__main__':
159 elif args
[1] == '-test':
161 for txt
, key
, cipher
in testCases
:
162 coded
= s
.encrypt(txt
, key
)
163 assert cipher
== coded
164 decoded
= s
.decrypt(coded
, key
)
165 assert decoded
[:len(txt
)] == string
.upper(txt
)
166 print 'All tests passed.'
169 elif args
[1] == '-e':
170 print Solitaire().encrypt(sys
.stdin
.read(), args
[2])
171 elif args
[1] == '-d':
172 print Solitaire().decrypt(sys
.stdin
.read(), args
[2])