PROJECT 7

Cryptopy

The muscles you used in the 1337 sp34k3r project get flexed in this project, too. You encrypt and decrypt messages using Python and a little something called a Caesar Cipher.

image

Julius Caesar was a Roman general. Ciphers are what real-life spies use to make and break codes. A cipher is a procedure or a device that changes readable text (called plaintext) into unreadable text (called ciphertext). Caesar encrypted, or coded, his messages by shifting letters along the alphabet. If he had a plaintext d, he would replace it with an a. For an e he would write b, and so on.

In this project you

  • Take a plaintext message.
  • Encrypt the message.
  • Present the encrypted message.
  • Decrypt a ciphertext message into plaintext.
  • Present the plaintext.

Your cipher is going to handle uppercase and lowercase letters, plus punctuation marks and numbers. Caesar got off easy. He didn’t have to worry about punctuation or uppercase letters.

Slice Off Those Dud Characters

None of the characters from onwards are needed for the encryption regime. For example, if you’re encrypting a new line you probably want to keep it as a new line, rather than change it to a letter. Conversely, if your ciphertext contains a new line you probably don’t think that’s significant to the meaning of the plaintext. Everything else in string.printable is fine except for that slice of slashes at the end.

How do you create a string that has everything except that slice? Python has an operator [:] (square brackets and a colon) that slices up strings or lists.

Here is an example:

>>> test_string = '0123456789'

>>> test_string[0:1]

'0'

>>> test_string[1:3]

'12'

>>> # range(10) is a list of the numbers from 0 to 9 inclusive

>>> range(10)[0:1]

[0]

>>> range(10)[1:3]

[1, 2]

>>> test_string[:3]

'012'

>>> test_string[3:]

'3456789'

The slicing operator takes the form string_name[a:b]. (Instead of string_name, type the actual name of the variable to slice.) a is the index (the number that shows how far into the string) of the start of the slice, and b is the index of the end of the slice. In the string '0123456789', each character is also its index. The string '0' is at index 0, the string '3' is at index 3, and so on. In the string 'Hello', the letter 'H' is at index 0, 'e' is at index 1, and so on.

You get the characters starting at character number a up to, but not including, character number b. This might be a little confusing since the characters start at 0. What’s more, the numbers a and b can be negative. If they are, then they are counted from the end of the string.

>>> # everything up to, but not including, the last character

>>> test_string[:-1]

'012345678'

>>> test_string[-1:] # everything *from* the last character

'9'

It’s actually pretty easy to get the printable characters less those strange ones at the end. Just slice them off by taking all the characters up to the fifth from the end. This code slices off the last five characters and saves the reset into a variable char_set holding the set of characters to be encoded:

>>> char_set = string.printable[:-5]

>>> char_set

'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ '

This is the list of characters that you’re going to encode.

tip You can slice a list in the same way as you slice a string, except that, with a list, the things you’re slicing are elements, not characters.

Make a Substitution Table

Did you ever send a coded message to your friend? Have you sent a text that your parents thought sounded like code? (tbh, they don’t use brb or ttyl, do they?)

Assign a character to each character in the plaintext message. Caesar’s cipher substituted (say that six times quickly) the letter a for the letter d. That means shifting the alphabet three characters to the left. How can you do that?

Figure 7-1 shows how the characters are changed. Caesar would go through each letter in his message, find it in the top row, then write the letter below it on the paper he gave to his messenger. If his first plaintext word was BAD, then the ciphertext would be YXA.

image

Figure 7-1: That’s one sick cipher, Caesar.

In this example you’re working with a shift of three, so slice off three characters from the front of char_set and add them to the end. If you want to encrypt with a different shift, choose a different number.

This code creates your substitution characters from the base character set, then prints them out so you can have a look at them:

>>> substitution_chars = char_set[-3:]+char_set[:-3]

>>> substitution_chars

'}~ 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

!"#$%&'()*+,-./:;<=>?@[\]^_`{|'

Then you have your code. (Get it?) The first line shows the letter as it appears in the plaintext of a message. The next line has the ciphertext characters you’ll use to write your message. (Just the first 62 are here.)

>>> print(char_set[:62]+' '+substitution_chars[:62])

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

}~ 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW

To see the full list, open the IDLE Shell window and type print(char_set+' '+substitution_chars).

Set Up Your Cipher

Time now to do the basic stuff:

  1. Create a file called cryptopy.py.
  2. Write a module docstring at the top of the file.
  3. Create an Imports section and import the string module.

    #### Imports Section

    import string

  4. Create a Constants section and use the code to create char_set and substitution_chars. But change their names so that they are ALLCAPS (since they’re supposed to be constants).

    #### Constants Section

    CHAR_SET = string.printable[:-5]

    SUBSTITUTION_CHARS = CHAR_SET[-3:]+CHAR_SET[:-3]

  5. Create a constant called TEST_MESSAGE and store a test message in it. You’ll use it for testing.

    TEST_MESSAGE = "I like Monty Python. They are very funny."

  6. Create a Functions section and in it, create a function stub called encrypt_msg that takes one argument (the plaintext to be encrypted). Include a docstring.

    Project 5 explains how to make a function stub. You can go back there if you need a reminder.

    #### Function Section

    def encrypt_msg(plaintext):

        """Take a plaintext message and encrypt each character using

        a Caesar cipher (d->a). Return the cipher text"""

     

        return plaintext # no encrypting atm

  7. Create a Testing section and call the function stub passing the constant TEST_MESSAGE to it.

    #### Testing Section

    ciphertext = encrypt_msg(TEST_MESSAGE)

You should get something like this:

"""Cryptopy

Take a plaintext message and encrypt it using a Caesar cipher

Brendan Scott, 2015

"""

 

#### Imports Section

import string

 

