Skip to content

Commit 09ef318

Browse files
committed
refactor: helper methods and docs added
1 parent fac30fc commit 09ef318

File tree

1 file changed

+126
-85
lines changed

1 file changed

+126
-85
lines changed

src/FileEncryptor.java

Lines changed: 126 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import 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

Comments
 (0)