2727import javax .crypto .spec .SecretKeySpec ;
2828
2929
30+
31+
3032/**
3133 *
3234 * @author Erik Costlow
@@ -38,7 +40,7 @@ public class FileEncryptor {
3840 private static final String ALGORITHM = "AES" ;
3941 private static final String HASH_AlGORITHM = "HmacSHA256" ;
4042 private static final String CIPHER = "AES/CBC/PKCS5PADDING" ;
41- private static final int ITERATION_COUNT = 1000 * 128 ;
43+ private static final int ITERATION_COUNT = 100000 ;
4244
4345 public static void main (String [] args ) throws Exception {
4446 // Error Message
@@ -78,27 +80,6 @@ public static void main(String[] args) throws Exception {
7880 charArgs = null ; dec = null ; enc = null ;
7981 }
8082
81- /**
82- * Generates a Secret key with a specified password. The password is added with
83- * a salt and iterated multiple times before being hased to increase entropy.
84- * The salt and key lenghts need to be specified to then return a secret key
85- * encoded in a byte array.
86- *
87- * @param password char[] The password specified by the user
88- * @param salt byte[] A randomly gnerated set of bytes
89- * @param keyLength int The lenght of the final key, in bits e.g. 128, 256 etc.
90- * @return byte[] An encoded byte array of the secret key
91- * @throws NoSuchAlgorithmException
92- * @throws InvalidKeySpecException
93- */
94- private static byte [] generateKey (char [] password , byte [] salt , int keyLength ) throws NoSuchAlgorithmException ,
95- InvalidKeySpecException {
96- PBEKeySpec passwordKeySpec = new PBEKeySpec (password , salt , ITERATION_COUNT , keyLength );
97- SecretKeyFactory keyFactory = SecretKeyFactory .getInstance ("PBKDF2WithHmacSHA256" );
98- SecretKey secretKey = keyFactory .generateSecret (passwordKeySpec );
99- return secretKey .getEncoded ();
100- }
101-
10283 /**
10384 * Encrypts a plain text input file by outputing an encrypted version. It does this
10485 * generating a 128 bit secret key and initialisation vector which are used as the
@@ -122,22 +103,13 @@ public static void encrypt(char[] password, String inputPath, String outputPath)
122103 final byte [] initVector = new byte [16 ], salt = new byte [16 ], macSalt = new byte [16 ];
123104
124105 SecureRandom sr = new SecureRandom ();
125- sr .nextBytes (initVector );
126- sr .nextBytes (salt );
127- sr .nextBytes (macSalt );
106+ sr .nextBytes (initVector ); sr .nextBytes (salt ); sr .nextBytes (macSalt );
128107
129108 // Get Keys from password
130109 final byte [] key = generateKey (password , salt , 128 );
131110 final byte [] macKey = generateKey (password , macSalt , 256 );
132111
133- // Initialize Vector and Keys
134- IvParameterSpec iv = new IvParameterSpec (initVector );
135- SecretKeySpec skeySpec = new SecretKeySpec (key , ALGORITHM );
136112 SecretKeySpec macKeySpec = new SecretKeySpec (macKey , HASH_AlGORITHM );
137-
138- // Initialize cipher and Mac
139- Cipher cipher = Cipher .getInstance (CIPHER );
140- cipher .init (Cipher .ENCRYPT_MODE , skeySpec , iv );
141113
142114 Mac hmac = Mac .getInstance (HASH_AlGORITHM );
143115 hmac .init (macKeySpec );
@@ -150,20 +122,14 @@ public static void encrypt(char[] password, String inputPath, String outputPath)
150122 final Path encryptedFile = Paths .get (outputPath );
151123
152124 // Compute Mac for authentication
153- hmac .update (initVector );
154- hmac .update (salt );
155- hmac .update (macSalt );
125+ hmac .update (initVector ); hmac .update (salt ); hmac .update (macSalt );
156126 final byte [] mac = computeMac (hmac , plaintextFile );
157127
158128 // Display the Base64 encoded versions of Key, Vector and computed mac
159- System .out .print ("\n <---------------------------------------->\n " );
160- System .out .println ("Secret Key is: " + Base64 .getEncoder ().encodeToString (key ));
161- System .out .println ("Key salt is: " + Base64 .getEncoder ().encodeToString (salt ));
162- System .out .println ("IV is: " + Base64 .getEncoder ().encodeToString (initVector ));
163- System .out .println ("Mac Key is: " + Base64 .getEncoder ().encodeToString (macKey ));
164- System .out .println ("Mac salt is: " + Base64 .getEncoder ().encodeToString (macSalt ));
165- System .out .println ("Computed Mac: " + Base64 .getEncoder ().encodeToString (mac ));
166- System .out .print ("<---------------------------------------->\n \n " );
129+ displayInformation (getPair ("Secret Key" , key ), getPair ("Init Vector" , initVector ), getPair ("Salt" , salt ),
130+ getPair ("Mac Key" , macKey ), getPair ("Mac salt" , macSalt ), getPair ("Computed Mac" , mac ));
131+
132+ Cipher cipher = createCipher (key , initVector , 1 );
167133
168134 // Write plaintext into ciphertext
169135 if (writeEncryptedFile (plaintextFile , encryptedFile , cipher , salt , macSalt , mac )) {
@@ -177,7 +143,7 @@ public static void encrypt(char[] password, String inputPath, String outputPath)
177143 * Writes an encrypted version of the input file, into a new output file.
178144 * Uses a FileInputStream to read the plaintext file and wraps the OutputStream
179145 * with a CipherOutStream to write an encrypted version. Prior to writing the
180- * encrypted data, IV and the computed mac is saved as metadata in the encrypted
146+ * encrypted data, IV, salts and the computed mac is saved as metadata in the encrypted
181147 * file with the use of a FileOutputStream. Returns True if the encryption writing
182148 * was successfull, False otherwise.
183149 *
@@ -214,33 +180,9 @@ private static boolean writeEncryptedFile(Path inputPath, Path outputPath, Ciphe
214180 return true ;
215181 }
216182
217- /**
218- * Computes a Message authencitaion code for a given inputfile
219- * Takes in an initialised hmac which gets updated with the file's
220- * contents line by line. Once completed the doFinal method will
221- * return a byte array with the computed Mac.
222- *
223- * @param hmac Mac The initialised Mac object
224- * @param filePath Path The file path
225- * @return byte[] The file's computed Mac
226- */
227- private static byte [] computeMac (Mac hmac , Path filePath ) {
228- try (InputStream fin = Files .newInputStream (filePath );) {
229- final byte [] bytes = new byte [1024 ];
230- for (int length = fin .read (bytes ); length != -1 ; length = fin .read (bytes )){
231- hmac .update (bytes , 0 , length );
232- }
233- } catch (IOException e ) {
234- LOG .log (Level .SEVERE , "IOException caught - Please check filepath specified" );
235- System .exit (0 );
236- }
237-
238- return hmac .doFinal ();
239- }
240-
241183 /**
242184 * Decrypts a given cipertext file into its original plaintext form.
243- * A successful decryption occurs when provided with the right key
185+ * A successful decryption occurs when provided with the right password
244186 * to create the Cipher specifications required for decryption.
245187 * Will overwrite the resultant output file if it already exists.
246188 *
@@ -276,10 +218,10 @@ public static void decrypt(char[] password, String inputPath, String outputPath)
276218 * Reads an encrypted file by wrapping an InputStream with a CipherInputStream
277219 * The encrypted files gets decrypted and written out to the output file.
278220 * For a successful decryption the Cipher needs to be initialized in DECRYPT mode
279- * with the correct key and vector specifications. The IV is read from the encrypted
280- * file as it was saved unencrypted during the encryption process. Decryption will
281- * also fail if the computed authentication code doesn't match with the given
282- * authentication code, which it also reads from the encrpted file .
221+ * with the correct key and vector specifications. The IV, salts and mac is read
222+ * from the encrypted file as it was saved unencrypted during the encryption process.
223+ * Decryption will also fail if the computed authentication code doesn't match with
224+ * the given authentication code .
283225 *
284226 * @param inputPath Path The input file path (encrypted file)
285227 * @param outputPath Path The output file path (decrypted file)
@@ -298,22 +240,16 @@ private static boolean writeDecryptedFile(Path inputPath, Path outputPath, char[
298240 // Read metadata from the input file
299241 final byte [] initVector = new byte [16 ], salt = new byte [16 ], macSalt = new byte [16 ], givenMac = new byte [32 ];
300242
301- encryptedData .read (initVector );
302- encryptedData .read (salt );
303- encryptedData .read (macSalt );
304- encryptedData .read (givenMac );
243+ encryptedData .read (initVector ); encryptedData .read (salt ); encryptedData .read (macSalt ); encryptedData .read (givenMac );
305244
306245 final byte [] key = generateKey (password , salt , 128 );
307246 final byte [] macKey = generateKey (password , macSalt , 256 );
308247
309248 // Create key specifications
310- IvParameterSpec iv = new IvParameterSpec (initVector );
311- SecretKeySpec skeySpec = new SecretKeySpec (key , ALGORITHM );
312249 SecretKeySpec macKeySpec = new SecretKeySpec (macKey , HASH_AlGORITHM );
313250
314251 // Initialise cipher
315- Cipher cipher = Cipher .getInstance (CIPHER );
316- cipher .init (Cipher .DECRYPT_MODE , skeySpec , iv );
252+ Cipher cipher = createCipher (key , initVector , 2 );
317253
318254 // Read cipertext data and write plaintext data
319255 try (CipherInputStream decryptStream = new CipherInputStream (encryptedData , cipher );) {
@@ -329,14 +265,18 @@ private static boolean writeDecryptedFile(Path inputPath, Path outputPath, char[
329265 Mac hmac = Mac .getInstance (HASH_AlGORITHM );
330266 hmac .init (macKeySpec );
331267
332- hmac .update (initVector );
333- hmac .update (salt );
334- hmac .update (macSalt );
268+ hmac .update (initVector ); hmac .update (salt ); hmac .update (macSalt );
335269 final byte [] computedMac = computeMac (hmac , outputPath );
270+
336271 if (!Arrays .equals (givenMac , computedMac )) {
337272 throw new SecurityException ("Authentication failed, file may have been tampered with" );
338273 }
339-
274+
275+ // Display the Base64 encoded versions of the values used for decryption - for marking and testing
276+ displayInformation (getPair ("Secret Key" , key ), getPair ("Init Vector" , initVector ), getPair ("Salt" , salt ),
277+ getPair ("Mac Key" , macKey ), getPair ("Mac salt" , macSalt ), getPair ("Computed Mac" , computedMac ),
278+ getPair ("Given Mac" , givenMac ));
279+
340280 LOG .info ("Authentication passed, file integrity maintained" );
341281
342282 } catch (IOException ex ) {
@@ -345,4 +285,105 @@ private static boolean writeDecryptedFile(Path inputPath, Path outputPath, char[
345285 }
346286 return true ;
347287 }
288+
289+ /**
290+ * Generates a Secret key with a specified password. The password is added with
291+ * a salt and iterated multiple times before being hased to increase entropy.
292+ * The salt and key lenghts need to be specified to then return a secret key
293+ * encoded in a byte array.
294+ *
295+ * @param password char[] The password specified by the user
296+ * @param salt byte[] A randomly gnerated set of bytes
297+ * @param keyLength int The lenght of the final key, in bits e.g. 128, 256 etc.
298+ * @return byte[] An encoded byte array of the secret key
299+ * @throws NoSuchAlgorithmException
300+ * @throws InvalidKeySpecException
301+ */
302+ private static byte [] generateKey (char [] password , byte [] salt , int keyLength ) throws NoSuchAlgorithmException ,
303+ InvalidKeySpecException {
304+ PBEKeySpec passwordKeySpec = new PBEKeySpec (password , salt , ITERATION_COUNT , keyLength );
305+ SecretKeyFactory keyFactory = SecretKeyFactory .getInstance ("PBKDF2WithHmacSHA256" );
306+ SecretKey secretKey = keyFactory .generateSecret (passwordKeySpec );
307+ return secretKey .getEncoded ();
308+ }
309+
310+ /**
311+ * Creates and initialises a Cipher with the specified key and initialisation vector.
312+ * The cipher can be initialised in either Encrypt or Decrypt mode. The mode argument
313+ * specifies which mode to initialise the cipher in. Only two values are accepted by
314+ * the 'mode' argunment, 1 for Encryptoin and 2 for Decryption
315+ *
316+ * @param key byte[] The key to be used to generate the cipher
317+ * @param initVector byte[] The IV to be used to create the cipher
318+ * @param mode int The mode in which to initialise the cipher, Encrypt = 1; Decrypt = 2
319+ * @return Cipher The initialised cipher in the specified mode
320+ * @throws InvalidKeyException
321+ * @throws InvalidAlgorithmParameterException
322+ * @throws NoSuchAlgorithmException
323+ * @throws NoSuchPaddingException
324+ */
325+ private static Cipher createCipher (byte [] key , byte [] initVector , int mode ) throws InvalidKeyException ,
326+ InvalidAlgorithmParameterException , NoSuchAlgorithmException , NoSuchPaddingException {
327+ if (mode != 1 && mode != 2 ) { throw new IllegalArgumentException ("Invalid Mode value, Encrypt = 1, Decrypt = 2" ); }
328+
329+ // Initialize Parameter specs
330+ IvParameterSpec iv = new IvParameterSpec (initVector );
331+ SecretKeySpec skeySpec = new SecretKeySpec (key , ALGORITHM );
332+
333+ // Initialize cipher
334+ Cipher cipher = Cipher .getInstance (CIPHER );
335+ if (mode == Cipher .ENCRYPT_MODE ) {
336+ cipher .init (Cipher .ENCRYPT_MODE , skeySpec , iv );
337+ } else {
338+ cipher .init (Cipher .DECRYPT_MODE , skeySpec , iv );
339+ }
340+
341+ return cipher ;
342+ }
343+
344+ /**
345+ * Computes a Message authencitaion code for a given inputfile
346+ * Takes in an initialised hmac which gets updated with the file's
347+ * contents line by line. Once completed the doFinal method will
348+ * return a byte array with the computed Mac.
349+ *
350+ * @param hmac Mac The initialised Mac object
351+ * @param filePath Path The file path
352+ * @return byte[] The file's computed Mac
353+ */
354+ private static byte [] computeMac (Mac hmac , Path filePath ) {
355+ try (InputStream fin = Files .newInputStream (filePath );) {
356+ final byte [] bytes = new byte [1024 ];
357+ for (int length = fin .read (bytes ); length != -1 ; length = fin .read (bytes )){
358+ hmac .update (bytes , 0 , length );
359+ }
360+ } catch (IOException e ) {
361+ LOG .log (Level .SEVERE , "IOException caught - Please check filepath specified" );
362+ System .exit (0 );
363+ }
364+
365+ return hmac .doFinal ();
366+ }
367+
368+ /**
369+ * A helper method for displayInformation, creates an Object array which cointans
370+ * a name and a value, which can be later used to display it on the console
371+ * @return Object[] An array consisting of a name and value
372+ */
373+ private static Object [] getPair (String name , byte [] value ) { return new Object [] {name , value }; }
374+
375+ /**
376+ * Allows for the input of any number of object array created by the getPair method.
377+ * Each object array with it's name and value are printed out in its Base64 encoded
378+ * version on the console. Method used for testing and marking purposes.
379+ *
380+ * @param args Object[] Any number of Object arrays consisting of a name and a value
381+ */
382+ private static void displayInformation (Object []... args ) {
383+ System .out .print ("\n <---------------------------------------->\n " );
384+ for (Object [] o : args ) {
385+ System .out .println (o [0 ] + ": " + Base64 .getEncoder ().encodeToString ((byte []) o [1 ]));
386+ }
387+ System .out .print ("<---------------------------------------->\n \n " );
388+ }
348389}
0 commit comments