Some flexible substitution cryptogram encryption/decryption tools and a cipher generator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | from random import seed, shuffle
from string import ascii_lowercase as alphabet
from string import ascii_uppercase as capitals
from string import maketrans as mt
from sys import argv, exit
""" some cypherin' tricks
"""
long_message = """
anztheX yhnC -
"?ffbepn frzbp ru jbu bg qrfbccb fn ,gutve fv
ag - rfhnc fergebcre xfn bg obw lz gv g'afV" ,friyrfzrug xfn gfnry gn gutvz lrug gnug - rfhnc fergebcre
rivt qyhbj rparverckrheg ruGfrqabp rug ugvj tabyn frbt tavgebcre fjra uphz lnj rug fv ,uthbug ,tavug qnf lyheg ruGfrqabp rug ugvj tabyn frbt tavgebcre fjra uphz lnj rug fv ,uthbug ,tavug qnf lyheg ruG
.ugebj-syrf sb rfarf evrug ab xpnggn an f'gv
- pvtby pvfno eb fgpns evrug ab ch ajbuf re'lrug aruj rtne ugvj gpnre rycbrc uphf ,arug ,lyongviraV
)".rxvy fqahbf abferc yhsguthbug n gnuj sb nrqv f'abferc qvchgf n rxvy f'rU" :fhug lrzeN xpvQ qrovepfrq
rpab avryX nemR( .genzf qahbf zrug frxnz fvug gnug srvyro rug av zrug gnrcre qan ,frqhgvgnyc rfbug ch
xpvc bg qarg fhbvehpav lyynhgpryyrgav ren gho fpvgvybc ghbon ftavyrrs tabegf rinu buj rycbrC .frqhgvgnyc
rivgnierfabp sb rab lytavzyrujerib fv rfvba qahbetxpno rug ,rehgyhp ynpvgvybc gareehp ehb av :gv rrf V
jbu f'rerU .uphz bf gba ,rZ .lgvehprfav ynhgpryyrgav f'gutve rug lo qrymmhc syrfzvu frffrsbec gvnuP
?gba luJ .ba ghO .lqnynz fvug sb rfehbpfvq ehb qrehp rinu qyhbj - sshgf
tavjbax lyynhgpn ebs qrufvahc fnj rebT yN rfhnpro lyrfvprec rfhbU rgvuJ rug sb rpangfvq qnup-tavtanu
avugvj gbt buj - ufhO .J rtebrT ugvj rparverckr f'abvgna rug gnug qrcbu ,gvzqn bg rinu V ,qnu V
.tavqarpfrqabp qan gantbeen fn ffbepn rznp ru ,rrf hbl - rtanupkr rug gfby rinu bg qrerqvfabp fv O abferC qaN
".fgpns rug ren reru - rgvuj g'afv xpnyo ,bA" ,flnf O abferC .rgnpfhsob bg gebssr rgnerovyrq n sb ghb argsb
rebz uthbugyn ,rpanebatv sb ghb fcnuerc - "rgvuj fv xpnyO" flnf N abferC :fvug rxvy frbt gV
.ghbon tavxyng re'lrug gnuj tavjbax lyynhgpn ebs qrufvahc ren frehtvs pvyohC
""".encode("rot13")[-1:0:-1] # somewhat obscured in case you actually want to solve it after you read the code
# print long_message # uncomment to cheat
def make_cypher(Seed=None):
""" returns a shuffled alphabet
return is random if Seed not provided
return is repeatable function of Seed if provided
"""
if Seed:
seed(Seed)
L = (list(alphabet))
shuffle(L)
return "".join(L)
def encrypt_factory(cypher = None,Seed = None):
""" creates an encryptor and a decryptor function from a cypher, or a seed, or randomly
"""
if not cypher:
cypher = make_cypher(Seed)
trans = mt(alphabet,cypher)
untrans = mt(cypher,alphabet)
def encryptor(message):
return message.lower().translate(trans)
def decryptor(message):
return message.lower().translate(untrans)
return encryptor, decryptor
def strip_cases(mixed_message):
""" returns a lowercase version of input and a corrsponding list of booleans for case changes
note: ASCII
"""
return mixed_message.lower(), [ch in capitals for ch in mixed_message]
def restore_cases(lower_message,cases):
""" applies a list of case changes to a message; assumes input message all lowercase
note: ASCII
"""
assert len(lower_message) == len(cases)
return "".join([case and ch.upper() or ch for (ch,case) in zip(lower_message,cases)])
if __name__ == "__main__":
"""
#tests
message = "Python rocks"
#stripping, restoring cases
message_l,cases = strip_cases(message)
assert message == restore_cases(message_l,cases)
# basic usage (random encryptor)
encryptor,decryptor = encrypt_factory()
assert decryptor(encryptor(message)) == message.lower()
# prespecified encryptor
rot13 = alphabet[13:] + alphabet[:13]
e2,d2 = encrypt_factory(rot13) # rot13 repeated is a null operation
assert e2(e2(message)) == message.lower()
# repeatability, also demonstrates the relation between the two-step and one-step approach
e3,d3 = encrypt_factory(Seed=1234)
cypher = make_cypher(1234)
e4,d4 = encrypt_factory(cypher) # these will be the same because the random seed is set
assert d4(e3(message)) == message.lower() # so the decryption works
# tests pass!
"""
# play the game
# You can provide text from a file; alter this ad lib
# note: apply .encode("rot13")[-1:0:-1] to a string to obscure it;
# apply again to unobscure it
if len(argv) > 1:
long_message = file(argv[1]).read().encode("rot13")[-1:0:-1]
# random encrypt/decrypt pair
E,D = encrypt_factory(Seed = seed())
# save uppercase info as a list of booleans
long_l,cases2 = strip_cases(long_message)
# encode
cyphertext = E(long_message)
# display coded message
print "\n\n"+80*"="
print restore_cases(cyphertext,cases2)
print 80*"="
# collect up the coded symbols in the message
cypherbet = "".join([ch for ch in alphabet if ch in cyphertext])
# wait for the user to provide an answer
print "enter the decyphered alphabet below the letters of the code"
# print D(" ".join(cypherbet)) # uncomment to cheat
answer = None
try:
while not answer:
answer = raw_input(" ".join(cypherbet)+"\n")
if not answer:
print "Please enter some text or ^C to exit"
else:
decypher = "".join([ch for ch in answer if ch.isalpha()])
if len(decypher) != len(cypherbet):
print "\nPlease enter the correct number of characters."
answer = ""
except KeyboardInterrupt:
print "Thanks for trying!"
exit()
# test answer
decypher_table = mt(cypherbet,decypher)
print 80*"="
try:
assert cyphertext.translate(decypher_table) == long_message.lower()
except:
print "\n\nSorry. It should have been:\n%s\n%s" % (" ".join(cypherbet)," ".join(D(cypherbet)))
else:
print "\n\nYes!\n"
|
It's convenient in making a cryptogram game to extend maketrans to create a decoder. I do this with a factory function that creates an encoder/decoder pair. Maybe this is a good example of a simple function that returns functions.
Inspired by http://www.stealthcopter.com/blog/2010/04/how-to-create-a-cryptogram-in-python-random-substitution-cipher/