1717import javax .crypto .Cipher ;
1818import javax .crypto .CipherInputStream ;
1919import javax .crypto .CipherOutputStream ;
20+ import javax .crypto .Mac ;
2021import javax .crypto .NoSuchPaddingException ;
2122import javax .crypto .spec .IvParameterSpec ;
2223import javax .crypto .spec .SecretKeySpec ;
@@ -31,6 +32,7 @@ public class FileEncryptor {
3132 private static final Logger LOG = Logger .getLogger (FileEncryptor .class .getSimpleName ());
3233
3334 private static final String ALGORITHM = "AES" ;
35+ private static final String HASH_AlGORITHM = "HmacSHA256" ;
3436 private static final String CIPHER = "AES/CBC/PKCS5PADDING" ;
3537
3638 public static void main (String [] args ) throws Exception {
@@ -116,11 +118,16 @@ public static void encrypt(byte[] key, String inputPath, String outputPath) thro
116118 System .out .println ("IV is: " + Base64 .getEncoder ().encodeToString (initVector ));
117119 System .out .print ("<---------------------------------------->\n \n " );
118120
119- // Initialize Key, Vector Specfications and the Cipher mode
121+ // Initialize Vector and Keys
120122 IvParameterSpec iv = new IvParameterSpec (initVector );
121123 SecretKeySpec skeySpec = new SecretKeySpec (key , ALGORITHM );
124+ SecretKeySpec macKey = new SecretKeySpec (key , HASH_AlGORITHM );
125+
126+ // Initialize cipher and Mac
122127 Cipher cipher = Cipher .getInstance (CIPHER );
123128 cipher .init (Cipher .ENCRYPT_MODE , skeySpec , iv );
129+ Mac hmac = Mac .getInstance (HASH_AlGORITHM );
130+ hmac .init (macKey );
124131
125132 File outputFile = new File (outputPath );
126133 // Create the output file if it doesn't exist
@@ -129,8 +136,10 @@ public static void encrypt(byte[] key, String inputPath, String outputPath) thro
129136 final Path plaintextFile = Paths .get (inputPath );
130137 final Path encryptedFile = Paths .get (outputPath );
131138
139+ byte [] mac = computeMac (hmac , initVector , plaintextFile );
140+
132141 // Write plaintext into ciphertext
133- if (writeEncryptedFile (plaintextFile , encryptedFile , cipher )) {
142+ if (writeEncryptedFile (plaintextFile , encryptedFile , cipher , mac )) {
134143 LOG .info ("Encryption finished, saved at " + encryptedFile );
135144 } else {
136145 LOG .log (Level .WARNING , "Encryption Failed" );
@@ -151,11 +160,15 @@ public static void encrypt(byte[] key, String inputPath, String outputPath) thro
151160 * specifications in ENCRYPT mode
152161 * @return boolean True if encryption successful False otherwise
153162 */
154- private static boolean writeEncryptedFile (Path inputPath , Path outputPath , Cipher cipher ) {
163+ private static boolean writeEncryptedFile (Path inputPath , Path outputPath , Cipher cipher , byte [] mac ) {
155164 try (InputStream fin = Files .newInputStream (inputPath );) {
156165
157166 try (FileOutputStream fout = new FileOutputStream (outputPath .toFile ());) {
167+ // Write Metadata
168+ fout .write (cipher .getIV ().length );
158169 fout .write (cipher .getIV ());
170+ fout .write (mac .length );
171+ fout .write (mac );
159172
160173 try (CipherOutputStream cipherOut = new CipherOutputStream (fout , cipher );) {
161174 final byte [] bytes = new byte [1024 ];
@@ -172,6 +185,20 @@ private static boolean writeEncryptedFile(Path inputPath, Path outputPath, Ciphe
172185 return true ;
173186 }
174187
188+ private static byte [] computeMac (Mac hmac , byte [] initVector , Path filePath ) {
189+ hmac .update (initVector );
190+ try (InputStream fin = Files .newInputStream (filePath );) {
191+ final byte [] bytes = new byte [1024 ];
192+ for (int length = fin .read (bytes ); length != -1 ; length = fin .read (bytes )){
193+ hmac .update (bytes , 0 , length );
194+ }
195+ } catch (IOException e ) {
196+
197+ }
198+
199+ return hmac .doFinal ();
200+ }
201+
175202 /**
176203 * This function is invoked when the 'key' option is specified
177204 * in the command line. Generates a random 128 bit key which
@@ -238,14 +265,21 @@ private static boolean writeDecryptedFile(Path inputPath, Path outputPath, byte[
238265 try (InputStream encryptedData = Files .newInputStream (inputPath );){
239266
240267 // Read metadata from the input file
241- byte [] initVector = new byte [16 ];
268+ byte [] initVector = new byte [encryptedData . read () ];
242269 encryptedData .read (initVector );
243-
244- // Initialise cipher, IV and key specifications
270+ byte [] givenMac = new byte [encryptedData .read ()];
271+ encryptedData .read (givenMac );
272+
273+ // Create key specifications
245274 IvParameterSpec iv = new IvParameterSpec ((initVector ));
246275 SecretKeySpec skeySpec = new SecretKeySpec (key , ALGORITHM );
276+ SecretKeySpec macKey = new SecretKeySpec (key , HASH_AlGORITHM );
277+
278+ // Initialise cipher and HMac
247279 Cipher cipher = Cipher .getInstance (CIPHER );
248280 cipher .init (Cipher .DECRYPT_MODE , skeySpec , iv );
281+ Mac hmac = Mac .getInstance (HASH_AlGORITHM );
282+ hmac .init (macKey );
249283
250284 // Read cipertext data and write plaintext data
251285 try (CipherInputStream decryptStream = new CipherInputStream (encryptedData , cipher );) {
@@ -255,7 +289,16 @@ private static boolean writeDecryptedFile(Path inputPath, Path outputPath, byte[
255289 decryptedOut .write (bytes , 0 , length );
256290 }
257291 }
258- }
292+ }
293+
294+ // Check authentication and file integerity
295+ byte [] computedMac = computeMac (hmac , initVector , outputPath );
296+ if (!Arrays .equals (givenMac , computedMac )) {
297+ throw new SecurityException ("Authentication failed, file may have been tampered with" );
298+ } else {
299+ LOG .info ("Authentication passed, file integrity maintained" );
300+ }
301+
259302 } catch (IOException ex ) {
260303 Logger .getLogger (FileEncryptor .class .getName ()).log (Level .SEVERE , "IOException caught" );
261304 return false ;
@@ -266,6 +309,7 @@ private static boolean writeDecryptedFile(Path inputPath, Path outputPath, byte[
266309 } catch (InvalidAlgorithmParameterException e ) {
267310 Logger .getLogger (FileEncryptor .class .getName ()).log (Level .SEVERE , "InvalidAlgorithmParameterException caught,"
268311 + " file may have been modified, or invalid key was specified." );
312+ return false ;
269313 }
270314 return true ;
271315 }
0 commit comments