Project #2: The Vault

Part 1 Due: 0600 EDT on 24 Mar 2020
Entire Project Due: 0600 EDT on 03 Apr 2020

  Executive Summary

In this project you are writing a utility called "Vault" that provides secret, authenticated storage. The conceptual setup is "real world", although the particular cryptographic algorithms we use are not, because they are text-based historical techniques, rather than modern digital ones. The key points are:
  1. Data is stored by labels, so you give commands like "get the data associated with the label mystuff22".
  2. There are multiple users storing data.
  3. Users are authenticated with passwords so that nobody sees any data other than their own.
  4. Cryptography keeps the data secret, even from someone who can read the Vault file and who has a copy of the Vault program and its source code.
  5. The labels themselves are secured by encryption, so that someone who steals the file can get usernames but not data or the labels under which the data is stored.
This is probably a bit easier to see with an example:
file vault0 sample run
user joe shift+vigenere ?5d`54=22d5Boqqv
user ann shift+caesar 2018^midGO_NAVY_
data joe vigenere LjTmZ8o*s=CEWyi.q
data joe caesar z53y5vJKDIHDHO
data ann caesar instrument_french_horn
~/$ java Vault vault0
username:  joe
password:           ← password not echoed to the screen!
Access granted!
> labels
ssnum
combo
> get ssnum
892-88-1263
> quit

This project is designed to exercise your ability to write good Object-Oriented code in Java, which will include good use of encapsulation, data-hiding, inheritance, polymorphism, class hierarchies, interfaces, and exception handling. The project has three main pieces: encryption algorithms, hash algorithms, and the vault. Failing to do a good job of following OOP best-practices in producing your solutions to the first two parts will likely result in difficulty completing the subsequent parts.

Honor

The course policy and the instructions it references, most pertinently COMPSCIDEPTINST 1531.1D, spell out what kinds of assistance are permissible for programming projects. The instructor can give explicit permission for things that would otherwise not be allowed. For this project, you have explicit permission
  1. to get help from any Spring 2020 IC211 instructors (any assistance must be documented though), and
  2. to use general purpose Java resources online (though such use must be documented). Resources that might specifically address this project, like the code for programs that do encryption with the Vigenere cipher, are not allowed.
Put together the instructions referenced in the course policy and the explicit permissions above, and what you get is summarized as:
  • The only help you may receive on a project is from your instructor or the other IC211 instructors, and that help must be clearly cited. In turn, you cannot provide help to any IC211 students on this project.
  • Under no circumstance may you copy code and submit it as your own work. That is the definition of plagiarism.
  • You may not look at other people's project code, nor may you show your own code to others.
  • You can look at online resources for general purpose Java programming, but not for help writing anything that is specific to the functionality required of your project.


Rules and important extra information

  1. Labels, data, and passwords are restricted to ASCII characters in the range 42-122 inclusive, i.e. they may only include:
    *+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz

    The chosen encryption and hashing algorithms produce results that are in that restricted character set as well. Any encrypted entry in a vault-file and any user-entered label, data, or password that is outside of these bounds is an error and should result in some kind of message.

  2. Sometimes we will want to represent strings as String objects, other times as objects of type char[]. Just in case you didn't know here's how to convert back and forth:
    String str = "foo";
    char[] buff = str.toCharArray(); // convert String to char[]
    String rts = new String(buff);   // convert char[] to String
  3. You'll be converting chars to ints and ints to chars. Please remember that you must explicitly cast to do this in Java, e.g. (int)str.charAt(0) + 7, or (char)(i + 3).

  Part 1 (25pts): Encryption

The encryption we will be using in this project is symmetric encryption (see the original SI110 symmetric encryption notes). The three symmetric encryption algorithms you must support are clear, caesar, and vigenere (all described later). A Part 1 solution will be a program TestEncryptors that allows the user to encrypt a test message of their choice using any one of the three algorithms above. The program itself is not the important product here. Instead, correct implementations of the three encryption algorithms with a single coherent interface and a well-thought out system of error-handling is the real point.

example 1 example 2 example 3
~/$ java TestEncryptors
algorithm: clear
password : grumpy
message  : This_is_just_test:1234
plain : This_is_just_test:1234
cipher: This_is_just_test:1234
decryp: This_is_just_test:1234
~/$ java TestEncryptors
algorithm: caesar
password : grumpy
message  : This_is_just_test:1234
plain : This_is_just_test:1234
cipher: y<=G3=G3>IGH3H9GH_VWXY
decryp: This_is_just_test:1234
~/$ java TestEncryptors
algorithm: vigenere
password : grumpy
message  : This_is_just_test:1234
plain : This_is_just_test:1234
cipher: @_ceTg_VdghrKk_ei8nz-w
decryp: This_is_just_test:1234

Your project will benefit tremendously from following good object oriented design principals. To help you out, I'm going to impose more structure on this first part of the assignment. Here are the rules and recommendations:

  1. Start off with the program defined by the following three files, and make them the basis for your solution. They are provided to you, and you can (and should) modify them to suit your needs.
    • Encryptor.java - an interface for objects that provide encryption functionality.
    • Clear.java - an implementation of Encryptor providing the clear algorithm, which is to not change the plaintext at all.
    • TestEncryptors.java - a driver program that matches the basic input/output functionality of Part 1. As you implement more Encryptors, you just put an "add" line in for them, and they become part of the test driver.
    Download with the following:
    curl -O http://faculty.cs.usna.edu/IC211/docs/proj2/Encryptor.java
    curl -O http://faculty.cs.usna.edu/IC211/docs/proj2/Clear.java
    curl -O http://faculty.cs.usna.edu/IC211/docs/proj2/TestEncryptors.java
  2. Add an implementation of the Encryptor interface for the Caesar-shift cipher (algorithm name caesar): see Caesar-shift details.
    Add an implementation of the Encryptor interface for the Vigenere cipher (algorithm name vigenere): see Vigenere details.
  3. Each of these three implementations should throw exceptions when error situations occur: for the TestEncryptors program all errors should result in exceptions thrown out of main() and vomited onto the screen. The types and accompanying messages of these exceptions should be informative!
    Important: it's up to you to define a nice hierarchy of exceptions that will allow you to handle errors nicely in the coming sections.
  4. Test thoroughly! To help you do so, here are some test vectors:
      algorithm   password     plaintext        ciphertext     note
      clear       whale65]     987:test;DOG     987:test;DOG
      caesar      whale65]     987:test;DOG     ?>=@zkyzAJUM
      vigenere    whale65]     987:test;DOG     5vn+^q-V7158
      clear       10$More      987:test;DOG                    error $ not allowed in key
      caesar      10$More      987:test;DOG                    error $ not allowed in key
      vigenere    10$More      987:test;DOG                    error $ not allowed in key
      clear       hone_5^      1$L0VE$cats$                    error $ not allowed in plaintext
      caesar      hone_5^      1$L0VE$cats$                    error $ not allowed in plaintext
      vigenere    hone_5^      1$L0VE$cats$                    error $ not allowed in plaintext

Reminder: Part 1 Due: 0600 EDT on 24 Mar 2020
How to submit
~/bin/submit -c=IC211 -p=project02 *.java 
How Part 1 is graded

Part 1 of the Project is worth 25 total points, 15 of which are "on the chopping block" for this deadline.

Here's how your submission will be assessed at this intermediate point:

  1. If you submit your solution before the deadline and your submission passes the first ten tests (named "part1-a" through "part1-j") then your 15 points are safe.
  2. If you fail to submit your solution before the deadline, then your 15 points are forfeit and cannot be recovered.
  3. If you submit your solution before the deadline and do not pass all 10 of the part1 tests, then your 15 points are forfeit and cannot be recovered.
... and that's it, no partial credit for passing some of the tests, and no partial credit for "simple" mistakes like forgetting a space character. Your submission either passes all of tests for Part 1, or it doesn't.

So, be sure to log into the submission system and check your solution against the tests.

  Part 2 (25pts): Hashing

Encryption takes a plaintext message and a key and produces a ciphertext message in such a way that it can, if you know the key, eventually be decrypted to recover the plaintext message. A different kind of operation called hashing, which simply takes a message and generates a fixed length random-looking output called the "digest", or the "hash" of the method. Encryption provides confidentiality. Hashing provides integrity. Hashing, as it turns out, is a critical tool in verifying passwords. In this context, the "message" to be hashed is the user's password. See the original SI110 notes on hashing and passwords for more information.

A Part 2 solution is a program TestHashers that allows the user to hash a test message (which will be the "password") of their choice using one of the following three algorithms: clear, shift+caesar, or shift+vigenere. The program itself is not the important product here: correct implementations of the three hashing algorithms with a single coherent interface and a well-thought out system of error-handling is the real point.

Note: Please take note of the nice design laid out for you for the encryption stuff in Part 1, and try to do something similar here!

example 1 example 2 example 3
~/$ java TestHashers
algorithm: clear
password : grumpy
password read : grumpy
hash computed : grumpyxxxxxxxxxx
~/$ java TestHashers
algorithm: shift+caesar
password : grumpy
password read : grumpy
hash computed : hxgZorxKIJQw51,`
~/$ java TestHashers
algorithm: shift+vigenere
password : grumpy
password read : grumpy
hash computed : U@P9yh>l0N<;_o]o

  1. The clear algorithm takes first 16 characters of the input (add x's to the back to pad out to 16 characters if the message is shorter) as the resultant hash of the input. The shift+ algorithms uses a symmetric encryption algorithm to produce a hash, which gives us shift+caesar and shift+vigenere (you could offer shift+clear if you wanted to): see shift+ details.

  2. Exceptions: Each of these three hash implementations must throw exceptions when error situations occur: for the TestHashers program all errors should result in exceptions thrown out of main() and vomited onto the screen. The types and accompanying messages of these exceptions should be informative! For example, if the inputted password contains invalid characters, an exception should be thrown before the program continues to ask for more input.
    Important: it's up to you to define a nice hierarchy of exceptions that will allow you to handle errors nicely in the coming sections.

  3. Test thoroughly! To help you do so, here are some test vectors:

    algorithm       password  hash               note
    clear           whale65]  whale65]xxxxxxxx
    shift+caesar    whale65]  V^n]PehnA?@Gm+xs
    shift+vigenere  whale65]  C+HK`8fzqA+4P3Q[
    clear           10$more                      error $ not allowed in key
    shift+caesar    10$more                      error $ not allowed in key
    shift+vigenere  10$more                      error $ not allowed in key

Submit and check that you've passed all the tests for Parts 1-2 before proceeding to the next Part.

  Part 3 (25pts): Authentication

A Part 3 solution is a program Vault that takes a vault-file that contains only "user" entries (format described below), queries the user for a username and password, and replies with either the message "Access granted!" or "Access denied!" depending on whether the username and password pass authentication. If access is granted, the program prints a > prompt and processes commands ... although the only command it understands is quit, which immediately quits the program.

file vault3 example 1a, 1b example 2a, 2b example 3a, 3b
user chambers clear princessxxxxxxxx
user taylor shift+caesar FDELr0,x[csbUjms
user hawkins shift+vigenere 7sM7;9/D:@R_c]3i
~/$ java Vault vault3
username: chambers
password: 123456
Access denied!
~/$ java Vault vault3
username: chambers
password: princess
Access granted!
> quit
~/$ java Vault vault3
username: taylor
password: Neural4evr
Access denied!
~/$ java Vault vault3
username: taylor
password: Cuddly1
Access granted!
> quit
~/$ java Vault vault3
username: hawkins
password: G0N4\/y
Access denied!
~/$ java Vault vault3
username: hawkins
password: OOP-C-day-Z
Access granted!
> quit

Make sure you have a nice clean design for your solution, and test thoroughly to make sure you meet all of the following requirements:

  1. The program must run as java Vault <filename>, and its prompts and output messages must match the format of the examples above exactly. This also means that the password should not be echoed to the screen. See TestEncryptors.java for an example of how to do this.
  2. No error should result in a an exception being thrown out of main and dumped to the screen. Instead, all error conditions should be caught and result in meaningful error messages. Here is a list of some possible errors and the required output/behavior:
    • called with no command-line argument: output "usage: java Vault <filename>" and exit.
    • called with filename that can't be found/opened: output "Error! File 'name' could not be opened." and exit.
    • input file not in the proper format, i.e. could not be read in as a number of "user" records, one per line: output "Error! File 'name' improperly formatted." and exit. (see vault31 for example)
      Note: this message comes before either the username or the password are read.
    • username not found or password does not match or password contains a character outside ASCII range 42-122: these are not treated as errors, "Access denied!" is the proper message for this case.
      Note: this message always comes after both the username and the password are read.
    • input file specifies a hash algorithm for the given username that is not supported: output "Error! Hash algorithm 'hashalg' not supported." and exit. (see vault32 and login with username=chambers and password=princess for example)
      Note: this message always comes after both the username and the password are read.

Submit and check that you've passed all the tests for Parts 1-3 before proceeding to the next Part.

  Part 4 (10pts): Adding users

A Part 4 solution builds on top of a Part 3 solution to add the ability to add new users to the vault-file. In this case, the program is run as
java Vault -au filename
where the -au option indicates that you want to add a user. The user is prompted to enter a username, password, and a hash algorithm as shown in the examples below.

1. file vault4 2. add user 3. new file vault4 4. authenticate
user chambers clear princessxxxxxxxx
~/$ java Vault -au vault4
username: roche
password: Tuba+Time
Hash algorithm: shift+caesar
user chambers clear princessxxxxxxxx
user roche shift+caesar *0TRSZ/>:5iq0pcx
~/$ java Vault vault4
username: roche
password: Tuba+Time
Access granted!
> quit

We haven't really done anything with writing files yet. Here's some example code. Note that you need to make sure to close the file. That's important. You'll also want to make sure you close the input file immediately after reading it at the beginning of your program. There are two options for you. The first is to overwrite the file, the second is to append to the end of an existing file.

// Creates a new file and clobbers the existing one.
PrintWriter pw = null;
try {
  pw = new PrintWriter(new File(fname));
} catch (FileNotFoundException fnfe) {
  fnfe.printStackTrace();
}

// Do whatever you need to do

if (pw != null) pw.close();
// Opens an existing file, and appends to the end of it.
PrintWriter pw = null;
try {
  pw = new PrintWriter(new BufferedWriter(new FileWriter(filename, true)));
} catch (FileNotFoundException fnfe) {
  fnfe.printStackTrace();
}

// Do whatever you need to do

if (pw != null) pw.close();

Obviously, we're adding functionality to your Part 3 solution, so all the part three stuff should work as before. The only really new things are:

  1. The usage message should now read: " usage: java Vault [-au] <filename>"
  2. When adding a user, if the password contains characters outside ASCII values 42-122, output "Error! Invalid symbol 'x' in password." and exit.
    Note: this message only comes after the username, password and algorithm have all been read.
  3. if the user specifies a hash algorithm that is not supported output "Error! Hash algorithm 'hashalg' not supported." and exit.
    Note: this message only comes after the username, password and algorithm have all been read.
  4. If the given username already appears in the input file, output " Error! Username 'username' already in use." and exit.
    Note: this message only comes after the username, password and algorithm have all been read.

Note: Writing to files is a bit new. PrintWriter objects give you your usual print, println and printf. In the sample code above on the left, when a new user is added, you completely overwrite the vaultfile. That means you have to write entries for all the existing users as well as for the new user. You will want to read and save the entire file in memory first, and then create a PrintWriter to write to that same file that you just read.

Submit and check that you've passed all the tests for Parts 1-4 before proceeding to the next Part.

  Part 5 (15pts): Viewing data

A Part 5 solution builds on top of a Part 4 solution in two ways
  1. The inputfile now contains "data" entries mixed in with the "user" entries. Each data entry is a line of the form:
    data username encalg cipertext
    where encalg can be one of clear, caesar, or vigenere, and ciphertext is a ciphertext-string that decrypts (with the user's password as key and using encryption algorithm encalg) to a plaintext string of the form label_text, where both the label and the text come from the ASCII range 42-122, but the label does not include the '_' character. For example, suppose there is a user crabbe with password FlappyBirds. We might have a data entry like this:
    data crabbe caesar ,//=0>>*j@8-0=*P*c=48,@7/*l7J
    
    Note: decrypt with caesar and password FlappyBirds gives: address_Number_4_Grimauld_Pl.
                                                              \_____/ \___________________/
                                                               label          text
    This means that the entry represents the label "address" with the associated text "Number_4_Grimauld_Pl.".
  2. Once a user has been successfully authenticated, that user enters into a little command shell that process the following commands:
    • labels — this should print out all the labels associated with this user. Note that the only way to do this is to go through all data entries with the matching username, decrypt (with the same password that the user authenticated with), split the resulting plaintext into everything before the first "_" and everything after, and take the "before" part as the label.
    • get label — this should print out the text associated with label for the current user. Note: you decrypt entries for the current user with the same password that the user authenticated with.
    • quit — this should quit the program

file vault5a sample run 1 vault5b sample run 2
user chambers clear princessxxxxxxxx
data chambers clear weakness_backhand
data chambers caesar bZa`WQwtspu+vpyyxx
data chambers vigenere [XdX*Kf\TF\XG.ajZZb\WX
~/$ java Vault vault5a
username: chambers
password: princess
Access granted!
> labels
weakness
phone
faveBand
> get weakness
backhand
> get faveBand
One_Direction
> quit
user chambers shift+caesar /+wZbraTilrECDKq
user taylor shift+vigenere 5[0E,;d2rp;M8[0:
data chambers vigenere [XdX*Kf\TF\XG.ajZZb\WX
data taylor caesar 43/@-b=C:@=>6=07/
data chambers caesar bZa`WQwtspu+vpyyxx
$ java Vault vault5b
username: taylor
password: Neural4evr
Access granted!
> labels
fear
> get fear
Coulrophobia
> get faveBand
> done
Unknown command 'done'.
> quit

Other requirements and hints:
  1. If the user enters an unknown command (i.e. not add, label or quit), output the message "Unknown command ' comm'." and continue reading more commands.
  2. If the user enters a label for the "get" command that is not matched by any entry, nothing should be printed at all. Just return the prompt and wait for the next command.
  3. If in decrypting an entry (whether for the "labels" command or the "get" command) there is any kind of error (e.g. invalid character in the input or the output, or the decrypted string does not have a '_' in it) print out the message "Error! Corrupted entry 'ciphertext' in vault file.", and continue processing the other data entries as if the one that caused the error didn't exist.

    Note: try the file vault5c, logging in as user taylor with password Neural4evr. If you give the labels command, the last entry for user taylor should give this kind of error.

  4. Hint: A combination of String's indexOf() and substring() methods make it easy to break up the decrypted string into the label (i.e. what comes before the first '_') and the text (i.e. what comes after the first '_').

Submit and check that you've passed all the tests for Parts 1-5 before proceeding to the next Part.

  Part 6 (+ 10pts Extra Credit): Adding data

A Part 6 solution builds on a Part 5 solution to add the last piece of core functionality: the ability to add new data entries. In particular, one new command is added to the command shell the user gets after a successful login:
add encalg label text
This command should add a data entry for the current user, with the encryption algorithm given by encalg, and form the ciphertext by encryption label_text with algorithm encalg using the password the user authenticated with as the key for the encryption.
Note: if the label entered by the user is already in use by that user, the given text replaces the text in the old entry with that label. In particular, this means that we never get duplicate labels in data entries for a given user.

file vault6a before sample run file vault6a after
user chambers clear princessxxxxxxxx
user roche shift+caesar *0TRSZ/>:5iq0pcx
data roche caesar dgn]WZac]Wja\af_
$ java Vault vault6a
username: roche
password: Tuba+Time
Access granted!
> add vigenere ssnum 123-45-6789
> quit
user chambers clear princessxxxxxxxx
user roche shift+caesar *0TRSZ/>:5iq0pcx
data roche caesar dgn]WZac]Wja\af_
data roche vigenere LmU[n8punW.md7aw+

What makes this challenging is the sheer number of things that can go wrong with this command — all of which should be handled gracefully.
  1. The user enters an unsupported encryption algorithm. In this case, the program should output "Error! Encryption algorithm 'encalg' not supported.", print the prompt and await the next command from the user.
  2. The user enters a label with characters outside ASCII 42-122, or which includes a '_' character, which is not allowed in labels. In this case, the program should output "Error! Label 'label' is invalid.", print the prompt and await the next command from the user.
  3. The user enters text that includes characters outside ASCI 42-122. In this case, the program should output "Error! Invalid character 'X' in text.", print the prompt and await the next command from the user.

Submit and check that you've passed all the tests for this Part before proceeding to the next Part.

  Part 7 (+ 5pts Extra Credit): "Full" shift+ hashing

Shift+ hashing as implemented in Part 2 is limited to message/passwords of at most 16 characters. If you give it longer messages/passwords, it simply ignores everything after the 16th character. Look at the full shift+ hashing details and extend your implementation to handle longer messages/passwords.

~/$ java TestHashers
algorithm: shift+vigenere
password : ThisIsExactly_16
password read : ThisIsExactly_16
hash computed : 4rgZDs.8u1bjr5A/
~/$ java TestHashers
algorithm: shift+vigenere
password : ThisIsExactly_16_plus_some
password read : ThisIsExactly_16_plus_some
hash computed : K>0F_<6vvHfYP:c^

When to submit

What to submit

Your electronic submission must include:
  1. All .java files (with complete javadoc) required to compile and run your program - including the ones you were given in the assignment.
  2. A file named README with your name and alpha code, which step you are submitting for (including any extra credit), and any issues you know your program has.

Your paper submission must include:

  1. A completed copy of this coversheet on paper.
  2. A printout of your README file.

How to submit

~/bin/submit -c=IC211 -p=project02 *.java README

How projects are graded / how to maximize your points

Things to keep in mind to receive full marks:
  1. Submit all required items (as described above) on time.
  2. Do not submit any .java files that do not compile!
  3. Make sure your output matches the example output in ALL ways.
  4. To receive credit, steps must be completed in order. The maximum score that you can recieve will be determined by the lowest Step that passes all tests in the submission system. In other words, you cannot receive credit for completing Step 4 of the assignment if you still have errors from Step 3. In this case, the maximum credit you could receive would be the cumulative point value of Steps 1 & 2 (up to the highest Step that passed all the tests.) There is no partial credit in determining the maximum score that you can obtain.
  5. Use good object-oriented design! The means adhering to the principles of encapsulation, data/information hiding, making good use of inheritance and polymorphism.
  6. Document all of your code, and use javadoc syntax.
  7. Practice good Java Style. This means proper indentation, use of whitespace within lines of code, logical organization of chunks of code, proper naming of classes, methods, fields, and constants in accordance with Java conventions.