#### Constants Section

CHAR_SET = string.printable[:-5]

SUBSTITUTION_CHARS = CHAR_SET[-3:]+CHAR_SET[:-3]

 

TEST_MESSAGE = "I like Monty Python. They are very funny."

 

#### Function Section

def encrypt_msg(plaintext):

    """Take a plaintext message and encrypt each character using

    a Caesar cipher (d->a). Return the cipher text"""

    return plaintext # no encrypting atm

 

 

#### Testing Section

ciphertext = encrypt_msg(TEST_MESSAGE)

print(TEST_MESSAGE) # for comparison while testing

print(ciphertext)

Run your program just to see if there are any mistakes. If you do make mistakes, make sure your code looks the same as the code here. Also try the debugging methods you used in Project 6.

Use the Dictionary

You use the replace method in the l33t sp34k3r project. You’re not going to use replace in this case because

  • Here it’s an ugly ad hoc (short-term) solution. The l33t sp34k3r project has a handful of characters to change. Here, you have to swap every character. Aim for beautiful code. You read that right — beautiful.
  • The solution is harder to generalize. For example, if you want to use a different encryption shift, such as ca, then you have to rewrite both the encryption function and the decryption function. Forget that!

Use a data type that, if you feed in a d will give you back an a. Python has that kind of data type — a dictionary. In this case the d is called the key and the a is called the value.

tip You create a dictionary by using curly braces {}. Here’s an example, using a key 'd' and a value 'a'. In this example, the dictionary is on the right side ({'d':'a'}) and the name of a variable (my_dictionary) is on the left. You can use any valid variable name here.

>>> my_dictionary={'d':'a'}

>>> my_dictionary['d']

'a'

Look up a value in a dictionary by putting its corresponding key in square brackets. In this code, the key is 'd'. To get the value 'a' from this dictionary, type my_dictionary['d']. If you pass a value to the dictionary, it will complain (unless by luck there’s a key with the same name). In this example, my_dictionary has one item: ('d':'a'). That item has a key 'd' and the value 'a'. I asked for my_dictionary[‘d'] and got the value a.

remember I’m using the word element for the thing that lists have and the word item for the thing that dictionaries have. Elements have only one part (their value), but items always have two parts: a key and a value.

Dictionaries in Python have their own methods. (Did I tell you everything in Python is an object?) They’re items, keys, and values which, when called, give you the information about the dictionary. For example, to get the items in my_dictionary, type my_dictionary.items(). Try each of these methods now on my_dictionary and make sure you understand the output you’re getting.

You can create a dictionary with a number of items in the curly braces, but make sure you separate each item by a comma. Each item needs to be in this form — <key>:<value>.

A value can be just about anything. For keys, use either a string or a number. (You can use other things, but stick to strings and numbers for now.)

The comments in the following code tell you what’s going on:

>>> # an empty dictionary

>>> my_empty_dictionary = {}

>>> # earlier example, dictionary with one item

>>> my_dictionary={'d':'a'}

>>> # dictionary with two items separated by a comma

>>> my_dictionary={'d':'a', 'e':'b'}

Python complains if you try to get a key that doesn’t exist:

>>> my_dictionary['f']

 

Traceback (most recent call last):

  File "<pyshell#45>", line 1, in <module>

    my_dictionary['f']

KeyError: 'f'

After you create a dictionary, you can add to or change the items by assigning a value to the relevant key — either an existing key (changing the value associated with it) or a new key (creating a new item). When you use dictionaries, you don’t often have to assign values using bare strings. Instead, you’re usually getting the data from another source, storing it in dummy variables, and then using those dummy variables for the assignment. It’s conventional to refer to the key by a dummy variable called k and to the corresponding value by a dummy variable called v.

Here’s an example:

>>> k = 'f'

>>> v = 'c'

>>> my_dictionary[k]=v # create a new item in the existing dictionary

>>> my_dictionary

{'e': 'b', 'd': 'a', 'f': 'c'}

Create an Encryption Dictionary

Time to harness the dictionary’s power (it’s over nine thousaaaand!). You’re going to create a dictionary where every character in CHAR_SET is a key and the matching value is what that character should be changed to.

To do this, you need to know about the enumerate built-in. If you have a list, enumerate will create a new numbered object from it. Because you can treat a string as a list, this is what happens when you use enumerate("Hello"):

>>> [x for x in enumerate("Hello")]

[(0, 'H'), (1, 'e'), (2, 'l'), (3, 'l'), (4, 'o')]

This code has created a list of tuples that gives the index of each letter in the string "Hello". You can use this in a for loop to run through the elements of a list and get their index at the same time.

>>> for i,c in enumerate("Hello"):

        print(i,c)

 

 

(0, 'H')

(1, 'e')

(2, 'l')

(3, 'l')

(4, 'o')

In this code, each element created by enumerate is unpacked into two separate dummy variables (i and c). The i is the index of the character stored in c.

Use enumerate in this project to link the plaintext character set to the encrypted characters in the substitution list. They’ve been created this way:

  1. Name your dictionary.
  2. Create an empty dictionary with that name.

    Now you’re going to assign values to keys.

  3. Use for i,k in enumerate(CHAR_SET) to iterate through each character in it.
  4. Set each character to a dummy variable k (for key).

    Earlier, you used c (for character) in this project. Use k from now on.

  5. Use the index i to get the corresponding character from SUBSTITUTION_CHARS and assign it to a dummy variable v (for value).
  6. Create an item by assigning the value v to the key k.

How did it go? I added this in the Constants section at the top of the file after the entry that creates SUBSTITUTION_CHARS:

# generate encryption dictionary from the character set and

# its substitutions

encrypt_dict = {}

for i,k in enumerate(CHAR_SET):

    v = SUBSTITUTION_CHARS[i]

    encrypt_dict[k]=v

Use a join

To encrypt an entire message, you have to change every character in the message. Unfortunately, strings are immutable; you can’t change them. You’ll build a new string, one character at a time.

warning A simple way to create the string is by adding new bits one at a time, like this:

>>> a_string = ""

>>> a_string

''

>>> for i in range(10):

     a_string = a_string + str(i) # add '0' then '1' and so on

 

 

>>> a_string

'0123456789'

 

warning Joining strings together like this is called concatenation. The main thing to know about concatenation is — don’t do it. If your life depends on it, or you need a quick fix for a print statement, fine. Otherwise don’t. Just. Don’t. What’s happening is that for each iteration of the for loop, Python creates a new string, copies across the old string, and adds a single character to it. It’s adding ten characters but in the process it makes ten copies. This copying takes too long.

The proper Pythonic way of making a new string from a number of individual characters (or even other strings) is to add all the characters to a list and then join them all together to make a string.

Here’s the same example using the join method. It’s a method of any string. First you store all the characters in a dummy list called accumulator, then you join them up using the join method of the empty string:

>>> accumulator = []

>>> for i in range(10):

        accumulator.append(str(i))

 

 

>>> accumulator

['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

>>> ''.join(accumulator)

'0123456789'

You can use any string you like to join the elements of a list. In this example you use the empty string, but you can use another string and it will be included between each element of the list when it’s joined.

Here is the string ' spam, ' to join the accumulator from the previous example:

>>> ' spam, '.join(accumulator)

'0 spam, 1 spam, 2 spam, 3 spam, 4 spam, 5 spam, 6 spam, 7 spam,

8 spam, 9'

Spam, spam, spam! Wonderful spam! Experiment joining with some other strings. The list accumulator could have been any list of strings.

remember Whatever string you use to join the elements of the list gets put in between each pair of elements.

Rewrite the Encryption Function

Now it’s time to rewrite the encryption function:

  1. Keep the same function name but update the function’s docstring.
  2. Change the function’s definition to add an encryption_dict parameter.

    def encrypt_msg(plaintext, encrypt_dict):

  3. Comment out the old encryption code.

    You’ll delete it when the new code is working.

  4. Create an empty list to save up all the encrypted characters.

        ciphertext = []

  5. Iterate (go) through the plaintext and get the encrypted character by using the dictionary passed in.

        for k in plaintext:

            v = encrypt_dict[k]

  6. Store up the encrypted characters in your list.

            ciphertext.append(v)

  7. When it has made its way through the plaintext, join up the ciphertext and return it.

        return ''.join(ciphertext)

  8. Change the line where the function is called to add ENCRYPTION_DICT.

    ciphertext = encrypt_msg(CHAR_SET, ENCRYPTION_DICT)

I commented out the old function and replaced it with this:

#### Function Section

def encrypt_msg(plaintext, encrypt_dict):

    """Take a plaintext message and encrypt each character using

    the encryption dictionary provided. key translates to its

    associated value.

    Return the cipher text"""

    ciphertext = []

    for k in plaintext:

        v = encrypt_dict[k]

        ciphertext.append(v)

        # you could just say

        # ciphertext.append(encrypt_dict[k])

        # I split it out so you could follow it better.

    return ''.join(ciphertext)

And in the Testing section I changed the line ciphertext = encrypt_msg(CHAR_SET) to ciphertext = encrypt_msg(CHAR_SET, ENCRYPTION_DICT):

ciphertext = encrypt_msg(CHAR_SET, ENCRYPTION_DICT)

Now run it:

>>> ================================ RESTART ================================

>>>

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

!"#$%&'()*+,-./:;<=>?@[]^_`{|}~

}~ 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW

XYZ!"#$%&'()*+,-./:;<=>?@[]^_`{|

}~ 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW

