p3-ciphers
EECS 183 Project 3: Ciphers
Due Friday, October 18th at 8:00 p.m. (accepted until 11:59:59 p.m.)
Direct autograder link
Overview
In this project, you will move on to cryptography and you’ll be asked to encrypt and decrypt messages using three different encryption algorithms. The S’more part of this project will challenge you to “crack” others’ secret messages in ciphertext and convert them back to plaintext.
Objectives
- To have fun.
- To practice loops.
- To gain experience with strings.
- To become better acquainted with functions and libraries.
- To allow you to dabble in cryptography.
WARNING: Beware the autograder’s ability to detect cheating, see Honor Code for further information.
Honor Code
Collaboration Policy and the Honor Code
You are encouraged to:
- Give or receive help in understanding course concepts covered in lecture or lab.
- Practice and study with other students to prepare for assessments or exams.
- Consult with other students to better understand project specifications.
- Discuss general design principles or ideas as they relate to projects.
- Help others understand compiler errors or how to debug parts of their code.
To clarify the last item, you are permitted to look at another student’s code to help them understand what is going on with their code. You are not allowed to tell them what to write for their code, and you are not allowed to copy their work to use in your own solution. If you are at all unsure whether your collaboration is allowed, please contact the course staff via the admin form before you do anything. We will help you determine if what you’re thinking of doing is in the spirit of collaboration for EECS 183.
The following are considered Honor Code violations:
- Submitting others’ work as your own.
- Copying or deriving portions of your code from others’ solutions.
- Collaborating to write your code so that your solutions are identifiably similar.
- Sharing your code with others to use as a resource when writing their code.
- Receiving help from others to write your code.
- Sharing test cases with others if they are turned in as part of your solution.
- Sharing your code in any way, including making it publicly available in any form (e.g. a public GitHub repository or personal website).
The full collaboration policy can be found in the syllabus.
Grading
-
10 points testing. Write a test suite in
test.cpp
. -
60 points correctness. To what extent does your code implement the features required by our specification? To what extent is your code consistent with our specifications and free of bugs?
-
10 points style. To what extent is your code written well? To what extent is your code readable? Consult EECS 183 Style Guide and check the Style Checklist at the end of this project’s specification for some tips!
-
If your last submission is on Wednesday, October 16th you will receive a 5% bonus on your autograder score. If your last submission is on Thursday, October 17th, you will receive a 2.5% bonus.
Submission Rules
- You get 4 submissions to the autograder per day, with feedback. Beyond that, you get one additinal “bonus” submission per autograder, meaning that you get one extra submission with feedback, one time during the project period.
Remember that we grade your BEST submission for style. If multiple submissions are tied in score, we take the last of those.
Working with a Partner
- For Projects 3 and 4, you may choose to work with one other student who is currently enrolled in EECS 183.
- You may change partners between projects, e.g., you may have a different partner for project 3 than for project 4.
- You may not change partners during a project.
- Although you are welcome to work alone if you wish, we encourage you to consider partnering up for Project 3. If you would like a partner but don’t know anyone in the class, we encourage you to use the Search for Teammates post on Piazza if you want to find someone! Please make sure to mark your search as Done once you’ve found a partner.
- As a further reminder, a partnership is defined as two people. Outside of your partnership, you are encouraged to help each other and discuss the project in English (or in some other human language), but don’t share project code with anyone but your partner.
- To register a partnership on the autograder, go to the autograder link for the project and select “Send group invitation”. Then, add your partner to the group by entering their email when prompted. They will receive a confirmation after registration, and must accept the invitation before the partnership can submit. You must choose whether or not to register for a group on the autograder before you can submit. If you select the option to work alone, you will not be able to work with a partner later in the project. If a partnership needs to be changed after you register, you may submit an admin request.
- The partnership will be treated as one student for the purpose of the autograder, and you will not receive additional submits beyond the given four submits per day.
- If you decide to work with a partner, be sure to review the guidelines for working with a partner.
- If you choose to use late days and you are working in a partnership, review this section for how late days will be charged against each partner.
Starter Files
Download the starter files using this link. After unzipping, you’ll find these files:
utility.h A header file with declarations (aka prototypes) of the helper functions you’ll have to implement.
utility.cpp
Implementations of functions declared in utility.h
.
caesar.h A header file with declarations of the functions related to caesar cipher you’ll have to implement.
caesar.cpp
Implementations of functions declared in caesar.h
.
vigenere.h A header file with declarations of the functions related to vigenere cipher you’ll have to implement.
vigenere.cpp
Implementations of functions declared in vigenere.h
.
polybius.h A header file with declarations of the functions related to polybius cipher you’ll have to implement.
polybius.cpp
Implementations of functions declared in polybius.h
.
ciphers.cpp
A function that allows the user to encrypt and decrypt messages. This
file uses functions from utility.h
, caesar.h
, vigenere.h
,
polybius.h
.
start.cpp A program that allows you to select between executing your tests in test.cpp and using the ciphers you have created.
Additionally, you’ll be working with this file that you’ll have to create yourself:
test.cpp
A test suite for functions declared in utility.h
, caesar.h
,
vigenere.h
, polybius.h
. Its job is to reveal bugs that someone (e.g.
you or staff) could have made while implementing those functions. All
testing should be done by printing to the standard output. Note that
there will be no main
function in this file.
NOTE: The starter code will not compile until you:
- Create a test.cpp file with a function named
startTests()
. Your project will not compile until you have created your test.cpp. More details can be found in the section Creating test.cpp.
Suggested Timeline
As an approximate timeline, you will be on track if by:
- September 30: Starter code downloaded and new project set up in IDE. All necessary file(s) created. Code compiles on your IDE. Code compiles on the autograder (submit to verify). You’ve read through the spec.
- October 4: Functions in
utility.cpp
implemented, fully tested, and passed on the autograder. Associated testing functions intest.cpp
implemented, working, and catching the associated bugs on the autograder. Suggested order:toUpperCase()
,removeNonAlphas()
,charToInt()
,removeDuplicate()
. - October 9: Functions in
caesar.cpp
implemented, fully tested, and passed on the autograder. Associated testing functions intest.cpp
implemented, working, and catching the associated bugs on the autograder. Necessary order:shiftAlphaCharacter()
,caesarCipher()
- October 11:
vigenereCipher()
implemented, fully tested, and passed on the autograder. Associated testing function intest.cpp
implemented, working, and catching the associated bug(s) on the autograder. - October 14: Functions in
polybius.cpp
implemented, fully tested, and passed on the autograder. Associated testing functions intest.cpp
implemented, working, and catching the associated bugs on the autograder. Suggested order:mixKey()
,fillGrid()
,findInGrid()
,polybiusSquare()
. Began working onciphers()
. - October 15:
ciphers()
and all other code should be completed. Debugging in progress. Passing all individual function tests, 80% or higher on autograder. - October 16: Last day to still get 5% extra credit for your project 3 submission!
- October 18: Final due.
- If at any point your code is not working as intended checkout the troubleshooting checklist
Warm-up
To make working on this project easier and more fun, be sure you’re able to answer the following questions:
- Recall our friend modulo,
%
. What does it do? - What’s the difference between a
char
and astring
? - How can you figure out the length of a given string?
- Suppose you have a variable of type
string
calledword
, and thatword
is of some positive length.- Without knowing
word
’s length in advance, how could you print its first character? - How about the last character?
- How would you print
word
’s nth character?
- Without knowing
- How can you represent an empty string, i.e. a string of length 0?
-
Suppose you somehow ended up with this code:
string firstName = "Julius"; string lastName = "Caesar"; // ...
and that you’d like to have another
string
,fullName
that would join (i.e. concatenate)firstName
andlastName
, so as to get"Julius Caesar"
. How best to do this? - Surf on over to http://www.asciitable.com and make note of
Dec
andChar
columns.- Recall from lecture that characters, such as letters,
punctuation marks and digits are represented by a number in the
computer. So to store the character
A
, the computer is really storing the number65
. Similarly it stores97
to representa
. Because of this property, you can incrementchar
s and do arithmetic with them just like you can withint
s.- Note You should never refer to ASCII values when working with
char
s. Instead, always use the alphanumeric or symbol representation. For example
// do NOT use numerical ASCII values in your code, like below char ch = 65; if (ch == 66) { .... } // Instead, use the alphanumeric representation, like below char ch = 'A'; if (ch == 'B') { .... }
- Note You should never refer to ASCII values when working with
-
What is the value of
character
at the end of this code’s execution?char character = 'A'; character += 1; character += 'A' - 'a';
- How would you print all the letters in the alphabet, A through Z, with just a couple lines of code (without hardcoding all those letters)?
- Recall from lecture that characters, such as letters,
punctuation marks and digits are represented by a number in the
computer. So to store the character
- What’s the difference between
0
and'0'
? - How can you determine if a character is a lowercase letter?
- How can you determine if a character is an uppercase letter?
- How can you determine if a character is alphanumeric or alphabetical?
Getting Started
Multiple Files
-
Most programs in the real world are written in more than just one file, to break down the functionality into smaller parts and to keep the program’s organization clean.
-
For Projects 1 and 2, you worked with just one file, such as
rps.cpp
. It had amain()
function, where the execution began, and then some other functions that were called frommain()
or from other functions. But as a program gets more complicated, the file becomes longer and it becomes difficult to organize and test the program. -
And so a common practice is to put (at least some) functions into separate files. The functions’ declarations (aka prototypes) go in what’s known as a header files that end in
.h
and the functions’ definitions (aka implementations) go in.cpp
files. Each.cpp
file that implements functions will#include
its respective.h
file that contains the functions’ declarations.Then the program will contain one other
.cpp
file (without a header file) that will contain amain
function that drives the program. This.cpp
file will#include
any header files that contain the definition of any functions that it might need.
Creating a Project
-
In the Distribution Code you’ll find
utility.h
,caesar.h
,vigenere.h
, andpolybius.h
that has declarations for some functions. You’ll also findcaesar.cpp
,vigenere.cpp
,polybius.cpp
, andutility.cpp
whose job is to implement those functions. You will also findciphers.cpp
which is the driver that makes use of the ciphers you have implemented. Finally, there is a filestart.cpp
which contains themain()
function for the project. This function will allow you to select executing your test cases or using the ciphers you have written.NOTE: Your project will not compile or run until you have created a
test.cpp
with astartTests()
function. You can see how to do this in the next Section, Test Suite. -
As usual, the code in
start.cpp
will print the project menu with options to either run the tests in test.cpp or call driver function for the project,ciphers()
. Enter 1 to select executing your test cases starting with thestartTests()
function intest.cpp
, enter 2 to select executing your ciphers starting with theciphers()
function inciphers.cpp
.test_toUpperCase()
, your program might run as follows (bold text represents your input):
-------------------------------
EECS 183 Project 3 Menu Options
-------------------------------
1) Execute testing functions in test.cpp
2) Execute ciphers() function to use ciphers
Choice --> 1
Executing your test case
Now testing function toUpperCase()
Expected: "HELLO WORLD!", Actual: "HELLO WORLD!"
Expected: "HI THERE 123", Actual: "HI THERE 123"
Expected: "&&GO BLUE**", Actual: "&&GO BLUE**"
-
To begin, create a new project in Xcode or in Visual Studio and add
caesar.h
,vigenere.h
,polybius.h
,utility.h
,caesar.cpp
,vigenere.cpp
,polybius.cpp
,utility.cpp
, andciphers.cpp
to your project. Be sure that the files are copied and stored in the project directory.WARNING: When adding files to your project, be sure that the files are copied and stored in the project directory!
Test Suite
Creating test.cpp
-
As you write code, it’s important to test it! Catching and fixing bugs early is much easier than later on; this will save you hours when you work. So you’ll be required to create and submit a test suite for this project.
NOTE: The best practice is to write tests before even implementing functions. Writing tests will make implementing the function faster/easier, PLUS it is infinitely satisfying to be able to test a function immediately once you’ve implemented it.
-
Create a new file and call it
test.cpp
. At the top of the file, put a multiline comment with the project’s name, your name and uniqname, your partner’s name and uniqname, if you have one, and a short description for this test file. This file will in fact test the functions declared inutility.h
,caesar.h
,vigenere.h
, andpolybius.h
so be sure to add these lines:#include "utility.h" #include "caesar.h" #include "vigenere.h" #include "polybius.h"
after the multiline comment. Note that the filename is enclosed in double quotes
""
and not angle brackets<>
, which means that it’s a local file and not a system library.WARNING: Make sure that it’s
#include "utility.h"
. Don’t#include "utility.cpp"
.NOTE: If you’re using Xcode and it gave you
main.cpp
with amain
function, you won’t need it! So be sure to delete it. Or just renamemain.cpp
(from Xcode) totest.cpp
; this way you don’t have to create a new file. -
Next, write a
startTests
function intest.cpp
. This file will test functions declared inutility.h
,caesar.h
,vigenere.h
, andpolybius.h
via standard output, so you’ll be calling those functions many times. Here’s a good way to start it:#include "utility.h" #include "caesar.h" #include "vigenere.h" #include "polybius.h" #include <iostream> #include <string> using namespace std; void testShiftAlphaCharacter(); void startTests() { testShiftAlphaCharacter(); // Repeat for all other functions to be tested return; } void testShiftAlphaCharacter() { cout << "Now testing function ShiftAlphaCharacter()" << endl; cout << "Expected: 'a', Actual: '" << shiftAlphaCharacter('a', 0) << "'" << endl; cout << "Expected: 'b', Actual: '" << shiftAlphaCharacter('a', 1) << "'" << endl; cout << "Expected: 'd', Actual: '" << shiftAlphaCharacter('b', 2) << "'" << endl; return; }
-
shiftAlphaCharacter
does not print anything, it just returns a character. If you were to just call this function, nothing would be printed to the console. ButshiftAlphaCharacter
does return achar
. And so in order to check the correctness of this function’s implementation, you have to print its return value. -
When you test these functions, you may wish to pay close attention to the Requires clause of each function. For example, the Requires clause for
shiftAlphaCharacter
functions “requires” thatc
is an alphabetical character. This means that you can assume that this function will always receive an argument that is an alphabetical character. Furthermore, you should not be calling it from the test suite with a value that violates the Requires clause. Doing so will cause your test suite to fail the autograder. For example, don’t callshiftAlphaCharacter
with an argument of'@'
.WARNING: If you submit a test case that violates the Requires clause, we will stop grading that submission and you will receive a very low score.
-
As you work on the functions in
utility.cpp
, you should write some test cases first, then write the implementation, and then run the program to check if the implementation is correct. You should repeat this with functions incaesar.cpp
,vigenere.cpp
, andpolybius.cpp
.
List of Functions to Test
Here the a list of functions you will need to test in test.cpp.
toUpperCase()
removeNonAlphas()
removeDuplicate()
charToInt()
shiftAlphaCharacter()
caesarCipher()
vigenereCipher()
fillGrid()
mixKey()
findInGrid()
polybiusSquare()
Submit Frequently
-
As you progress through the project, we encourage you to submit after completing each section of the project. Doing so will help to ensure that you have written each part correctly before moving onto building the next.
-
When you submit
test.cpp
, we will compile and run it with our correct implementation ofutility.cpp
,caesar.cpp
,vigenere.cpp
, andpolybius.cpp
and with our buggy implementation ofutility.cpp
,caesar.cpp
,vigenere.cpp
, andpolybius.cpp
so as to generate two different outputs. We will then compare two outputs. If there is any difference, you’ve successfully exposed a bug! The autograder does not go into the details of what the difference is, it only sees if there exists a difference. -
Remember that some functions do not print anything on their own; we have to print their return value, as with the function
shiftAlphaCharacter()
:cout << shiftAlphaCharacter('a', 0) << endl; cout << shiftAlphaCharacter('b', 2) << endl; cout << shiftAlphaCharacter('X', 5) << endl; cout << shiftAlphaCharacter('X', 50) << endl;
-
After you submit your test suite, you might see output that looks like this:
That means that your test suite exposed 1 out of th 13 bugs in the staff’s “buggy” implementations of the project and your score for the test suite is 0.9 out of 10 points. The total points you can earn on test.cpp is capped at 10 points. You do not need to find all of the bugs to receive all of the points.
Bugs To Expose
There are a total of 13 unique bugs to find in our implementations. Your tests do not need to expose all of the bugs to receive full points for the project. The autograder will tell you the names of the bugs that you have exposed, from the following set:
- CAESAR_SHIFTALPHACHARACTER1
- CAESAR_SHIFTALPHACHARACTER2
- CAESARCIPHER
- UTILITY_CHARTOINT
- UTILITY_REMOVEDUPLICATE
- UTILITY_REMOVENONALPHAS1
- UTILITY_TOUPPERCASE
- POLYBIUS_FILLGRID
- POLYBIUS_FINDINGRID
- POLYBIUS_MIXKEY
- POLYBIUSSQUARE
- UTILITY_CHARTOINT
- UTILITY_REMOVEDUPLICATE
- UTILITY_REMOVENONALPHAS2
- UTILITY_TOUPPERCASE
- VIGENERECIPHER
Helper functions
After your code compiles, your next task in this project is to indulge in writing functions in
utility.cpp
. These functions will serve as helper functions in
caesar.cpp
, vigenere.cpp
, and polybius.cpp
Remember to write your testing function for each
function before you write the function itself. For example, write test_toUppercase()
(in test.cpp
)
before you write the implementation of toUpperCase()
.
toUpperCase()
string toUpperCase(string original);
-
This function converts all alphabetical characters in the string
original
to uppercase. -
As an example of how this function works, suppose in
test.cpp
you call it and print its return value like this:cout << toUpperCase("Diag @ 11 p.m.") << endl;
Then the following should print:
DIAG @ 11 P.M.
removeNonAlphas()
string removeNonAlphas(string original);
-
This function removes all non-alphabetical characters from the string
original
. -
As an example of how this function works, suppose in
test.cpp
you call it and print its return value like this:cout << removeNonAlphas("Diag @ 11 p.m.") << endl;
Then the following should print:
Diagpm
removeDuplicate()
string removeDuplicate(string original);
-
This function removes all duplicate characters except the first occurrence of it from the string
original
. -
As an example of how this function works, suppose in
test.cpp
you call it and print its return value like this:cout << removeDuplicate("HELLOWORLD") << endl;
Then the following should print:
HELOWRD
charToInt()
int charToInt(char original);
-
This function converts the character
original
to its integer representation. -
As an example of how this function works, suppose in
test.cpp
you call it and print its return value like this:cout << charToInt('1') << endl;
Then the following should print:
1
cctype Library Functions
You are allowed and encouraged to use functions from the cctype library shown in the Character operations section in zyBooks.
WARNING: Do not compare boolean values to
true
orfalse
in a conditional expression.
When using these functions, the note below the table is critical
in understanding how to use the functions like isalpha
, isdigit
, and isspace
.
As noted in zyBooks, for functions like isalpha
, false is zero and true is non-zero.
This means you must never do the following:
if (isalpha(someCharVariable) == true)
Instead you should do something like this:
if (isalpha(someCharVariable))
to_string Function
You may find the function to_string
helpful in this project.
The function takes as input an integer (it also works for double
but that is not relevant for this project),
and returns a string with characters of text representation of the integer input.
Note this does not work to convert a char to a string.
// Example: converting an integer to a string using to_string
int course = 183;
// string word will have the value "183"
string word = to_string(course);
// prints 183
cout << word << endl;
Ciphers
IMPORTANT: As you implement the functions in the next section, you will find it helpful to refer to the Function Table linked at the bottom of the spec. The table contains the relationship between the functions below, i.e., which functions are called by others.
Art of Cryptography
- This type of “art” deals with scrambling information, so that
passwords, credit cards and other sensitive data are all the more
secure. An example with which you might already be somewhat familiar
is HTTPS, a secure version of HTTP (a protocol that web browsers use
to communicate with servers). When a browser requests a webpage from
a server via HTTPS (and the address of that webpage will begin with
https://
), all the data flowing between the browser and the server is encrypted (i.e. converted into seemingly meaningless characters). This is useful for logging into websites like Facebook, for credit card purchases on online stores such as Amazon, and especially so for accessing account information on banks’ websites.
caesar.cpp
Cæsar
-
History holds that Julius Caesar protected sensitive messages by “rotating” each letter by 3 positions, so
A
becameD
,B
becameE
, …,Z
becameC
: -
This type of cipher is know as a substitution cipher, i.e. each letter is substituted with another. In the case of Caesar’s cipher, we have a secret key that’s known only by those who are supposed to know the information that is shared. This secret key (
k
) is used to rotate (i.e. shift) each letter byk
places, wrappingA
toZ
anda
toz
as needed. -
For example, suppose that the secret key is 10 and the plaintext message is
Meet me at the Diag at 11 p.m.
We would encrypt this message by shifting each letter 10 places:Meet me at the Diag at 11 p.m. Wood wo kd dro Nskq kd 11 z.w.
Notice how
M
becameW
, sinceW
is 10 characters away fromM
. Similarly,t
becamed
.t
is the 20th letter in the alphabet (and the English alphabet has just 26 letters), so after we get toz
(26th letter), we go back toa
and go through three more letters to findd
. -
Some additional notes:
-
A negative key would shift the letters back. So if the key were -3,
E
would becomeB
andA
would becomeX
. -
Because the English alphabet has just 26 letters, keys of -25, 1, 27, 53, etc. are equivalent.
-
This cipher will encrypt only uppercase and lowercase letters. This means that only alphabetical characters are shifted. Furthermore, uppercase letters will remain uppercase and lowercase letters will remain lowercase after shifting. All other characters will remain the same.
-
Decrypting Caesar cipher is fairly simple: it suffices to shift the letters the other way.
-
-
For more information (and history), check out http://en.wikipedia.org/wiki/Caesar_cipher.
NOTE: Most of this project’s specification has been encrypted with a key of 26, which is twice as secure as a key of 13.[1]
- And now, an overview of functions that you must implement in
caesar.cpp
. Note that the functions that follow do not print anything on its own; instead they return the result.
shiftAlphaCharacter()
char shiftAlphaCharacter(char c, int n);
-
This function “requires” that the first argument that’s passed in,
c
, be a letter. So you may assume that it will only be called with an uppercase or a lowercase letter. And remember not to call this function with anything but alphabetical characters, not even in your test suite! -
Shifting a character is illustrated by the same diagram you saw earlier. Suppose that you’re working with uppercase characters and
n
is 3. Then the characters would be shifted like this: -
Note that the letter must remain in the same case after you shift it. So if
c
is lowercase, it will remain lowercase when this function returns it; ifc
is uppercase, it will remain uppercase. -
And this is how you can test
shiftAlphaCharacter
intest.cpp
. Since the function itself does not print anything, we have to print its return value:cout << shiftAlphaCharacter('a', 0) << endl; cout << shiftAlphaCharacter('b', 2) << endl; cout << shiftAlphaCharacter('X', 5) << endl; cout << shiftAlphaCharacter('X', 50) << endl;
You should get this output:
a d C V
caesarCipher()
string caesarCipher(string original, int key, bool encrypt);
-
As its name suggests, this function encrypts or decrypts the string that’s passed in using the algorithm described above.
-
For example, suppose
original
is the string"Meet me at the Diag at 11 p.m."
,key
is 42 andencrypt
istrue
. Then callingcaesarCipher
and printing its return value intest.cpp
cout << caesarCipher("Meet me at the Diag at 11 p.m.", 42, true) << endl;
would cause the following to be printed:
Cuuj cu qj jxu Tyqw qj 11 f.c.
To decrypt a message, call
caesarCipher
withencrypt
set tofalse
:cout << caesarCipher("Cuuj cu qj jxu Tyqw qj 11 f.c.", 42, false) << endl;
which would print
Meet me at the Diag at 11 p.m.
IMPORTANT: Don’t forget to keep writing tests in
test.cpp
for functions we declared incaesar.h
!
For creating and verifying test cases for your caesarCipher, this website may be helpful.
vigenere.cpp
Vigenère
-
As you might imagine, Caesar cipher is not all that strong, since it only takes to go through at most 25 different keys to break it. (Interested in breaking the Caesar cipher? Check out this project’s S’more!) For this reason, the world (or the French?) came up with Vigenère cipher. It dates to the fifteenth century and is one of the truly great breakthroughs in the development of cryptography. For more information, check out http://en.wikipedia.org/wiki/Vigenère_cipher.
-
Vigenère cipher improves upon Caesar cipher by shifting letters using different keys. This sequence of keys is known as a keyword. Each letter in the keyword represents by how far the corresponding letter in the original message will be shifted (
A
anda
represent 0,B
andb
represent 1,Z
andz
represent 25). -
For example, suppose you still want to send that same secret message,
Meet me at the Diag at 11 p.m.
But this time, you’re more careful and are using Vigenère cipher with the keySquirrel!
Here’s how to encrypt:plaintext: Meet me at the Diag at 11 p.m. key: SQUI RR EL SQU IRRE LS Q U ciphertext: Euyb dv ee lxy Lzrk ll 11 f.g.
-
We first converted each letter of the keyword to uppercase and removed all non-alphabetic characters. We also applied the key just to letters and repeated the keyword after its last letter. Since
S
is 18 characters away fromA
,M
is shifted by 18. -
So putting it all together, you will need to do the following:
- Convert all letters in the keyword to uppercase.
- Remember to strip all non-alphabetic characters from the keyword.
- The keyword can be of any length greater than 0 and repeats after its last character.
- Apply the keyword only to alphabetical characters in the original message.
- As in the Caesar cipher,
Z
wraps toA
andz
wraps toa
. - Decrypting would shift the letters backward.
- This cipher will encrypt and decrypt only uppercase and lowercase letters. This means that only alphabetic characters will be shifted. Furthermore, uppercase letters will remain uppercase and lowercase letters will remain lowercase after shifting. All other characters will remain the same.
vigenereCipher()
string vigenereCipher(string original, string keyword, bool encrypt);
-
Notice that this function “requires” that
keyword
contain at least one alphabetical character. This means that you every time you call this function the string you provide as the second argument needs to have at least one letter in it. Make sure this is true for you function calls when testing the function, as well as anywhere else in your code. -
When you implement this function, be sure to follow the rules outlined above.
-
As an example of how this function works, suppose in
test.cpp
you try to encrypt the string"Meet me at the Diag at 11 p.m."
with the keyword"Squirrel!"
call it and print its return value like this:cout << vigenereCipher("Meet me at the Diag at 11 p.m.", "Squirrel!", true) << endl;
Then the following ciphertext should print:
Euyb dv ee lxy Lzrk ll 11 f.g.
Remember that decrypting shifts the letters backward! As an example:
cout << vigenereCipher("Euyb dv ee lxy Lzrk ll 11 f.g.", "Squirrel!", false) << endl;
And this is the output:
Meet me at the Diag at 11 p.m.
polybius.cpp
Polybius Square
-
While shifting characters based on a key is common in ciphers, another common approach is to construct a grid as a cipher. As the name may suggest, the Polybius Square is a device invented by the Ancient Greek historian and scholar Polybius.
-
Although Polybius did not intend for his device to be used as a cipher, the Polybius Square is said to have been used in the form of the “knock code” to signal messages between cells in prisons by tapping the numbers on pipes or walls.
-
The original grid consists of the English alphabet and the digits 0 through 9.
Each letter is then represented by its coordinates in the grid, with the row number first and then the column number. For example,
"EECS"
becomes"04040230"
in the original grid above. -
The encryption process using a Polybius Square begins with generating a Mixed Square, using a keyword. Once the Mixed Square is generated, we replace each letter with the “coordinates” of the letter within the grid, reading across first and then down (i.e. row and then column).
-
As an example, we shall encrypt the plaintext “EECS” with the keyword “POLYBIUS”.
-
First we make the Mixed Square using the keyword. We start by filling in the squares in the grid with the letters of the keyword, ignoring repetitions, and then continue with the rest of the alphanumerical letters in its original order.
-
With the Square complete, we simply find each plaintext letter in the grid, and replace it with its coordinates. So “E” becomes “15”, “C” becomes “13”, and “S” becomes “11”. With this, we get the ciphertext “15151311”. Note that each alphanumeric character is always represented by a pair of digits.
-
Decryption works in the reverse order, by translating the coordinates to its corresponding letter in the grid.
-
The Mixed square is generated in exactly the same way as we did before.
-
Imagine we received the ciphertext “435445” and the key is “POLYBIUS” again. Then “43” becomes “1”, “54” becomes “8”, and “45” becomes “3”. With this, we get the plaintext “183”.
IMPORTANT: Note that the grid does not represent any non-alphanumerical character. This means that such characters cannot be encrypted. However spaces are allowed in the plaintext, and should be represented as spaces in the ciphertext.
fillGrid()
void fillGrid(char grid[SIZE][SIZE], string content);
-
Notice that this function “requires” that
content
be of length of 36. This means that you should never be passing a string that has a length that does not equal 36. -
As an example of how this function works, suppose in
test.cpp
you try to fill the grid with the constant stringALNUM
defined inutility.h
. To test that your grid has been filled correctly, you should call theprintGrid
function implemented for you inutility.cpp
.NOTE:
SIZE
is a constant inutility.h
that represents the maximum dimension of the grid, which has the value 6.char grid[SIZE][SIZE]; fillGrid(grid, ALNUM); printGrid(grid);
Then the following content should print:
--- --- --- --- --- --- | A | B | C | D | E | F | --- --- --- --- --- --- | G | H | I | J | K | L | --- --- --- --- --- --- | M | N | O | P | Q | R | --- --- --- --- --- --- | S | T | U | V | W | X | --- --- --- --- --- --- | Y | Z | 0 | 1 | 2 | 3 | --- --- --- --- --- --- | 4 | 5 | 6 | 7 | 8 | 9 | --- --- --- --- --- ---
We have implemented
printGrid()
for you, and its RME can be found inutility.h
. Feel free to use this helper function when testing other functions inpolybius.h
.
mixKey()
string mixKey(string key);
-
Notice that this function “requires” that
key
does not contain duplicate characters and consists of only uppercase alphabet and numbers. This means that you do not have to handle duplicate characters or lowercase alphabet. You will later handle duplicate characters and lowercase alphabet inciphers.cpp
. -
As an example of how this function works, suppose in
test.cpp
you try to mix the key"POLYBIUS"
, call it, and print its return value like this:cout << mixKey("POLYBIUS") << endl;
Then the following content should print:
POLYBIUSACDEFGHJKMNQRTVWXZ0123456789
NOTE: Make use of the constant string
ALNUM
defined inutility.h
. You should always begin with the alphabet and digits in their original order.
findInGrid()
string findInGrid(char c, char grid[SIZE][SIZE]);
-
Notice that this function “requires” that
c
is an uppercase alphabet or a digit. This means that you do not have to handle lowercase alphabet. -
As an example of how this function works, suppose in
test.cpp
you callfindInGrid
with'A'
asc
, a grid filled as follows, and printing its return value like this:char grid[SIZE][SIZE]; fillGrid(grid, ALNUM); cout << findInGrid('A', grid) << endl;
Then the following content should print:
00
polybiusSquare()
string polybiusSquare(char grid[SIZE][SIZE], string key, string original, bool encrypt);
-
Notice that this function “requires” that
key
does not contain duplicate characters and consists of only uppercase alphabet and numbers. This means that you do not have to handle duplicate characters or lowercase alphabet. You will later handle duplicate characters and lowercase alphabet inciphers.cpp
. -
When you implement this function, be sure to follow the rules outlined above.
-
As an example of how this function works, suppose in
test.cpp
you try to encrypt the string"EECS 183 is the best"
with the keyword"183"
call it and print its return value like this:char grid[SIZE][SIZE]; cout << polybiusSquare(grid, "183", "EECS 183 IS THE BEST", true) << endl;
Then the following ciphertext should print:
11110533 000102 1533 341411 04113334
Remember that decrypting uses the same grid as encrypting! As an example:
char grid[SIZE][SIZE]; cout << polybiusSquare(grid, "183", "11110533 000102 1533 341411 04113334", false) << endl;
And this is the output:
EECS 183 IS THE BEST
IMPORTANT: Note that spaces are allowed in the original message, and they must stay as spaces in the encrypted message as well.
ciphers.cpp
WARNING: Sanity check! At this point,
utility.cpp
,caesar.cpp
,vigenere.cpp
, andpolybius.cpp
should have implementations of all functions that we declared inutility.h
,caesar.h
,vigenere.h
, andpolybius.h
, andtest.cpp
should have a test suite for those functions. If you have not yet submitted, we highly encourage you to do so, unless you are starting late and it is close to the deadline so you have few submissions remaining.
Overview
-
Note: The autograder tests for
ciphers.cpp
test only your ciphers.cpp file. The autograder tests for ciphers.cpp will use EECS 183 staff implementations for the other files, likecaesar.cpp
. A common problem students encounter in implementing functions in ciphers.cpp is violating the Requires clause of RMEs for the functions in the other files, like caeser.cpp, vigenere.cpp, and polybius.cpp. If your solution seems to work on your computer, but fails test cases in the autograder for ciphers.cpp, check very carefully that you are not providing an argument from a function call in ciphers.cpp that would violate the Requires of the RME for that function. -
As your last task involving ciphers, create a program that asks the user for a cipher (Caesar, Vigenere, or Polybius), whether the user would like to encrypt or decrypt a message, asks for the message and then for a key. Finally, the program should print the encrypted or decrypted message, as specified by the user.
-
At the top of the file, you’ll notice this line that lets
ciphers.cpp
use functions that are declared inutility.h
,caesar.h
,vigenere.h
, andpolybius.h
:#include "utility.h" #include "caesar.h" #include "vigenere.h" #include "polybius.h"
after the multiline comment.
-
Implement the
ciphers
function inciphers.cpp
. Let us recommend this structure:void ciphers() { // ask user for cipher (Caesar, Vigenere, or Polybius) // ask user to encrypt or decrypt // get message from user // get key or keyword from user // encrypt or decrypt message using selected cipher and key(word) // print encrypted/decrypted message }
-
When you ask the user for input be sure to use these prompts, followed by a single space, in this order:
When encrypting,
Choose a cipher (Caesar, Vigenere, or Polybius): Encrypt or decrypt: Enter a message: What is your key: The encrypted message is:
Or when decrypting,
Choose a cipher (Caesar, Vigenere, or Polybius): Encrypt or decrypt: Enter a message: What is your key: The decrypted message is:
Expect the user to be bad at capitalization and accept input ignoring the case, such as
cAEsar vigenEre POLYBIus ENCRYPT decrypt
You must also accept
c
,v
,p
,e
andd
(or uppercase versions) as valid input. If an invalid cipher type or mode (encrypt or decrypt) is entered, you must print out the messageInvalid cipher!
orInvalid mode!
.We recommend using
getline
to readstring
s, so as to read more than just the first word of the keyword or message.NOTE: For the caesar cipher, you can assume the user will always enter a key that is an integer when they have selected to use a Caesar cipher.
-
Then print the encrypted/decrypted message on the same line.
-
Because you’ll probably be repeating some code in
ciphers.cpp
, like prompting the user for a string and reading that string, it’s a good idea to factor out common functionality into separate functions. Declare those functions inciphers.cpp
(not inutility.h
) aboveciphers
and implement them belowciphers
. Be sure to write RME comments above those functions’ declarations to maximize style points.
WARNING: Be sure not to modify any of the header files, since we’ll be using the original version when grading your project.
- So that we can automate some tests of your code, your program must behave per the examples below. Assumed that the red underlined text is what some user has typed.
Error handling in ciphers()
WARNING: This section applies to the
ciphers()
function inciphers.cpp
. These are not instructions for how to implement each cipher - caesar, vigenere, or polybius. These are instructions for how to handle user input within theciphers()
function so that your ciphers.cpp does not violate the Requires clause of the RMEs for yourcaesarCipher
,vigenereCipher
, andpolybiusSquare
functions.
Your ciphers()
function must not violate the Requires clause of the RME for any functiuon.
Since the user may enter values that may do so, you must catch these and either print an error message,
or modify the input values to conform to the Requires clause before calling the corresponding cipher function.
Here are the errors you must handle in ciphers()
:
-
If the user enters an invalid cipher type (anything other than what is defined above), print
Invalid cipher!
, and exit the program, i.e.,return;
fromciphers()
. -
If the user enters an invalid mode (anything other than what is defined above), print
Invalid mode!
, and exit the program, i.e.,return;
fromciphers()
. -
For Caesar Cipher, you can assume that the user will always enter an integer-valued key when they have selected to use the Caesar Cipher. You do not need to handle the case where they enter a non-integer key.
-
For Vigenere Cipher, you must ensure that the keyword contains at least one alphabetical character. If not, print
Invalid key!
, and exit the program, i.e.,return;
fromciphers()
. -
For Polybius Square, you must ensure that the message is valid. That is, you must verify that all characters are alphanumeric or a space. Lowercase letters are valid, but the message must be converted to uppercase before calling the polybiusSquare function.
If an invalid message is entered, you must print
Invalid message!
, and exit the program, i.e.,return;
fromciphers()
. -
For Polybius Square, you must ensure that the key is valid. That is, you must verify that all characters are alphanumeric, all characters are uppercase, and that there are no duplicates. To ensure this, the key must be converted to uppercase, and duplicates must be removed from the key before calling the polybiusSquare function. Non-alphanumeric characters in the key should not be removed, but instead should result in an error message.
If an invalid key is entered - containing anything other than alphanumeric characters - you must print
Invalid key!
, and exit the program, i.e.,return;
fromciphers()
. -
HINT: think of which functions in utility.h you can use to accomplish the above requirements.
Sample Output
When you run ciphers.cpp
, it should behave per the examples below.
Assume that the red underlined text is what some user has typed.
NOTE: The following sample runs do not include the menu selection detailed in Creating a Project.
Sample Run 1
Choose a cipher (Caesar, Vigenere, or Polybius): caesar Encrypt or decrypt: encrypt Enter a message: I solemnly swear that I am up to no good. What is your key: 7 The encrypted message is: P zvsltusf zdlhy aoha P ht bw av uv nvvk.
Sample Run 2
Choose a cipher (Caesar, Vigenere, or Polybius): c Encrypt or decrypt: d Enter a message: P zvsltusf zdlhy aoha P ht bw av uv nvvk. What is your key: 7 The decrypted message is: I solemnly swear that I am up to no good.
Sample Run 3
Choose a cipher (Caesar, Vigenere, or Polybius): vigenere Encrypt or decrypt: decrypt Enter a message: U lgp'a os qaoxitk iaz ltvcfqq. Teoafoq ckwhtpd riady qh. What is your key: Mischief managed. The decrypted message is: I don't go looking for trouble. Trouble usually finds me.
Sample Run 4
Choose a cipher (Caesar, Vigenere, or Polybius): ViGenere Encrypt or decrypt: DECrypt Enter a message: U lgp'a os qaoxitk iaz ltvcfqq. Teoafoq ckwhtpd riady qh. What is your key: Mischief managed. The decrypted message is: I don't go looking for trouble. Trouble usually finds me.
Sample Run 5
Choose a cipher (Caesar, Vigenere, or Polybius): polybius Encrypt or decrypt: encrypt Enter a message: EECS 183 is the best What is your key: POLYBIUS The encrypted message is: 15151311 435445 0511 332215 04151133
Sample Run 6
Choose a cipher (Caesar, Vigenere, or Polybius): P Encrypt or decrypt: E Enter a message: EECS 183 is the best What is your key: polybius The encrypted message is: 15151311 435445 0511 332215 04151133
Sample Run 7
Choose a cipher (Caesar, Vigenere, or Polybius): hello
Invalid cipher!
Sample Run 8
Choose a cipher (Caesar, Vigenere, or Polybius): C Encrypt or decrypt: V Invalid mode!
Sample Run 9
Choose a cipher (Caesar, Vigenere, or Polybius): v Encrypt or decrypt: e Enter a message: EECS 183 is the best What is your key: 183 Invalid key!
Sample Run 10
Choose a cipher (Caesar, Vigenere, or Polybius): P Encrypt or decrypt: enCRYPT Enter a message: Life isn't about waiting for the storm to pass its about learning to dance in the rain Invalid message!
Sample Run 11
Choose a cipher (Caesar, Vigenere, or Polybius): PolyBius Encrypt or decrypt: EnCrypt Enter a message: The grasshopper lies heavy What is your key: YEET!!! Invalid key!
Sample Run 12
Choose a cipher (Caesar, Vigenere, or Polybius): Polybius Encrypt or decrypt: Encrypt Enter a message: The grasshopper lies heavy What is your key: 183EECS The encrypted message is: 341503 1433100505153031310333 23200305 1503104043
Sample Run 13
Choose a cipher (Caesar, Vigenere, or Polybius): Polybius Encrypt or decrypt: decrypt Enter a message: 341503 1433100505153031310333 23200305 1503104043 What is your key: 183EECS The decrypted message is: THE GRASSHOPPER LIES HEAVY
Sample Run 14
Choose a cipher (Caesar, Vigenere, or Polybius): Caesar Encrypt or decrypt: encrypt Enter a message: Do androids dream of electric sheep? What is your key: 9000 The encrypted message is: Hs erhvsmhw hvieq sj ipigxvmg wliit?
- Now you can send secret messages to your friends. (Although they’ll need the same program to decrypt your messages!)
IMPORTANT: Note that when selecting Polybius Square, the decrypted message is all uppercase, while the original message contained lower case letters. This is fine, as the grid we construct only supports uppercase letters.
Function Table
- The table below provides an outline of which other functions each function should call, if any.
WARNING: The table below does not include all cases of where to use the helper functions from
utility.cpp
. There are hints in the specification where to use your helper functions, and you should consider implementing these functions first.
File | Function | Other functions it should call |
---|---|---|
utility.cpp |
toUpperCase() |
Does not utilize any other functions |
utility.cpp |
removeNonAlphas() |
Does not utilize any other functions |
utility.cpp |
charToInt() |
Does not utilize any other functions |
utility.cpp |
removeDuplicate() |
Does not utilize any other functions |
caesar.cpp |
shiftAlphaCharacter() |
Does not utilize any other functions |
caesar.cpp |
caesarCipher() |
shiftAlphaCharacter() |
vigenere.cpp |
vigenereCipher() |
toUpperCase() , removeNonAlphas() , shiftAlphaCharacter() |
polybius.cpp |
mixKey() |
removeDuplicate() |
polybius.cpp |
fillGrid() |
Does not utilize any other functions |
polybius.cpp |
findInGrid() |
Does not utilize any other functions |
polybius.cpp |
polybiusSquare() |
mixKey() , fillGrid() , findInGrid() , charToInt() |
Here is an example of how to read the table:
caesarCipher()
should callshiftAlphaCharacter()
NOTE: Note that the functions under “Other functions it should call” only refer to the functions you will implement. Feel free to use library functions anywhere.
Style Checklist
To maximize your style points, be sure to follow this non-exhaustive checklist:
-
Review EECS 183 Style Guide and P3 Style Rubric. While all sections are relevant to this project, pay particular attention to Loops and Functions.
-
Be sure that your code is well-commented. It might at first seem that the algorithm is straight-forward and self-explanatory, but would you remember all the details a month from now? And it’s much easier for the staff to help you with your code if you have comments!
-
The RME comments must go above functions’ declarations (prototypes). This means that RMEs for functions declared in
utility.h
should stay inutility.h
. The “required” functions inutility.cpp
must have no RMEs. If you did, however, add you own helper functions inciphers.cpp
, be sure to include their RMEs above those functions’ declarations. However, you must not add any additional function definitions in the header files provided. -
Do not compare boolean values to
true
orfalse
in a conditional expression. That is, do not writeif (someBoolValue == true)
but instead useif (someBoolValue)
-
Make sure that you’re not redundantly repeating code. If you have large blocks of code that appear in several places, it might make sense to create a helper function. And certainly utilize functions such as
shiftAlphaCharacter
inside others. -
Don’t use
break
orcontinue
in loops. Loops should be controlled by a condition and should terminate when that condition is met. -
Remember to avoid magic numbers. Be sure that you don’t have numbers like 26, 32, 65 or 97 in your code!
-
And certainly don’t use global variables.
-
Remember that all lines must be 80 characters or less.
NOTE: We will not be grading style in
test.cpp
.
How to Submit
- Go to https://autograder.io/web/project/2684, where you will submit six files:
- caesar.cpp
- utility.cpp
- test.cpp
- ciphers.cpp
- polybius.cpp
- vigenere.cpp
IMPORTANT:
- Differences in whitespace can fail the autograder.
- Be sure to submit all parts of the project (including test.cpp).
- Ensure that you have included your (and your partner’s) name, your (and your partner’s) uniqname and a small description of the program in the header comments in all files submitted.
- You have four submissions to the autograder per day with feedback, and one additional “wildcard” submission to use once during the project.
- We will grade your best submission.
S’More Functionality
OPTIONAL: For more practice and fun, check out the S’More for Project 3.
With this optional challenge you can implement the functions in decrypt.cpp
to break encryption.