Assignment 2 - File Encryption with the Java Cryptography Architecture This assignment will teach you how file encryption is performed using the standard cryptoprimitives provided in the Java...



Assignment 2 - File Encryption with the Java Cryptography Architecture


This assignment will teach you how file encryption is performed using the standard cryptoprimitives provided in the Java Cryptography Architecture. Along the way, you'll explore issues such as padding, block cipher modes and how to generate large, reasonably random keys from passwords (or, better still, passphrases). You can split this task into three easy stages to allow you to check your work, and as you proceed, these instructions ask you several questions -you should record your answers because they are part of your assignment submission.


Download the file FileEncryptor.java from iLearn and import it into an Eclipse Java project called FileEncryptor so that you can work on it.
FileEncryptor.java is theskeletonof a simple file encrypt/decrypt program. On the command line, the first parameter is "E" or "D" for encrypt or decrypt, respectively, while the second and third parameters are the input file and output file, as you'd expect. However, the fourth parameter isnota key, but a password or passphrase. If a passphrase which includes spaces is used, it must be surrounded by quotes to stop the shell parsing it as multiple parameters, e.g. "My Super Secret Passphrase".
Your mission, should you choose to accept it - and you don't really have a lot of choice - is to complete the program by filling in the missing code which instantiates objects of the right classes and then calls the appropriate method calls. I created the skeleton by taking the completed, working version, and deleting code while leaving the explanatory comments - but I've been reasonably careful to replace multi-line function calls and blocks of code with the same number of blank lines (there's usually a blank line below the excised code). So if you see a two-line gap, you can reasonably assume that one line of code will provide the missing functionality - and if you think a single line of code will fill a 15-line gap, you should wonder whether you're missing something.
Along the way, you will need to refer to the online documentation for the Java Cryptography Architecture, which you will find athttp://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html. The JavaDoc for the various javax.crypto classes are athttp://docs.oracle.com/javase/8/docs/api/javax/crypto/package-summary.htmlwhile javax.crypto.spec JavaDoc is athttp://docs.oracle.com/javase/8/docs/api/javax/crypto/spec/package-summary.html.
You may also need to refer to the Standard Algorithm Names athttp://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.htmland the Oracle Providers documentation athttp://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html.
Anything else can be found under the main Security Documentation athttp://docs.oracle.com/javase/8/docs/technotes/guides/security/index.html.

This is a shameless ploy to get you to become at least slightly familiar with the JCA reference documentation - you are quite likely to need it in the real world, as well as for this assignment.However, I will provide some overview guidance in this article.
I've also moved the declarations of the required variables and objects to the beginning of themain()method - looking at these will give you some valuable clues.


If you are not a strong Java programmer, you might want to review the skeleton code first, while referring to the notes on Programming Style at the end of this document. However, it should be possible for a non-programmer to work out the required methods and their arguments like algorithm names and transformations from the lectures and the write-up that follows.

The Java Cryptography Architecture

The JCA provides a standard interface which Java programmers can use to both use cryptographic functionality and also implement crypto functionality. Notice the latter point: anybody can develop cryptoprimitives that conform to the JCA and package them, as JAR files, into what are called providers. In this exercise, we will be using one of the default providers from the Oracle Java SE SDK - the SunJCE provider. Others exist and may be preferable if you require some advanced functionality - a good example is the Australian-developed Bouncy Castle package found athttps://www.bouncycastle.org/.
Because the JCA is highly generic and algorithm-independent - you can utilise DES, Blowfish or AES with almost-identical code - in many cases you do not directly instantiate a particular class of cipher. Instead, you call ageneratororfactory methodto get an instance of the required cipher or other cryptoprimitive. So the various base classes -Cipher,SecretKeyFactory,KeyPairGenerator, etc. - all provide a staticgetInstance()method which you should call, usually with the name of the required algorithm as the first parameter.
This technique allows the JCA runtime to search multiple different providers in order to instantiate the required algorithm, rather than tying your code to a specific provider.
In practice, just the algorithm name alone is insufficient, as discussed in the lectures - we also need to specify what mode the cipher will be used in, as well as a padding mechanism. These options are concatenated with "/" characters as a delimiter, so we arrive at strings like "AES/GCM/NoPadding", which the JCA documentation callstransformations.


One of the benefits of strongly typed languages like Java is that many errors can be discovered either by the compiler, at compile time, or even by the editor of your IDE. However, in the JCA many different cipher implementations share the same class or interface - and the actual cipher implementation to be used is specified by a string parameter specifying the required transformation.
This means non-existent cipher implementations cannot be discovered at compile time, but only at run time, and so many of thegetInstance()calls will need to be surrounded by try/catch blocks. Fortunately, Eclipse will take care of most of that work for you.
The various cryptoprimitives require different sets of their corresponding parameters, and so there are supporting classes such asAlgorithmParameters,KeySpecand its derivativesSecretKeySpec,PBEKeySpec,RSAPrivateKeySpec, etc. that allow the programmer to fully specify how he wants ciphers configured, keys generated, etc.
In general, the various algorithms will provide default values if a parameter is not specified. However, be aware that the various API's don't like null pointers. So, for example, if you don't want to specify a salt value for a KeySpec, you can't say
salt = null;
but must write:
salt = new byte[20];
in order to avoid an exception being thrown at run time.

SunJCE

For this exercise, we'll use the standard SunJCE provider - the original Sun Java Cryptography Engine. All its documentation is in the standard JCA documentation and JavaDoc linked above, and no installation or configuration is required.

Converting a Passphrase to a Key

We're going to be encrypting and decrypting using AES with a 128-bit key - but humans are really bad at remembering 128-bit binary strings, so we're letting the user enter a passphrase instead. As a result, we'll need an algorithm that converts an arbitrary-length string into a fixed-length binary value.
That really ought to ring bells in your head; we spent a week discussing this type of algorithm.
Right: we need ahash function. Fortunately, there's an algorithm that is purpose-built for this task - taking a passphrase and turning it into a key. It's called PBKDF2, which stands for "Password Based Key Derivation Function 2", and it's based on repeated hashing of a single block of input text, typically using SHA-1 thousands of times over - the first time to hash the input text, and then to hash the previous hash value. It can also add in a littlesalt(something we'll discuss in a later lecture, in connection with password hashing and storage).
You may have already used PBKDF2 - it's the function that Veracrypt uses to turn a passphrase into a key, and it's also the way wi-fi access points and routers create a 256-bit AES key for use in WPA2. When setting up WPA2, you could enter a 64-digit hex string as a specific 256-bit key - or a string up to 63 characters long, which will be passed through PBKDF2, using 4096 iterations to produce what is called thePairwise Master Key. In addition, the network SSID is used as salt, so that the same passphrase produces different key values on different networks.
Here's an outline of the missing code:
To create a PBKDF2 key factory, call thegetInstance()method ofSecretKeyFactorywith the right transformation string (which you get from the Standard Algorithm Names page linked above).
Next, you'll need to create a key specification, which sets up the number of iterations, the required key size, the salt string, and - very importantly - the passphrase the key will be derived from. There are various types ofKeySpec, but you'll need one for Password-Based Encryption (this is a big hint - I've already mentioned this type ofKeySpec).
Finally, you get the key by calling your key factory'sgenerateSecret()method with theKeySpecobject as parameter.
Notice that the key is returned as aSecretKey, which is a class that wraps around the actual key value in order to provide type safety and also deal with various types of keyencodings- it is impossible to do things like attaching a key to an email or pasting it into a web form if it is in a purely binary representation, and so it may be saved in formats like ASN.1 DER (ISO Abstract Syntax Notation type 1, Data Extended Representation). To get the actual key as an array of bytes, call thegetEncoded()method on theSecretKeyobject.


Finally, you've got a key!

Performing Symmetric Encryption

The SunJCE's Cipher implementations support a wide variety of cryptoprimitives, including DES, RC4, Blowfish and AES along with various modes and padding types.
To use a Cipher, the general sequence of events is this:




  • Create aCipherof the required type by callingCipher.getInstance()while specifying the appropriate transformation.

  • Initialise theCipherobject by calling itsinit()method, specifying the encryption mode (encrypt or decrypt), a key spec and optionally anIVSpec.

  • Loop around, calling the cipher object'supdate()method which returns blocks of ciphertext.

  • Finally, call the cipher object'sdoFinal()method, which allows it to perform padding if it has a partial block left over from the previous update.


Note that the JCA Cipher interface does not have separateencrypt()anddecrypt()methods - instead, the sameupdate()anddoFinal()methods are used to both encrypt and decrypt and the operating mode is set in theinit()method call. You can see the static final constants that are passed toinit()in the skeleton code.
The other thing that is passed toinit()is aKeySpec- this a wrapper that goes around the raw bytes of the key. In this case, you'll need to set up aSecretKeySpec- take a look at its constructor in the JCA documentation.

ECB mode (Version 1)

For the first version of your program, you should implement the encryption using AES with 128-bit keys in ECB mode and PKCS #5 padding. This means that no IVSpec will be required.


PKCS #5 padding is discussed in the lecture - it basically means that if the final block fallsnbytes short of the AES 128-bit block size, we fill that space withnbytes, each with the valuen. There's a curious Java weirdness here: PKCS #5 padding is only defined for ciphers which use an 8-byte - that's 64 bits, of course - block size, which means DES. PKCS #7 defines this style of padding for arbitrary block sizes, but the JCA won't accept that as part of a transformation string, and so we're forced to call it PKCS #5 padding, even though it's really #7.


In any case, the good news is that you don't have to write any code to do the padding manually, because theCipherwill take care of this for you, if you specify it as part of the transformation string - that's the point of thedoFinal()method.


Once you've written your code, test your program, using increasingly long text files. You may need to insert some statements to print values toSystem.errso that you can see what's going on.
Once your program is working, encrypt the fileinfile20.txt, using a passphrase of your choosing:
java FileEncryptor e infile20.txt crypto.binpassphrase

The ciphertext is written to the filecrypto.bin- open it in Notepad++ or a similar editor. What do you notice about the ciphertext? Why do you think this is?
Experiment with encrypting small files of various sizes - say, 50 bytes, 60 bytes and 64 bytes - and comparing the size of the generated ciphertext file. Record your results. What do you notice?

CBC Mode With Default IV (Version 2)

Now edit your code to operate in Cipher Block Chaining mode. You will need to change the transformation string, and also create anIVSpec. Once again, check the documentation forIVSpecin the online documentation - especially its constructors. Notice that if you do not provide an initialization vector, theCipherwill provide a default. However, remember the note about default values above - passing a null value will generally cause an exception, so that a default IV is provided as
byte[] iv = new byte[16];
Once you have completed your program and tested it with some small text files, try encryptinginfile20.txtonce again and viewing the ciphertext in Notepad++. Does it look different?
Once again, try encrypting small files and comparing the size of the input plaintext and generated ciphertext. Record your results - what do you find?
Obviously, decryption has to be performed with the same key (i.e. the same passphrase) but also with the same initialization vector. How is the initialization vector being passed between encryption and decryption? Use the getIV() method of the Cipher interface to get the actual default initialization vector being used and print it - do you think this is particularly secure?

Final Challenge: CBC Mode With Random IV (Version 3)

The answer to the last question above should suggest a further weakness with using a default IV - setting aside the obviously problematic value, every file is being encrypted using the same IV value and quite possible the same passphrase, leaving us increasingly open to a known-ciphertext attack as we encrypt more and more files.
We really need a randomly-generated IV. Fortunately, the JCA provides a cryptographically adequate pseudo-random number generator class, inSecureRandom. Once you've create a 16-byte array, iv, as above, getting it filled in with a random value is as easy as:
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
Now modify your CBC-mode file encryption program so that, instead of using the same constant value for an IV, it uses a "genuinely" random IV.
But before you copy and paste the two lines above into your program and set about compiling it, consider that question asked in the previous task: How is the initialization vector being passed between encryption and decryption? Whenever you encrypt a file, a new, random value for the IV is generated - and the file has to be decrypted using the same IV value. If this is going to work, you're going to have to persistently save the IV somewhere.
Test your program by encryptinginfile20.txtand decrypting the resultant ciphertext - obviously, you should get back the same file. Now, repeat the test of encrypting some small files and comparing the sizes of the generated ciphertext files. Your ciphertext should be a bit larger - and you should know exactly how much larger and why.
Hopefully, you can see that initialization vectors aren't as straightforward as people often naively assume. In fact, many secure systems have failed, not because DES was cracked or AES was cracked, but because a naive programmer chose a weak way of generating initialization vectors. And you should also be able to see why initialization vectors for encrypting disk sectors are particularly challenging, as discussed in the lecture, and we have to come up with schemes like ESSIV and XTS.

Notes on Coding Style

Some general comments on coding style - for this demo program, I opted to let Eclipse generatetry/catch blocks around various method calls, then replaced the generatedprintStackTrace()with a call to a routine which prints a more meaningful error message - in particular, a message that gives a clue as to where things went pear-shaped. This is usually followed by a call toSystem.exit()to exit the program, setting theerrorlevelto an appropriate value. I use this technique a lot in "exploratory" programming, but for larger and more polished programs I tend to use largertry/catchblocks and throw to a higher-level error handler.
The main processing loop is written in a slightly verbose style, just to make what is happening clear to the less experienced programmers. The paradigm used is:
read_from_file()
while (something was successfully read) {

encrypt_what_was_read()

write_what_was_encrypted()

read_from_file()
}
perform_final_processing()
write_final_block()
However, it's quite common for C/C++/Java programmers to combine the read_from_file() with the while loop test. In this example, it would read like this:
while ((bytesRead = in.read(inputBuffer)) > 0) {
This paradigm would make the program a few lines shorter, as both calls toin.read()are consolidated into a single call.
Notice that the file I/O reads binary blocks of data, rather than using a stream reader to read lines of text. There are two reasons for this: firstly, the program cannot be restricted to simply encrypting text files - you might want to encrypt an Excel spreadsheet, a JPEG image, or any other kind of data. And secondly, the ciphertext written to the encrypted file will definitely not be text - and the program needs to be able to read it back in to decrypt it.
You may have noticed that I've used buffered streams for file input and output. This means that the block size for reading and writing has virtually no impact on performance: reading lots of small blocks of data will not cause lots of slow disk accesses because the Java runtime will buffer all I/O. The buffer size of 128 bytes set at the beginning of the program can be changed arbitrarily.



Requirements :


Please attach your Java source code file, FileEncryptor.java, along with a text file called Assignment 2.txt containing the answers to the following questions:


1. For version 1 of the program, operating in ECB mode:


a) What did you notice about the ciphertext, and why do you think that was?


b) What did you notice about the size of the generated ciphertext files. Why was this?


2. For version 2 of the program, operating in CBC mode with default IV:


a) What did you notice about the size of the generated ciphertext files?


b) How is the initialization vector being passed between encryption and decryption?


c) Is this particularly secure?


3. For version 3 of the program, operating in CBC mode with a random IV:


a) What did you notice about the size of the generated ciphertext files?

Nov 04, 2021
SOLUTION.PDF

Get Answer To This Question

Related Questions & Answers

More Questions »

Submit New Assignment

Copy and Paste Your Assignment Here