XYZ!"#$%&'()*+,-./:;<=>?@[]^_`{|

When you’re satisfied that the new code works, delete the code that’s been commented out.

Write the Decryption Function

A coded message isn’t going to do anyone any good if they can’t decode it. You need some way to decrypt ciphertext. You can use the encryption structure to write a decryption function.

All you have to do is make a new copy of the encrypt_msg function and make these changes to the copy:

  1. Rename it.

    def decrypt_msg(ciphertext, decrypt_dict):

  2. Update the docstring.
  3. Rename the plaintext argument to ciphertext (what you’re decrypting).

        plaintext = []

            plaintext.append(v)

        return ''.join(plaintext)

  4. Rename encrypt_dict to decrypt_dict.

    def decrypt_msg(ciphertext, decrypt_dict):

  5. Create a decryption dictionary from the encryption dictionary.

    You do that in the next section.

  6. Test the decryption function.

    You do that in the section after next.

The decryption function I get looks like this:

def decrypt_msg(ciphertext, decrypt_dict):

    """Take a ciphertext message and decrypt each character using

    the decryption dictionary provided. key translates to its

    associated value.

    Return the plaintext"""

    plaintext = []

    for k in ciphertext:

        v = decrypt_dict[k]

        plaintext.append(v)

    return ''.join(plaintext)

Create a decryption dictionary

To go from ciphertext to plaintext, you reverse what you did. For example, 'd' encrypts to 'a', so 'a' will decrypt to 'd'. The encryption entry was ENCRYPTION_DICT['d']= 'a'. The matching decryption entry will be DECRYPTION_DICT['a'] = 'd'. You made the ENCRYPTION_DICT by assigning kv.

You can make a decryption dictionary at the same time by doing the reverse assignment vk. You can make the decryption dictionary by assigning DECRYPTION_DICT[v]=k in the loop you’re using to create ENCRYPTION_DICT.

Here’s the revised code to do this:

# generate encryption dictionary from the character set and

# its substitutions

