```"""
Clubhouse crypto:  simple substitution, easily cracked
(but useful for learning purposes).

Permutation class S appended to facilitate group theory
approach to permutations as per this thread archived
at the Math Forum:

http://www.mathforum.com/epigone/mathedu/swexprundwand
"""
import string, random

def permute(pool):
"""
Randomly permute whatever list is passed in as pool
"""
oldlist = []+list(pool)
newlist = []
for i in range(len(oldlist)): # = range(26)

randitem = random.choice(oldlist)
oldlist.remove(randitem)
newlist.append(randitem)
return newlist

def mkdict(n=0):
"""
Pair uppercase alphabet or integers 1...n with
randomly permuted version of same
"""
if n>0:  pool = range(1,n+1)
else:    pool = string.uppercase
tuples = zip(pool,permute(pool))
codedict = {}
for pair in tuples:
codedict[pair[0]]=pair[1]
return codedict

def mkkey(codedict):
"""
Return key as a list of cyclic tuples, e.g.
[('A','R','C'),('D','Q')...]
"""
dictkeys = codedict.keys()
result = []
for i in dictkeys[:]:  # using copy of dictkeys

newtuple = ()
if i in dictkeys and not codedict[i]==i:
initval,nextval = i,i
while 1:
newtuple += (nextval,)
dictkeys.remove(nextval)
nextval = codedict[nextval]
if nextval==initval:  # cycle complete

break
result.append(newtuple)
return result

def gendict(keytuples):
"""
generate a dictionary from a list of cyclic tuples
"""
result = {} # return a dictionary

for cycle in keytuples:
rot1 = (cycle+(cycle[0],))[1:]
for i in range(len(cycle)):
result[cycle[i]]=rot1[i]
return result

def revkey(keytuples):
"""
Reverse all the cyclic tuples in a list of tuples
"""
result = []
for i in keytuples:
temp = list(i)
temp.reverse()
result.append(tuple(temp))
return result

def encrypt(filename):
"""
Use a permuted alphabet to encrypte a .txt file --
saving the key and file separately.  Save file
as filename.cpt, the codekey as filename.key
"""
encfile = filename[:string.rfind(filename,".")]+".cpt"
keyfile = filename[:string.rfind(filename,".")]+".key"

print "Writing to " + encfile
print "Saving key as " + keyfile

txtfileobj = open(filename,'r')
encfileobj = open(encfile,'w')
keyfileobj = open(keyfile,'w')

thedict = mkdict()

for line in txtfileobj.readlines():
uline = string.upper(line)
encline = ""
for char in uline:
if char in string.uppercase:
encline = encline + thedict[char]
else:
encline = encline + char
encfileobj.write(encline)

keyfileobj.write(str(mkkey(thedict)) + "\n")

txtfileobj.close()
encfileobj.close()
keyfileobj.close()

def decrypt(filename):
"""
Use a codekey to decrypt a file (see encrypt)
"""

txtfile  = filename[:string.rfind(filename,".")]+".dcp"
keyfile  = filename[:string.rfind(filename,".")]+".key"

print "Reading from " + filename
print "Writing to " + txtfile
print "Using key " + keyfile
print ""

encfileobj = open(filename,'r')
txtfileobj = open(txtfile,'w')
keyfileobj = open(keyfile,'r')

thekey  = revkey(temp)    # reverse the key tuples

thedict = gendict(thekey) # generate reverse lookup dictionary

for line in encfileobj.readlines():
dcpline = ""
for char in line:
if char in thedict.keys():
dcpline = dcpline + thedict[char]
else:
dcpline = dcpline + char
print dcpline[:-1]
txtfileobj.write(dcpline)

encfileobj.close()
txtfileobj.close()
keyfileobj.close()

def gcd(a,b):
"""Return greatest common divisor using Euclid's Algorithm."""
while b:
a, b = b, a % b
return a

def lcm(a,b):
"""
Return lowest common multiple."""
return (a*b)/gcd(a,b)

def LCM(terms):
"Return lcm of a list of numbers."
return reduce(lambda a,b: lcm(a,b), terms)

class S:
"""
Defines a permuation and operations therewith
"""

def __init__(self,n=0,dict={}):
# specify n>0 for permutation of integers 1...n

# specify dict={...} to initialize w/ specific dictionary

# p1 = S() defaults to permutation of uppercase A-Z

if len(dict.keys())>0:
self.dict = dict
elif n==0:
self.dict = mkdict()
else:
self.dict = mkdict(n)

def __mul__(self,other):
newdict = {}
for i in other.dict.keys():
newdict[i] = self.dict[other.dict[i]]
return S(dict=newdict)

def __repr__(self):
return str(mkkey(self.dict))

def __pow__(self,n):
r = S(dict=self.dict)
if n==0:  # return identity

return r*r.inv()
elif abs(n)>1:
for i in range(1,abs(n)):
r *= S(dict=self.dict)
if n<0:
r = r.inv()
return r

def __eq__(self,other):  # new syntax ver. 2.1

rtn = 1
for i in self.dict.keys():
if self.dict[i] != other.dict[i]:
rtn = 0
break
return rtn

def len(self):
return len(self.dict)

def inv(self):
newdict = {}
for i in self.dict.keys():
newdict[self.dict[i]] = i
return S(dict=newdict)

def order(self):
tuples = mkkey(self.dict)
return LCM(map(len,tuples))
```
`# code highlighted using py2html.py version 0.8`