("Assignment 3" was the second part of Assignment 2)
We'll kick off this assignment with a glimpse into the world of cryptographic algorithms. In the 1500s, Blaise de Vigenere developed a new algorithm for encrypting secret messages. Such encryption schemes are known as "cyphers", and Vigenere's was a major impovement over its predecessors. In fact, up until the end of the 1800s, it was viewed as uncrackable.
The general idea behind all encryption schemes is to replace each letter in the original message with another letter in a manner which makes the encrypted message unreadable, but which can be reversed to recover the the original message. This recovery is known as "decryption", and is possible in reversible encryption schemes if you know how the encryption was done (actually, modern methods are much harder than this, but we'll simplify here for the sake of discussion). The idea behind the Vigenere cypher is to use a secret key that determines how a letter in the original message is mapped to a letter in the encrypted message. However, we don't want the same replacement all the time, i.e., we don't want 'a' to always be replaced with 'r'. Instead, we want to change the replacement scheme as we move, letter by letter, through the message. So maybe 'a' will be replaced by 'r' the first time it appears in the clear message, but the second 'a' will be replaced by 'd'. While this solves the frequency analysis problem, it could get complicated coming up with the secret key. Vigenere came up with a clever simplification. He said, in effect, "Forget trying to remember all the different mappings from clear letters. Instead, use the position of letters in the clear message as the basis for encryption." What he proposed was a secret key composed of a finite sequence of integers from 0-25. Let's use an example of 3,1,5,8 as a secret key. (Real keys can be of suitably gigantic length.) His encryption algorithm is to take the letter in the first position of the clear message, and add 3 to it, e.g., 'a' becomes 'a'+3 or 'd'. Then take the letter in the second position of the clear message, and add 1 to it. Keep moving through the clear message and secret key, building up the enrypted message. If the key is shorter than the clear message, it just repeats. Here is an example using 3,1,5,8 as the secret key.
This is looking good, other than it is somewhat hard to remember number sequences. It's a lot easier to remember english phrases, e.g., "blaise was cool", or removing the spaces, "blaisewascool". We can convert each letter in "blaisewascool" to its alphabetic position, e.g., b=1, l=11, a=0, i=8, etc. Using the example above, "dbfi" would give us 3,1,5,8.secret key= 3,1,5,8 clear message= attackatdawn + |||||||||||| 315831583158 => note the key repeats |||||||||||| encrypted message= duyiflfbgbbv
Your job is to write a program that will automate all this. What I want is a function that will encrypt (e.g., "attackatdawn" => "duyiflfbgbbv") and a function that will decrypt (e.g., "duyiflfbgbbv" => "attackatdawn") using the Vigenere cypher. Here is a start at the function signature for encryption:
So the function takes in a clear message to encrypt, e.g., "attackatdawn", and a secret key, and returns an encrypted message, e.g., "duyiflfbgbbv". The secret key is represented by some sequence of lower case letters, with spaces removed, e.g., "dbfi" (which will turn into 3,1,5,8). We would use it as follows:string vigenereEncrypt( string message, string secret_key ){...}
String encrypted_message = the_object.vigenereEncrypt( "attackatdawn", "dbfi" );
At first glance, programming this scheme may appear quite overwhelming. However, with effective use of function decomposition to abstract away hard steps, it becomes quite tractable. There are three parts to this problem:
Vigenere based his algorithm on "shifting" an individual letter a given number of positions to the right. Thus, given the pair ('a', 1), the result is 'b': we shift 'a' 1 to the right. I'd like you to build a function, letterShift, that does this style of shifting. You don't actually have to "build" it - simply fill in the pieces that I have left out. I'll give you a small test program where you can work on it.
Before starting to fill in the missing pieces, here are some facts that may help you.
'a' < 'b' < 'c'... < 'z'
int int_a = 'a';
char char_a = (char) int_a;
(pos_d will be given the value 3).int pos_d = 'd' - 'a';
(25+2)%26 will have the value 1.
Save the code below in a file named vigenere.cpp, and complete the letterShift function by replacing the (non-compilable) question marks. Play close attention to the comments following each line for what legal values are.
#include <iostream> using namespace std; char letterShift(char c, int k); int main() { char c; int k; cout << "Char: "; cin >> c; cin.ignore(80,'\n'); cout << "Shift distance: "; cin >> k; cout << "return value = " << letterShift( c, k ) ); return 0; } //main char letterShift(char c, int k) { // We only shift lower case letters if( !islower(c) ) return '*'; // Compute the offset of c in alphabet (range 0-25) int c_offset = ????? //cout << "should be in range 0-25:" + c_offset ); // Compute the position of the shifted char in alphabet (range 0-25) int shifted = ???? //cout << "should be in range 0-25:" + shifted ); // Convert to char and return (range 'a'-'z', not 0-25) return ???? } //letterShiftRun Vigenere on some sample values, e.g., a 0, a 1, f 2, z 1, z 3, etc. Make sure your function is producing the right output.
We are now ready to build the encryption function. As with letterShift, I have given you the basic structure. Your job is to fill in the missing pieces. First copy the function below into vigenere.cpp. (Note that this function uses letterShift, so make sure letterShift is working before trying to complete vigenereEncrypt .)
string vigenereEncrypt(string message, string secret_key) { // We want to repeatedly move left-to-right one position in both // the clear message and the key. I've chosen to combine this into // a single for-loop, using i to keep track of where we are in // the clear message and k to keep track of where we are in the // key. If we run off the end of the key, we just start at the // left-most end again. int k = 0; // key index string encrypted = ""; // what we will eventually return for ( int i = 0; ????; ???? ) { // convert kth letter in key to an int (see letterShift for hint) int int_key = ????; encrypted = encrypted + ????; // Don't run off right end of key, start over at beginning ???????? } return encrypted; }Once you fill in the pieces, replace the forward declarationas and the main function in vigenere.cpp with the following:
char letterShift(char c, int k); string vigenereEncrypt(string message, string secret_key); int main() { string key,message; cout << "Key: "; cin >> key; cin.ignore(80,'\n'); cout << "Message: "; getline(cin,message); string encrypted = vigenereEncrypt( message, key ); cout << "message = " << message << endl; cout << "encrypted = " << encrypted << "(key = " << key << ")" << endl; return 0; } //mainNow test with some sample message-key pairs, e.g., attackatdawn, with key dbfi. When you are convinced it works, you are ready to move on to the last problem.
Now that you have encryption working, let's turn to decryption. Before hacking code, let's do a little work with paper-and-pencil, always a good idea. Let's say the first letter in our clear message is 'a' and the first shift in our key is 'e' (or 4). We get a shifted letter 'e' (i.e., shifting 'a' right four places gives us 'e'). For decryption, we have to get from 'e' back to 'a'. We need to either shift 'e' left 4 or right 22. We already have a function, letterShift, that would be happy to do a right shift of 22. Seems like a good idea to use it instead of writing yet another function. That leaves us with the question of how we get from the key value of 4 (used for encryption) to 22 (to reverse encryption)? Looks like 26 - 4 = 22. You might want to try this for several values of the key to convince yourself. Now all we have to do is to copy-and-paste the vigenereEncrypt function body, and make the following change: where we pass the key to letterShift, we pass it 26 minus the key value.
Now you can start hacking. We need a function like this in Vigenere:
string vigenereDecrypt(string encrypted, string secret_key) { // Here we have the encrypted message and we need to "unshift" // by the key to get back the clear message, which is returned. }Copy-and-paste from vigenereEncrypt and make the necessary change. Then replace the forward declarations and main with following.
string vigenereEncrypt(string message, string secret_key); string vigenereDecrypt(string encrypted, string secret_key); int main() { string key,message; cout << "Key: "; cin >> key; cin.ignore(80,'\n'); cout << "Message: "; getline(cin,message); string encrypted = vigenereEncrypt( message, key ); cout << "message = " << message << endl; cout << "encrypted = " << encrypted << "(key = " << key << ")" << endl; string restored = vigenereDecrypt( encrypted, key ); cout << "restored = " << restored << endl; cout << "message equiv to restored (should be true): " << (message == restored ) << endl; return 0; } //main
Run the following tests to verify your code works:
Message | Key |
attackatdawn | dbfi |
abcdefghijklmnopqrstuvwxyz | jihgfedcbaz |
floridastatemottoisvoteearlyandoften | disneymagic |
Your output should look like the following (user input emphasized):
Key: dbfi Message: attackatdawn message = attackatdawn encrypted = duyiflfbgbbv(key = dbfi) restored = attackatdawn message equiv to restored (should be true): 1 Key: jihgfedcbaz Message: abcdefghijklmnopqrstuvwxyz message = abcdefghijklmnopqrstuvwxyz encrypted = jjjjjjjjjjjuuuuuuuuuuuffff(key = jihgfedcbaz) restored = abcdefghijklmnopqrstuvwxyz message equiv to restored (should be true): 1 Key: disneymagic Message: floridastatemottoisvoteearlyandoften message = floridastatemottoisvoteearlyandoften encrypted = itgembmszivhuggxmusbwvhmsepwmnjwhwmf(key = disneymagic) restored = floridastatemottoisvoteearlyandoften message equiv to restored (should be true): 1
Bonus challenge: There is a much more elegant way to implement vigenereDecrypt, using only one line of code, with a single function call. Can you see it?
What to turn in
You are now done with the Vigenere cypher, great work. Turn in code in the file vigenere.cpp, which should contain the functions letterShift, vigenereEncrypt and vigenereDecrypt. If you were not able to get everything working, the code you turn in should contain as much as you got to work for partial credit. Whatever you turn in, make sure that it compiles with no errors!