ENCRYPTION_DICT = {}

DECRYPTION_DICT = {}

for i,k in enumerate(CHAR_SET):

    v = SUBSTITUTION_CHARS[i]

    ENCRYPTION_DICT[k]=v

    DECRYPTION_DICT[v]=k

Test the round trip

Now that you have your encryption and decryption functions working, test the round trip. That means encrypt a plaintext message to ciphertext and then decrypt it from ciphertext back to plaintext. Then compare them.

Follow these steps to test the decryption function:

  1. Choose some plaintext to test. Save it as test_message.

    You’ve been using CHAR_SET so far. Test that first. Then you can use some more interesting plaintext.

    test_message = CHAR_SET

  2. Print the test_message.

    print(test_message)

  3. Encrypt the plaintext using encrypt_msg. Save it as ciphertext.

    ciphertext = encrypt_msg(test_message, ENCRYPTION_DICT)

  4. Print the ciphertext.

    print(ciphertext)

  5. Decrypt the ciphertext using decrypt_msg. Save that as plaintext.

    plaintext = decrypt_msg(ciphertext, DECRYPTION_DICT)

  6. Print plaintext.

    print(plaintext)

  7. Print the comparison plaintext == test_message.

    print(plaintext == test_message)

  8. Did you end up with the same thing you started with?

I commented out some earlier print statements and got this:

#### Testing Section

##ciphertext = encrypt_msg(CHAR_SET, ENCRYPTION_DICT)

##print(CHAR_SET) # for comparison while testing

##print(ciphertext)

##print(SUBSTITUTION_CHARS) #what you should get

##print(ciphertext == SUBSTITUTION_CHARS) # are they the same?

##

##plaintext = decrypt_msg(ciphertext, DECRYPTION_DICT)

##print(plaintext)

##print(plaintext == CHAR_SET)

 

test_message = CHAR_SET

ciphertext = encrypt_msg(test_message, ENCRYPTION_DICT)

plaintext = decrypt_msg(ciphertext, DECRYPTION_DICT)

 

print(test_message)

print(ciphertext)

print(plaintext)

print(plaintext == test_message)

Running it should give you this:

>>> ================================ RESTART

================================

>>>

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

!"#$%&'()*+,-./:;<=>?@[]^_`{|}~

}~ 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW

XYZ!"#$%&'()*+,-./:;<=>?@[]^_`{|

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

!"#$%&'()*+,-./:;<=>?@[]^_`{|}~

True

You can compare the first line (test_message) to the third line (plaintext). However, the computer has done it for you in the last line. True means that these two lines are the same.

Try it with your own secret message. Change the value of test_message. Way back at the start of the project I suggested creating a constant called TEST_MESSAGE. You can use that now:

TEST_MESSAGE = "I like Monty Python. They are very funny."

test_message = TEST_MESSAGE

You get this output:

>>> ================================== RESTART ================================

>>>

I like Monty Python. They are very funny.

F|ifhb|Jlkqv|Mvqelk+||Qebv|7ob|sbov|crkkv+

I like Monty Python. They are very funny.

True

If you delete the code you commented out earlier, your code looks like this:

"""Cryptopy

Take a plaintext message and encrypt it using a Caesar cipher

Brendan Scott, 2015

"""

 

#### Imports Section

import string

 

#### Constants Section

CHAR_SET = string.printable[:-5]

SUBSTITUTION_CHARS = CHAR_SET[-3:]+CHAR_SET[:-3]

# generate encryption dictionary from the character set and

# its substitutions

ENCRYPTION_DICT = {}

DECRYPTION_DICT = {}

for i,k in enumerate(CHAR_SET):

    v = SUBSTITUTION_CHARS[i]

    ENCRYPTION_DICT[k]=v

    DECRYPTION_DICT[v]=k

 

 

TEST_MESSAGE = "I like Monty Python. They are very funny."

 

#### Function Section

def encrypt_msg(plaintext, encrypt_dict):

    """Take a plaintext message and encrypt each character using

    the encryption dictionary provided. key translates to its

    associated value.

    Return the cipher text"""

    ciphertext = []

    for k in plaintext:

        v = encrypt_dict[k]

        ciphertext.append(v)

        # you could just say

        # ciphertext.append(encrypt_dict[k])

        # I split it out so you could follow it better.

    return ''.join(ciphertext)

 

 

def decrypt_msg(ciphertext, decrypt_dict):

    """Take a ciphertext message and decrypt each character using

    the decryption dictionary provided. key translates to its

    associated value.

    Return the plaintext"""

    plaintext = []

    for k in ciphertext:

        v = decrypt_dict[k]

        plaintext.append(v)

    return ''.join(plaintext)

 

 

#### Testing Section

test_message = TEST_MESSAGE

ciphertext = encrypt_msg(test_message, ENCRYPTION_DICT)

plaintext = decrypt_msg(ciphertext, DECRYPTION_DICT)

 

print(test_message)

print(ciphertext)

print(plaintext)

print(plaintext == test_message)

Type in your plaintext or ciphertext

Now you need to apply the code to your own messages, rather than hard coding the message you want to encrypt into the code. (TEST_MESSAGE was hard coded.)

The obvious way to get more messages to encrypt/decrypt is to use raw_input. Trouble, though: The program can encrypt and decrypt, but it can’t tell whether it’s getting plaintext to encrypt or ciphertext to decrypt.

You have ways around this, but the simplest is to just do both the encryption and the decryption! Users can choose the answer they want.

