Welcome, guest | Sign In | My Account | Store | Cart

Some flexible substitution cryptogram encryption/decryption tools and a cipher generator.

Python, 181 lines
  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/