This lab will give you experience using the Strategy Pattern. In this lab you will use the Strategy Pattern to build a portable device for carrying secure messages known as a CrypStick. When a message is entered into the CrypStick, it is encrypted before storing on the internal media. When a message is read from the internal media, it is decrypted. Thus (after the encryption method is cleared) anyone who steals the media must also break the encryption to reveal the message. This is an individual lab.
The primary class you will implement is CrypStick
; it will
contain
Encrypter
objects to handle encrypting and decrypting messages.
The key elements are illustrated in the following class diagram:
You are being given a starting point for this
lab, crypstick-start.zip
. This
code includes a preliminary implementation of the CrypStick
and Media
classes. It also includes a short program that uses
these classes to encrypt and decrypt data. This program will serve as a
test bed to help check that your implementation is correct. The
provided CrypStick
and Media
classes are
summarized in the following class diagram:
Note the provided Main.java
interacts with the user through
standard input and output
(System.in
, System.out
). You
are not to change the code to use a GUI or GUI operations
like JOptionPane
. On start up, the program prompts the user for whether
to encrypt or decrypt the data and what encryption method to use. It then
encrypts the data and displays the result.
For example, if you run your program from IntelliJ and direct it to
encrypt tole
using the key told
, a sample run
would be
E)ncrypt or D)ecrypt: e Method (rev, shift, xor): xor Key: told Message: tole ^D 00 00 00 01 7e
Some notes on the interaction:
hasNext()
to
return false.
keys
", "@#2!
", "me
2
", but not
2-line key
NullEncrypter
; this encrypter applies no
transformation (except that the user will see encrypted messages printed in
hexadecimal and will enter hexadecimal for "decryption").
Shift
amount:
". You can assume the response is a valid, small integer.
The Media
object simply stores the byte
array
given to it. When given a string, it treats the string as a series of
space-separated bytes, each coded as two hexadecimal places. The
toString()
method returns a representation of the string using
hexadecimal digits. We are using lower case letters (a-f) to represent the
decimal values 10 through 15.
When encrypting information, the media's contents should be displayed in
the Media
object's String
format, that is, in
space-separated hexadecimal bytes.
When decrypting the message, the media's contents should be set with the space-separated hexadecimal bytes entered by the user before reading the message. A full application would not display this to the end user, but doing so in this lab allows us to confirm the media is properly encrypted.
Note that the encryption algorithms are fairly simple (that is, not
secure). For example, the ShiftEncrypter
encrypts text by simply substituting the another alphabetic character for each
character encountered. The amount attribute (set by the
ShiftEncrypter
constructor) affects which character is
substituted. For example,
when amount is 1, ShiftEncrypter
's encrypt()
method would
encrypt the 3-byte text Hal to Ibm, while the
decrypt()
method
does the opposite. Similar to a Caesar cipher, if the numeric value of the
character would fall outside of the ASCII range (0-255), the encrypter must "wrap around"
to the other end. For example, if a code would have the numeric value of 256, it
should wrap around to 0, and a numeric value of 260 should
wrap around to 4.
ReverseEncrypter
encrypts by reversing the characters in the
string to be encrypted; thus Hello there Bob!
becomes !boB ereht olleH. In hexadecimal, this is printed as
0a 21 62 6f 42 20 65 72 65 68 74 20 6f 6c 6c 65 48.
This is a silly form of encryption, but it is a worthwhile encryption
method to implement because it helps you debug your framework before
attempting to introduce better encryption methods.
XOREncrypter
is a little more complicated and so slightly harder to
crack. For each
byte in the array to be encrypted, this encrypter is to compute an exclusive-or (^
in Java) with each successive character in the specified key. For example, if
the key is "abcd", then the encrypted array is
H^a e^b l^c l^d o^a <sp>^b t^c h^d e^a r^b e^c <sp>^d B^a o^b b^c !^d
where H^a represents the character that results from applying the exclusive-or to H and a and <sp>^b represents computing the exclusive-or between b and a space character. Note how this also illustrates cycling through the bytes in the key arrway. For example, encrypting "Hello there Bob!" with "abcd" will give the hexadecimal output
29 07 0f 08 0e 42 17 0c 04 10 06 44 23 0d 01 45 6b
Decryption for XOR is the same process: reapply the exclusive-or operation (using the same key) to the encrypted array. Note α ^ β ^ β gives α; that is, computing an exclusive-or with the same string twice returns the original string.
The ShiftEncryter
and XOREncrypter
may produce
non-printable characters. This is why all input and output of encrypted
text uses hexadecimal.
CrypStick, Media,
Encrypter, ReverseEncrypter, ShiftEncrypter,
XOREncrypter
) as needed. Add constructors and getters if it is
useful for your implementation.
Main.java
.
Main.java
supports
this. Your encrypters should also support arbitrary binary data.
str
can be converted to a byte array
using the code str.getBytes(StandardCharsets.UTF_8)
.
bytes
that consists of ASCII or UTF-8 text
can be converted to a (Java) String
using the code
new String(bytes, StandardCharsets.UTF_8)
.
Your submission will consist of the following:
See our Enterprise Architect Notes for how to reverse-engeineer a class model. The reverse-engineered model must include stereotypes (e.g. <<strategy interface>>, <<concrete strategy>>, etc.). Note the reverse-engineered diagram is to match your implementation; it may differ in details from the above diagram! For example, you may have different types of assocations. The EA Notes page also indicates how to generate a .PNG of your model; do not use screen capture to generate the .PNG because this results in a low-resolution image with important, missing details.
When you are finished, submit your assignment according to your instructor's specific directions. For example, you may need to convert your .PNG into a .PDF (probably using print-to-PDF on your computer).
strategy-lab-tests.zip
.
String.format()
can be helpful when encoding bytes as
hexadecimal strings.
Scanner
has some very helpful methods for decoding bytes
from hexadecimal strings. Remember that a scanner can be wrapped around a
String
, and not just around System.in
.
{ Scanner intro_scanner = new Scanner(System.in); ... } { Scanner second_scanner = new Scanner(System.in); ... }(where the two portions of code might be in different classes or methods). This is unlikely to work as expected, because lots of operations can result in the first scanner reading text that you need the second scanner to process. The solution is simple: create a single scanner object and pass it as a parameter or store it as an attribute.
DataInputStream
useful for reading the binary
data.
byte
data type for the computation, not int
.
initialize scanner while the scanner has a next element, read the next element process that elementSee
CountLines.java
for sample code using this pattern.
NullEncrypter
and the encryption method "none".
This will ensure you can process hexadecimal data.
Reverse
encrypter since that encrypter is simple to debug.
getMedia()
method of CrypStick
is
protected to indicate that one would not normally need to access its
interior data. However, we do access it from the
provided Main.java
to facilitate testing. A production
version of CrypStick
would lock down the data more
completely.
Submission of the lab will be electronic. See Canvas for details.