To do this:

  1. Think of a prompt to use when asking for the message. The prompt should deal with both encryption and decryption and get a message to be handled with raw_input.

    message = raw_input("Type the message to encrypt below: ")

  2. Encrypt the message to ciphertext and decrypt the message to plaintext.

    ciphertext = encrypt_msg(message, ENCRYPTION_DICT)

    plaintext = decrypt_msg(message, DECRYPTION_DICT)

  3. Print a short line saying This message encrypts to. Then print the ciphertext.
  4. Print a short line saying This message decrypts to. Then print the plaintext.

    print("This message encrypts to")

    print(ciphertext)

    print  # just a blank line for readability

    print("This message decrypts to")

    print(plaintext)

I replaced the section marked Testing Section in the last full code readout with the following. (If you’d rather, you can comment out the code instead of deleting it.)

#### Input and Output Section

message = raw_input("Type the message to process below: ")

ciphertext = encrypt_msg(message, ENCRYPTION_DICT)

plaintext = decrypt_msg(message, DECRYPTION_DICT)

print("This message encrypts to")

print(ciphertext)

print # just a blank line for readability

print("This message decrypts to")

print(plaintext)

The character lets you type the message on a new line after the prompt. Running it gives you this:

    >>> ================================== RESTART ================================

    >>>

    Type the message you'd like to encrypt below:

I love learning Python. And my teacher is smelly. And I shouldn't start a sentence with and.

 

This message encrypts to

F|ilsb|ib7okfkd|Mvqelk+|xka|jv|qb79ebo|fp|pjbiiv+||xka|F|pelriak$q|pq7oq|7|pbkqbk9b|tfqe|7ka+

 

This message decrypts to

L2oryh2ohduqlqj2SBwkrq;2Dqg2pB2whdfkhu2lv2vphooB;22Dqg2L2vkrxogq*w2vwduw2d2vhqwhqfh2zlwk2dqg;

Run it again. This time, feed in the encrypted text to get the decryption. Copy and paste:

    >>> ================================== RESTART ================================

    >>>

    Type the message you'd like to encrypt below:

F|ilsb|ib7okfkd|Mvqelk+|xka|jv|qb79ebo|fp|pjbiiv+||xka|F|pelriak$q|pq7oq|7|pbkqbk9b|tfqe|7ka+

This message encrypts to

C_fip8_f84lhcha_Jsnbih(_uh7_gs_n846b8l_cm_mg8ffs(__uh7_C_mbiof7h!n_mn4ln_4_m8hn8h68_qcnb_4h7(

 

This message decrypts to

I love learning Python. And my teacher is smelly. And I shouldn't start a sentence with and.

You’ll get what you’re looking for, but you’ll have to print more information than you need. If this was a graphical interface (which you’ll read about in the projects at dummies.com/go/pythonforkids), you could just have two separate buttons, one for encryption and one for decryption.

Encrypt a Text File

Having to type (or even copy and paste) your text into a text_input is a bit of a drag. One option is to have Python read the text from a file. This is pretty simple to do but the explanation is a little lengthy.

warning This only works for text (.txt) files — files that have no extra formatting or other instructions in them. If you try this on a word processing file (like .docx or .doc), you’ll come to grief.

Open, write to, and close a file

Try this in the IDLE Shell window. Make sure you copy it exactly:

>>> file_object = open('p4k_test.py','w')

This tells Python to open a file on your computer’s hard drive using the open built-in with the name p4k_test.py for writing, and to store the result in a variable called file_object. If a file called p4k_test.py already exists, this overwrites the file and deletes whatever was in it before.

warning If you ask Python to do something that will delete data, it will go ahead and do it. It won’t warn you beforehand. It won’t make a backup. Think twice before you do any file operations in Python. Do a test run if you’re unsure.

Type help(open) at the interpreter to get the help text for open. It should tell you that it, “Open a file using the file() type, returns a file object.”. That’s why I called the variable file_object in the code. The file object isn’t strictly speaking the file itself. You can think of it as an interface between the file and the rest of Python. That it’s an object shouldn’t be any surprise. You can use dir to find its attributes.

Type dir(file_object) now on the file object that you opened at the start of this section. You get a list of its attributes. Of these, write, read, readlines, and close are the ones you should pay attention to here.

tip When you finish with a file, you must call its close method.

Write something to the file and close it by creating a string (text in this example). Then use the write and close methods of the file object:

>>> text = "print('Hello from within the file') " # watch the " and '

>>> file_object.write(text) # writes it to the file

>>> file_object.write(text) # writes it to the file again!

>>> file_object.close() # finished with file, so close it

This stores a string in the variable text. The string ends in the new line character . It then writes this value to the file using the write method of file_object. (It writes it twice. I need the second line later to demonstrate something.) The character in the string puts each on a separate line. Then it closes the file.

Read the file

Open the file again — only this time use a read mode rather than a write mode.

warning If you open a file in write mode you destroy its contents. Make sure you copy this exactly. You have to change the 'w' (write) character to 'r' (read).

>>> file_object = open('p4k_test.py','r')

This line is only different from the earlier line by a single character — 'r' instead of 'w' . This opens the file in read mode (r for read? Brilliant!). Opening the file in read mode won’t destroy the contents of the file. (Phew!)

When you’ve opened it, you can use the file_object read method:

>>> print(file_object.read())

print ('Hello from within the file')

print ('Hello from within the file')

The read method of file_object (which itself is an object of type file) reads the entire contents of the file. The file object also keeps track of how far through the file you’ve read and continues reading from where you left off. Since you’ve already read to the end of the file, you’ll get nothing if you read again:

>>> print(file_object.read())

If you close the file and open it again, you can read from the start:

>>> file_object.close()

>>> file_object = open('p4k_test.py','r')

>>> print(file_object.read())

print('Hello from within the file')

print('Hello from within the file')

 

>>> file_object.close() # finished, so close the file.

The readlines() method is, possibly, a more common way of reading from a file. Unlike read, which reads everything from the file’s current position to the end, readlines() reads one line at a time.

>>> file_object.close()

>>> file_object = open('p4k_test.py','r')

>>> counter = 0

>>> for line in file_object.readlines():

        counter = counter +1

        print(str(counter)+ ": "+line)

 

 

1: print('Hello from within the file')

 

2: print('Hello from within the file')

 

>>> file_object.close()

The lines are separated from each other because each line in the file has a at the end of it, and the print statement is printing and then adding its own line break before the next line. You can verify that there is an at the end of each line because the variable line still has the last line of the file in it:

>>> line

"print('Hello from within the file') "

Run the file

The data you’ve written to the file is valid Python code. You can prove that the file that you’ve been writing to is an average, ordinary file (a Python file since you gave it a .py ending). You can run it: Open the file p4k_test.py with IDLE’s Edit window. Run the file without making any changes to it.

>>> ================================== RESTART ================================

>>>

Hello from within the file

Hello from within the file

It’s like you typed the code into the file directly.

Open and close with with

It’s sometimes a pain to remember when to close a file that you’ve opened. Python has yet another keyword to deal with that (and other) issues — with. You put the open command after the with keyword, then you put an as keyword and the name of the dummy variable to hold the file object that will be created, like this:

>>> with open('p4k_test.py','r') as file_object:

        print(file_object.read())

 

 

print('Hello from within the file')

print('Hello from within the file')

 

>>> file_object

<closed file 'p4k_test.py', mode 'r' at 0xf7fed0>

Here Python opens the file named p4k_test.py in read mode and stores the object it creates in a variable named file_object. In the second line it reads and prints the contents of the file. When it gets to the end of the code block, Python realizes that you don’t need the file anymore and cleans up after itself. (Do your parents wish you were a bit more like Python?) Part of this is that it closes the file. The last line tells you that the object is a closed file, so Python has closed the file for you.

You can use with for multiple files like this fake code. Don’t type this at a command line. It won’t work:

with open(filename1, 'r') as file_object1,

     open(filename2, 'r') as file_object2:

    do stuff with file_object1

    do stuff with file_object2

warning Here’s a quick example in the Shell. This destroys any file called testfile2, so watch out!

>>> with open('testfile2','w') as a:

        a.write('stuff')

 

 

>>> with open('testfile2','r') as a, open('p4k_test.py','r') as b:

        print(a.read())

        print(b.read())

 

 

stuff

print('Hello from within the file')

print('Hello from within the file')

 

>>> a

<closed file 'testfile2', mode 'r' at 0xf6e540>

>>> b

<closed file 'p4k_test.py', mode 'r' at 0xef4ed0>

tip You’ll mainly use the with keyword this way. From now on, use with unless you have a good reason not to.

Encrypt and Decrypt from a File

It’s a bit of a hassle to cut and paste into the command line all the time. You’re going to encrypt a file in the next two sections.

  1. Choose a name for the file containing the plaintext to be encrypted (cryptopy_input.txt).

    Your application will be coded so that it only reads from this file and no others.

  2. Create that file and put some test data in it.
  3. Choose a name for the constant to use to store cryptopy_input.txt and set that constant.

    You can encrypt other files by changing this variable.

  4. Choose a name for the file that will store the encrypted ciphertext (cryptopy_output.txt).
  5. Choose a name for the constant to use to store ciphertext.txt and set that constant.
  6. Open the input file.
  7. Read the plaintext.
  8. Close the input file.
  9. Encrypt the file’s contents.
  10. Print the encrypted text.

    This is for the testing phase.

  11. Open the output file.
  12. Write the ciphertext.
  13. Close the output file.

The process from Step 6 to Step 13 should replace the Input and Output section in the current version of the code, so delete (or comment out) that section and put this there.

Choose names and create the test data

The names I’m choosing for the input and output filenames are INPUT_FILE_NAME and OUTPUT_FILE_NAME respectively.

Use the Shell window to quickly create the INPUT file and put some test data into it:

>>> INPUT_FILE_NAME = "cryptopy_input.txt"

>>> with open(INPUT_FILE_NAME,'w') as input_file:

        input_file.write('This is some test text')

You don’t get any feedback on the file’s creation, so if you’re feeling suspicious, use your file explorer to check that the file really exists.

tip The os.path library has a function — exists — that tells you whether a file exists. To use it, type import os.path, then call os.path.exists(<path to file>). Replace <path to file> with a string that has the relevant filename or a complete path to the file.

Open the file and encrypt the data

The main trick in opening a file is to tell the difference between the filename and the file object. The filename is what you give to the open built-in. The file object is what you get back from it. I use input_file and output_file to hold the file objects.

First add constants to hold the names of the input and output files to the Constants section. This is what I added:

INPUT_FILE_NAME = "cryptopy_input.txt"

OUTPUT_FILE_NAME = "cryptopy_output.txt"

Now do this:

  1. Comment out the old Input and Output section.
  2. In the Input and Output section, open the input file and read its contents into a variable called message.

    with open(INPUT_FILE_NAME,'r') as input_file:

        message = input_file.read()

  3. Use the encrypt_msg function to encrypt the message to ciphertext.

    ciphertext = encrypt_msg(message, ENCRYPTION_DICT)

  4. Open the output file and write the ciphertext to it.

    with open(OUTPUT_FILE_NAME,'w') as output_file:

        output_file.write(ciphertext)

This is my finished code:

#### Input and Output Section

with open(INPUT_FILE_NAME,'r') as input_file:

    message = input_file.read()

 

ciphertext = encrypt_msg(message, ENCRYPTION_DICT)

print(ciphertext) # just for testing

 

with open(OUTPUT_FILE_NAME,'w') as output_file:

    output_file.write(ciphertext)

Pretty amazing that Steps 6 through 13 are all covered there, isn’t it? Run it and you get this:

Qefp|fp|pljb|qbpq|qbuq

This pops up in the IDLE Shell window because of the print statement in the code. You can confirm it’s been written to the file in IDLE’s Shell window by opening the file and printing its contents, like this:

>>> OUTPUT_FILE_NAME = "cryptopy_output.txt"

>>> with open(OUTPUT_FILE_NAME,'r') as output_file:

        print(output_file.read())

 

 

Qefp|fp|pljb|qbpq|qbuq

Decrypt from Your Shell

It would be nice to decrypt this and test it in the interactive Shell without having to rerun the program (and write code to decrypt from a file). To do that you would need to somehow get the decrypt_msg function out of the cryptopy.py file and into the Shell. Luckily, you can use import to do this.

Go to your IDLE Shell window and type:

>>> import cryptopy

Qefp|fp|pljb|qbpq|qbuq

This is the same structure you used to import modules from the Python standard library. You can import any Python code. The only limit is that Python needs to be able to find that code. For you at the moment, that means that the code needs to be in C:Python27. Now that you’ve imported this code, you can use the functions and constants defined within it.

Define a plaintext message:

>>> plaintext = """I wonder if I can use the functions and constants

from cryptopy to make encrypted messages from the command line?"""

Use the encrypt_msg function from the file. You need to treat cryptopy like the module name, just as if you had imported a module from the standard library:

>>> ciphertext = cryptopy.encrypt_msg(plaintext,cryptopy.ENCRYPTION_DICT)

Print it to see its encrypted stuff:

>>> ciphertext

'F|tlkabo|fc|F|97k|rpb|qeb|crk9qflkp|7ka|9lkpq7kqp|colj|9ovmqlmv ql|j7hb|bk9ovmqba|jbpp7dbp|colj|qeb|9ljj7ka|ifkb<'

Now use the decrypt_msg function to decrypt it and confirm the round trip:

>>> print(cryptopy.decrypt_msg(ciphertext,cryptopy.DECRYPTION_DICT))

I wonder if I can use the functions and constants from cryptopy

to make encrypted messages from the command line?

Now you can pick the functions you want to use — even from the Python interpreter. You can use tab completion to get constant names like cryptopy.ENCRYPTION_DICT and so on. Just as you can use these functions within the interpreter, you can use them in a Python program, simply by importing the module.

There are some limits, though. To use the import statement, the module that you want to use must be either in one or the other:

  • A directory listed in the PYTHONPATH environment variable.
  • The current working directory of the Python script that’s importing the file.

For now, put your application (and any modules it is to call) into C:Python27.

When you imported cryptopy, some text printed: Qefp|fp|pljb|qbpq|qbuq. It looks a lot like some plaintext that’s been encrypted. Try to decrypt it:

>>> ciphertext = "Qefp|fp|pljb|qbpq|qbuq"

>>> cryptopy.decrypt_msg(ciphertext,cryptopy.DECRYPTION_DICT)

'This is some test text'

 

warning This is the message you saved to cryptopy_input.txt. It’s encrypted and printed out in the Input and Output section of cryptopy. When you imported cryptopy, Python needed to execute the whole file cryptopy.py, including the testing and input code. This is fine when you’re running the module on its own, but you don’t want that happening when other programs are importing the module. They just want access to the functions; they don’t want the testing code run.

Python gives you a way to stop this code running when you import it. Python identifies the name of the code being run with a variable called __name__. (Remember dunder name?) When you run a file by itself, the value "__main__" (“dunder main”) is assigned to this variable. Python automatically does this.

tip You can use the __name__ variable to identify whether the code is being run as an import. If __name__ is equal to "__main__", then the code isn’t being imported. Otherwise, it is.

It’s common in Python to isolate code (in an if block) that you don’t want run during an import, like this:

>>> if __name__ == "__main__":

        print("Not in an import")

 

 

Not in an import

Tidy up the cryptopy script so it can be imported by other scripts:

  1. Insert an if clause comparing __name__ to "__main__".
  2. Move the existing code from the Input and Output section to the code block of that if clause.

The new Input and Output section looks like this. Notice the indented commented-out sections:

if __name__ == "__main__":

##    message = raw_input("Type the message to process below: ")

##    ciphertext = encrypt_msg(message, ENCRYPTION_DICT)

##    plaintext = decrypt_msg(message, DECRYPTION_DICT)

##    print("This message encrypts to")

##    print(ciphertext)

##    print # just a blank line for readability

##    print("This message decrypts to")

##    print(plaintext)

    with open(INPUT_FILE_NAME,'r') as input_file:

        message = input_file.read()

 

    ciphertext = encrypt_msg(message, ENCRYPTION_DICT)

    print(ciphertext) # just for testing

 

    with open(OUTPUT_FILE_NAME,'w') as output_file:

        output_file.write(ciphertext)

I will refer to this as the Main section in the rest of the book. Rename your #### Input and Output Section to #### Main Section.

Now when you import, Python loads the functions without running what was previously in the Input and Output section:

>>> import cryptopy # nothing should print

>>>

tip This test if __name__ == "__main__": is a useful way to reuse the code you write without having to copy and paste it into your new files. Just import it.

This, in turn, means that instead of having one copy per file where it’s used, one copy is imported. If the code has errors or features that ought to be added to the code, you can add them in (ideally) only one file. Other programs that rely on that code will automatically use the code that you updated.

Change the Code to Decrypt Too

At the moment, the code will only open a file and encrypt its contents. It would be useful if it could decrypt the contents. Should the code be encrypting or should it be decrypting when you run it?

You’re going to deal with it by a switch in the software. Make a switch by setting a constant in the code and then executing one branch of code or another depending on how the switch is set.

To do so in this code, do the following in the Main section:

  1. Name a constant.

    The constant will take either value True or the value False. If True, then it will encrypt the input file. If False, it will decrypt it. Create the constant and add it to the start of the Main section. (You could put it in the Constants section.)

        ENCRYPT = False # This is the constant used for the if clause

  2. Change all references from ciphertext to text_to_output.

    The name ciphertext is no longer accurate.

  3. In between reading the input file and writing the output file, add an if clause.

    It tests this constant and encrypts or decrypts the contents of the input file depending on the value the switch takes.

        if ENCRYPT:

            text_to_output = encrypt_msg(message, ENCRYPTION_DICT)

        else:

            text_to_output = decrypt_msg(message, DECRYPTION_DICT)

The new Main section, excluding commented-out code, looks like this:

#### Main Section

if __name__ == "__main__":

    ENCRYPT = False # This is the constant used for the if clause

 

    with open(INPUT_FILE_NAME,'r') as input_file:

        message = input_file.read()

    if ENCRYPT:

        text_to_output = encrypt_msg(message, ENCRYPTION_DICT)

    else:

        text_to_output = decrypt_msg(message, DECRYPTION_DICT)

 

    print(text_to_output) # just for testing

 

    with open(OUTPUT_FILE_NAME,'w') as output_file:

        output_file.write(text_to_output)

I set the constant ENCRYPT to False (because the text is already encrypted). You can also see that, by choosing what code to change, you can easily decrypt.

To test the code, put some encrypted text in the input file. You can do that by writing the data there from the IDLE Shell window or by opening the file from within IDLE. Make sure you use the Files of Type drop-down menu on the Open dialog box to select Text Files (*.txt) or All Files (*). Otherwise, it won’t show up in the window.

I cut and paste the earlier encrypted text in there:

Qefp|fp|pljb|qbpq|qbuq

This is what it looks like when you run it:

>>> ================================== RESTART ================================

>>>

This is some test text

tip A more common use of a switch of this kind is to have a constant called DEBUG (or similar) to turn on and off debugging-related code.

The Complete Code

My code ended up looking like this:

"""Cryptopy

Take a plaintext message and encrypt it using a Caesar cipher

Take a ciphertext message and decrypt it using the same cipher

Encrypt/decrypt from and to a file

Brendan Scott, 2015

"""

 

#### Imports Section

import string

 

#### Constants Section

CHAR_SET = string.printable[:-5]

SUBSTITUTION_CHARS = CHAR_SET[-3:]+CHAR_SET[:-3]

# generate encryption dictionary from the character set and

# its substitutions

ENCRYPTION_DICT = {}

DECRYPTION_DICT = {}

for i,k in enumerate(CHAR_SET):

    v = SUBSTITUTION_CHARS[i]

    ENCRYPTION_DICT[k]=v

    DECRYPTION_DICT[v]=k

# other characters - , etc - are not changed

for c in string.printable[-5:]: # watch the colons!

    ENCRYPTION_DICT[c]=c

    DECRYPTION_DICT[c]=c

 

TEST_MESSAGE = "I like Monty Python. They are very funny."

INPUT_FILE_NAME = "cryptopy_input.txt"

OUTPUT_FILE_NAME = "cryptopy_output.txt"

 

#### Function Section

def encrypt_msg(plaintext, encrypt_dict):

    """Take a plaintext message and encrypt each character using

    the encryption dictionary provided. key translates to its

    associated value.

    Return the cipher text"""

    ciphertext = []

    for k in plaintext:

        v = encrypt_dict[k]

        ciphertext.append(v)

        # you could just say

        # ciphertext.append(encrypt_dict[k])

        # I split it out so you could follow it better.

    return ''.join(ciphertext)

 

 

def decrypt_msg(ciphertext, decrypt_dict):

    """Take a ciphertext message and decrypt each character using

    the decryption dictionary provided. key translates to its

    associated value.

    Return the plaintext"""

    plaintext = []

    for k in ciphertext:

        v = decrypt_dict[k]

        plaintext.append(v)

    return ''.join(plaintext)

 

 

#### Main Section

if __name__ == "__main__":

##     message = raw_input("Type the message to process below: ")

##     ciphertext = encrypt_msg(message, ENCRYPTION_DICT)

##     plaintext = decrypt_msg(message, DECRYPTION_DICT)

##     print("This message encrypts to")

##     print(ciphertext)

##     print # just a blank line for readability

##     print("This message decrypts to")

##     print(plaintext)

    ENCRYPT = False # This is the constant used for the if clause

 

    with open(INPUT_FILE_NAME,'r') as input_file:

        message = input_file.read()

 

    if ENCRYPT:

        text_to_output = encrypt_msg(message, ENCRYPTION_DICT)

    else:

        text_to_output = decrypt_msg(message, DECRYPTION_DICT)

 

    print(text_to_output) # just for testing

 

    with open(OUTPUT_FILE_NAME,'w') as output_file:

        output_file.write(text_to_output)

Summary

In this project you read about:

  • A new data type — the dictionary. Dictionaries have items. Each item has a key and a value.
  • Dictionaries, which allow direct access to a value referenced by a key. If key:value is an item in the dictionary a_dictionary, the syntax to access the value is a_dictionary[key].
  • Creating empty dictionaries.
  • Creating items in existing dictionaries by creating key:value pairs in dictionaries and assigning values to keys.
  • Using Caesar ciphers.
  • Creating a string by joining a list together and using the join method.
  • Opening files on your computer using open and close.
  • Reading from and writing to files on your computer using read and write.
  • Using the with and as keywords to have Python clean up after you’ve used a file.
  • Importing modules that aren’t in the standard library. These could be your own modules or someone else’s.
  • Using if __name__ == "__main__": to isolate the functions in your file from the code being executed when the module is run separately.
  • Using constants as switches to control program flow.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.196.182