package cologne.eck.peafactory.crypto;
/*
* Peafactory - Production of Password Encryption Archives
* Copyright (C) 2015 Axel von dem Bruch
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* See: http://www.gnu.org/licenses/gpl-2.0.html
* You should have received a copy of the GNU General Public License
* along with this library.
*/
/**
* The EAX mode of operation: Encryption with authentication.
* Contains function for byte blocks and files.
*
* There is no padding, the length of the MAC can be up to the
* block length of the underlying cipher.
*/
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import javax.swing.JOptionPane;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.modes.EAXBlockCipher;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import cologne.eck.all_peas.data.Attachments;
import cologne.eck.all_peas.data.PeaProperties;
import cologne.eck.all_peas.files.FileComposer;
import cologne.eck.all_peas.files.FileTypePanel;
import cologne.eck.all_peas.gui.PeaDialog;
import cologne.eck.all_peas.gui.PswDialogView;
import cologne.eck.peafactory.crypto.kdf.KeyDerivation;
import cologne.eck.tools.Comparator;
import cologne.eck.tools.Zeroizer;
public final class EAXMode implements AuthenticatedEncryption {
// block of file to encrypt/decrypt: Must be a multiple of any used block size64*16
private static final int FILE_BLOCK_SIZE = 16 * 1024 * 1024;// 16 MiB
// progress verticalBar:
private boolean showProgressBar = false; // show the verticalBar or not
// - depends on number of files and file size
private double progress = 0; // progress value to show in the progress verticalBar
private double progressPro100 = 0; // helper value: size of 1/100 of size of all files
private int progressHelper = 0; // helper value, to check for increments of progress
private double blockProgress = 0; // progress of one block of FILE_BLOCK_SIZE
// print error message once if occurred, not for each file
private boolean read_write_failed = false;
private boolean file_not_found = false;
private boolean unexpected_error = false;
private boolean file_identifier_failed = false;
private boolean inappropriate_file_length = false;
private boolean closing_file_failed = false;
private boolean salt_differs = false;
private boolean password_failed = false;
private String unexpectedError = "";
/**
* Encrypt/decrypt an array of bytes
*
* @param forEncryption - true for encryption, false for decryption
* @param input - plain text or cipher text
* @param key - the cryptographic key for the cipher
* @param nonce - unique nonce of 16 byte
* @return - plain text or cipher text
*/
public final byte[] processBytes(
boolean forEncryption,
byte[] input,
byte[] key,
byte[] nonce) {
int resultLen = 0;// proceeded bytes
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(key);
}
KeyParameter aeKey = new KeyParameter(key);
int macLength = CipherStuff.getCipherAlgo().getBlockSize();
if (macLength < 16) {
System.out.println("Warning: short mac size: " + macLength);
}
if (nonce.length != Attachments.getNonceSize()) {
System.out.println("Wrong size of nonce: " + nonce.length + ", must be: " + Attachments.getNonceSize());
throw new IllegalStateException("Wrong nonce size");
}
EAXBlockCipher eaxCipher = new EAXBlockCipher(CipherStuff.getCipherAlgo());
AEADParameters params = new AEADParameters(aeKey, macLength * 8, nonce);
eaxCipher.init(forEncryption, params);
byte[] result = new byte[eaxCipher.getOutputSize(input.length)];
resultLen = eaxCipher.processBytes(input, 0, input.length, result, 0);
try {
resultLen += eaxCipher.doFinal(result, resultLen);
// KeyParameter uses a copy of the key:
Zeroizer.zero(aeKey.getKey());
} catch (IllegalStateException e) {
CipherStuff.setErrorMessage("internal_application_error");
System.err.println("EAXMode - processBytes: " + e.toString());
return null;
} catch (InvalidCipherTextException e) {
System.err.println("Authentication failed. ");
JOptionPane.showMessageDialog(PswDialogView.getView(),
PeaProperties.getBundle().getString("integrity_check_failed_message"),
PeaProperties.getBundle().getString("authentication_error"),
JOptionPane.ERROR_MESSAGE);
} catch (Exception e) {
CipherStuff.setErrorMessage("unexpected_error");
System.err.println("EAXMode processBytes: an unexpected error occured: " + e.toString());
return null;
}
return result;
}
/**
* Encrypt/decrypt an array of bytes but without MAC (MAC size 0)
* - this is used to check the password identifier for each file before
* the decryption of the file starts.
*
* @param forEncryption - true for encryption, false for decryption
* @param input - plain text or cipher text
* @param key - the cryptographic key for the cipher
* @param nonce - unique Nonce of 16 byte
* @return - plain text or cipher text
*/
public final byte[] processBytesNoMAC(
boolean forEncryption,
byte[] input,
byte[] key,
byte[] nonce) {
//System.out.println("EAXMode processBytesNoMac" + "\nfor encryption: " + forEncryption);
//Help.printBytes("input", input);
//Help.printBytes("key", key);
//Help.printBytes("nonce", nonce);
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(key);
}
int resultLen = 0;// proceeded bytes
KeyParameter aeKey = new KeyParameter(key);
if (nonce.length != Attachments.getNonceSize()) {
System.err.println("Wrong size of nonce: " + nonce.length + ", must be: " + Attachments.getNonceSize());
throw new IllegalStateException("Wrong nonce size");
}
EAXBlockCipher eaxCipher = new EAXBlockCipher(CipherStuff.getCipherAlgo());
AEADParameters params = new AEADParameters(aeKey, 0, nonce);
eaxCipher.init(forEncryption, params);
byte[] result = new byte[eaxCipher.getOutputSize(input.length)];
resultLen = eaxCipher.processBytes(input, 0, input.length, result, 0);
try {
resultLen += eaxCipher.doFinal(result, resultLen);
// KeyParameter uses a copy of the key:
Zeroizer.zero(aeKey.getKey());
} catch (IllegalStateException e) {
CipherStuff.setErrorMessage("internal_application_error");
System.err.println("EAXMode - processBytesNoMAC: " + e.toString());
return null;
} catch (InvalidCipherTextException e) {
System.err.println("Authentication failed. ");
CipherStuff.setErrorMessage("authentication_failed");
return null;
} catch (Exception e) {
System.err.println("EAXMode processBytesNoMac: an unexpected error occured: " + e.toString());
CipherStuff.setErrorMessage("unexpected_error");
return null;
}
return result;
}
/**
* Encrypt an array of files, used by FilePEA
*
* @param fileNames array of file names
* @param keyMaterial derived material from KDF, contains the key
* @param encryptBySessionKey whether encrypt and store the derived key
* or zeroize it
* @param fileComposer FileComposer, used to display the progressPro100 of encryption
* @return array of error messages, with the same indexing as fileNames
*/
public final String[] encryptFiles( String[] fileNames, byte[] keyMaterial,
boolean encryptBySessionKey, FileComposer fileComposer){
// Attachments:
// NO PADDING (EAX mode)
// 1. MAC size == block size
// 2. 16 byte pswIdentifier to ciphertext (encrypted _PEA_FACTORY_ID_)
// 3. 16 byte nonce to ciphertext
// 4. 32 byte salt to ciphertext
// 5. 8 byte fileIdentifier to ciphertext
//return value:
String[] errorMessages = new String[fileNames.length];
int fileNumber = fileNames.length;
long allSize = fileComposer.getFileModel().getAllSize();
// TODO gui raus (progressBar)
FileTypePanel filePanel = fileComposer.getFileTypePanel();
// initialize the progress verticalBar if required:
progress = 0;
setProgressValues(allSize, fileNumber);
//------------------
// get the key:
//------------------
byte[] keyBytes = CipherStuff.getInstance().detectKey(keyMaterial);
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(keyBytes);
}
KeyParameter key = new KeyParameter(keyBytes);
int blockSize = CipherStuff.getCipherAlgo().getBlockSize();
int macSizeBytes = blockSize;// maximum MAC size for EAX
if (macSizeBytes < 16) { // this should not happen
System.out.println("Warning: short mac size: " + macSizeBytes);
}
int nonceSize = Attachments.getNonceSize();
EAXBlockCipher eaxCipher = new EAXBlockCipher(CipherStuff.getCipherAlgo());
byte[] attachedSalt = null;
if (KeyDerivation.getSalt() == null) {
// KeyDerivation.setSalt(Attachments.getProgramRandomBytes());
//Help.printBytes("EAXMode encrypt - programRandomBytes", Attachments.getProgramRandomBytes());
attachedSalt = new RandomStuff().createRandomBytes(KeyDerivation.getSaltSize());
//Help.printBytes("new attachedSalt", attachedSalt);
KeyDerivation.setSalt(attachedSalt);// this will set attachedSalt and update the salt
}
//----------------------------------
// FILE LOOP: ======================
for (int fileCounter = 0; fileCounter < fileNames.length; fileCounter++) {
RandomAccessFile f = null;
File file = new File(fileNames[fileCounter]);
if (! file.canRead() || ! file.canWrite() ){
errorMessages[fileCounter] = "read_write_failed";
read_write_failed = true;
continue;
}
try {
if (new File( fileNames[fileCounter]).isDirectory() ) {
continue;
}
f = new RandomAccessFile( fileNames[fileCounter], "rwd" );
long fileSizeLong = f.length();
// check overflow:
if (fileSizeLong < 0) {
System.out.println("ERROR: this file is too large...");
f.close();
errorMessages[fileCounter] = "file_too_large";
continue;
}
// check if file is too large to append attachments
if (fileSizeLong > (Long.MAX_VALUE - 200)){ // at most 200 for block size 128...
filePanel.closeProgressTask();
// FileTypePanel will not display errorMessages...
JOptionPane.showMessageDialog(PswDialogView.getView(),
PeaProperties.getBundle().getString("file") + ": " + fileNames[fileCounter] + "\n"
+ PeaProperties.getBundle().getString("file_too_large") + " (1)\n"
+ PeaProperties.getBundle().getString("file_skipped") + ".",
PeaProperties.getBundle().getString("error"),
JOptionPane.ERROR_MESSAGE);
errorMessages[fileCounter] = "### file is too large";
System.err.println("File is too large: \n" + fileNames[fileCounter]);
f.close();
continue;
}
// round down: get block number except last block
long blockNumber = fileSizeLong / FILE_BLOCK_SIZE;
if (fileSizeLong % FILE_BLOCK_SIZE == 0 && fileSizeLong != 0) {
// always process a last block:
blockNumber--;
}
// update the progress verticalBar, depending on file size
showFileProgress(filePanel, fileSizeLong);
if (blockNumber < 0){ // bug, error or overflow
errorMessages[fileCounter] = "### Inappropriate file size";
System.err.println("Inappropriate file size: \n" + fileNames[fileCounter]);
filePanel.closeProgressTask();
// FileTypePanel will not display errorMessages...
JOptionPane.showMessageDialog(PswDialogView.getView(),
PeaProperties.getBundle().getString("file") + ": " + fileNames[fileCounter] + "\n"
+ "~~ Inappropriate file size ~~~\n"
+ PeaProperties.getBundle().getString("file_skipped") + ".",
PeaProperties.getBundle().getString("error"),
JOptionPane.ERROR_MESSAGE);
// the file was not modified
f.close();
continue;
}
// prepare values for encryption: ================================
// new nonce for each file
byte[] nonce = new RandomStuff().createUniqueBytes(nonceSize);
// initialize the cipher and the parameters:
eaxCipher = new EAXBlockCipher(CipherStuff.getCipherAlgo());
AEADParameters params = new AEADParameters(key, macSizeBytes * 8, nonce, null);//macSize in Bits!
eaxCipher.init(true, params);
int processedBytes = 0;// number of processed bytes
byte[] block = null; // input buffer
byte[] out = null; // output buffer
if (blockNumber > 0) {
out = new byte[FILE_BLOCK_SIZE];
block = new byte[FILE_BLOCK_SIZE];
}
//-----------------------------------------------------
// BLOCK LOOP: =========================================
for (long blockCounter = 0; blockCounter < blockNumber; blockCounter++) { // only full blocks
// read one block from file and store in block
f.seek(blockCounter * FILE_BLOCK_SIZE);
f.read(block, 0, FILE_BLOCK_SIZE);
// we need the processedBytes value later to write at the right point of the file
int newProcessedBytes = eaxCipher.processBytes(block, 0, FILE_BLOCK_SIZE, out, 0);
// store the encrypted block in the file:
f.seek(processedBytes);
f.write(out, 0, FILE_BLOCK_SIZE );
// update processedBytes
processedBytes += newProcessedBytes;
showBlockProgress(filePanel);
} // END BLOCK LOOP
//------------------------------------------------
// process the last (maybe only) block: ==========
// get length of last part of plaintext:
int lastSize = (int) (fileSizeLong - (FILE_BLOCK_SIZE * blockNumber));
// buffer for last bytes
byte[] lastBlock = new byte[lastSize];
// read last bytes
f.seek(processedBytes);
f.read(lastBlock, 0, lastSize);
// get required size for last part of ciphertext
byte[] lastOut = new byte[eaxCipher.getOutputSize( lastSize)];
// process the last part and get number of new processed bytes
int lastProcessedBytes = eaxCipher.processBytes(lastBlock, 0, lastSize, lastOut, 0);
// finalize the encryption process: This will add the MAC
lastProcessedBytes += eaxCipher.doFinal(lastOut, lastProcessedBytes);
// clear the last plaintext block
Zeroizer.zero(lastBlock);
// reset the cipher
eaxCipher.reset();
// write new last bytes
f.seek(processedBytes);
f.write(lastOut, 0, lastOut.length );
// create an identifier to check the password
// this is the encrypted string _PEA_FACTORY_ID_
byte[] pswIdentifierAttachment = Attachments.createPswIdentifierAttachment(keyBytes, nonce);
// add the password identifier and the nonce to the file:
errorMessages[fileCounter] = addFileAttachments(f,
pswIdentifierAttachment, nonce);
if (errorMessages[fileCounter] != null) {
try{
f.close();
} catch (Exception e) {
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
unexpected_error = true;
unexpectedError = e.toString() + " - " + e.getMessage();
} else {
System.err.println(e.toString() + " - " + e.getMessage());
e.printStackTrace();
}
}
continue;
}
f.close();
} catch (FileNotFoundException e) {
errorMessages[fileCounter] = "file_not_found";
file_not_found = true;
//System.err.println("EAXMode " + e.toString() + ", file: " + fileNames[fileCounter]);
//System.err.println("file_probably_used_by_other_program");
closeAfterException(fileNames[fileCounter], f, eaxCipher);
continue;
} catch (IOException e) {
errorMessages[fileCounter] = "read_write_failed";
read_write_failed = true;
closeAfterException(fileNames[fileCounter], f, eaxCipher);
continue;
} catch (Exception e) {
errorMessages[fileCounter] = "unexpected_error";
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
unexpectedError = e.toString() + " - " + e.getMessage();
unexpected_error = true;
} else {
System.err.println(e.toString() + " - " + e.getMessage());
e.printStackTrace();
}
closeAfterException(fileNames[fileCounter], f, eaxCipher);
continue;
}
} // end for
//-----------------
// Handle the keys:
// encrypt or fill
//-----------------
CipherStuff.getInstance().handleKey(key.getKey(), encryptBySessionKey);
if (showProgressBar == true) {
filePanel.closeProgressTask();
}
printErrors("encrypting files"); // print all occurred errors to std.err
return errorMessages;
}
/**
* Decrypt an array of files, used by FilePEA
*
* @param fileNames array of file names
* @param keyMaterial derived material from KDF, contains the key
* @param encryptKeyBySessionKey whether encrypt and store the derived key
* or zeroize it
* @param fileComposer FileComposer, used to display the progressPro100 of encryption
* @return array of error messages, with the same indexing as fileNames
*/
public final String[] decryptFiles( String[] fileNames, byte[] keyMaterial,
boolean encryptKeyBySessionKey, FileComposer fileComposer){
int fileNamesLen = fileNames.length;
if (fileNamesLen == 0) {
return null;
}
//return value:
String[] errorMessages = new String[fileNamesLen];
int fileNumber = fileNames.length;
long allSize = fileComposer.getFileModel().getAllSize();
// TODO gui raus (progressBar)
FileTypePanel filePanel = fileComposer.getFileTypePanel();
// initialize the progress verticalBar if required:
progress = 0;
setProgressValues(allSize, fileNumber);
// Attachments:
// 1. cut fileIdentifier from cipher text - 8 bytes
// 2. cut attachedSalt from cipher text - 32 bytes
// 3. cut nonce from cipher text - 16 bytes
// 4. cut encryptedPswIdentifier from cipher text - 16 bytes
// 5. verify MAC at last block (block size length)
// NO PADDING (EAX)
//------------------
// get the key:
byte[] keyBytes = CipherStuff.getInstance().detectKey(keyMaterial);
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(keyBytes);
}
KeyParameter key = new KeyParameter(keyBytes);
int blockSize = CipherStuff.getCipherAlgo().getBlockSize();
int macSize = blockSize;
int nonceSize = Attachments.getNonceSize();
int pswIdentifierSize = Attachments.getPswIdentifierSize();
long blockNumber = 0;
EAXBlockCipher eaxCipher = new EAXBlockCipher(CipherStuff.getCipherAlgo());
//----------------------------------
// decrypt the files:
// decrypt block by block,
// add short plain text and cipher text to check password
// attach Nonce, attach fileIdentifier
//----------------------------------
int saltLen = KeyDerivation.getSaltSize();
int minimum_size = macSize // = block size of cipher
+ nonceSize // 16
+ pswIdentifierSize // 16
+ saltLen // 32
+ Attachments.getFileIdentifierSize();
//--------------------------------------
// FILE LOOP ===========================
for (int fileCounter = 0; fileCounter < fileNamesLen; fileCounter++) {
RandomAccessFile f = null;
File file = new File(fileNames[fileCounter]);
if (! file.canRead() || ! file.canWrite() ){
read_write_failed = true;
errorMessages[fileCounter] = "read_write_failed";
continue;
}
// last block and processedBytes:
// required to continue writing for authentication error
byte[] lastOut = null; // the last part to write
int processedBytes = 0; // the processed bytes in decryption process
byte[] nonce = null;
try {
if (new File( fileNames[fileCounter]).isDirectory() ) {
continue;
}
f = new RandomAccessFile( fileNames[fileCounter], "rwd" );
long fileSizeLong = f.length();
long originalFileSize = fileSizeLong;// needed for progressBar
// we need the original file size without attachments...
long relevantFileSize = fileSizeLong
- macSize
- nonceSize
- pswIdentifierSize
- Attachments.getFileIdentifierSize()
- saltLen;
// get the number of blocks:
blockNumber = (relevantFileSize / (FILE_BLOCK_SIZE));
if ( blockNumber != 0 && relevantFileSize / (FILE_BLOCK_SIZE) == 0 ) {
// always process the last block extra
blockNumber--;
}
if (fileSizeLong < minimum_size){
/* (macSize // = block size of cipher
+ nonceSize // 16
+ pswIdentifierSize // 16
+ saltLen // 32
+ Attachments.getFileIdentifierSize()) ) { // 8*/
errorMessages[fileCounter] = "inappropriate_file_length";
//System.out.println(file.getName() +", min size: " + minimum_size + ", file size: " + fileSizeLong);
inappropriate_file_length = true;
f.close();
continue;
}
// cut fileIdentifier
if (Attachments.checkFileIdentifier(f, true) == false){ // truncates if true
errorMessages[fileCounter] = "inappropriate_file_identifier";
file_identifier_failed = true;
f.close();
continue;
}
byte[] attachedSalt = Attachments.getAndCutSalt(f, true);
// check if the same salt is used, otherwise the decryption would fail
if (Comparator.compare(attachedSalt, KeyDerivation.getSalt()) == false){
errorMessages[fileCounter] = "salt_differs_from_first_selected_file";
salt_differs = true;
/* System.err.println("different salt compared to salt of first file, file: " + fileNames[fileCounter]
+ ", first file: " + fileNames[0]);*/
Attachments.addBytes(f, attachedSalt);
Attachments.addFileIdentifier(f);
f.close();
//progressBar:
if (showProgressBar == true) {
// close if last file:
if (fileCounter == fileNames.length - 1) {
filePanel.closeProgressTask();
}
}
continue;
}
// get and cut random Nonce for each file
nonce = Attachments.getAndCutNonce( f, true);
// Check and cut pswIdentifier: the encrypted string _PEA_FACTORY_ID_
f.seek(f.length() - pswIdentifierSize);
byte[] pswIdentifierAttachment = new byte[pswIdentifierSize];
f.read(pswIdentifierAttachment);
boolean check = Attachments.checkPswIdentifier(
pswIdentifierAttachment, keyBytes, nonce);
if (check == false) {
errorMessages[fileCounter] = "password_failed";
password_failed = true;
//System.out.println("password failed: identifier not equal, file: " + fileCounter + " - " + fileNames[0]);
// Restore the file:
addFileAttachments(f, nonce);
f.close();
continue;
} else {
// cut PswIdentifier and encryptedPswIdentifiere
f.setLength( f.length() - pswIdentifierSize);
}
byte[] block = null; // buffer for one block
byte[] out = null; // buffer for output
if (blockNumber > 0) {
block = new byte[FILE_BLOCK_SIZE ];
out = new byte[FILE_BLOCK_SIZE];
}
// get the current file size
fileSizeLong = f.length();
// update progress verticalBar
showFileProgress(filePanel, originalFileSize);
// decrypt block-wise
AEADParameters params = new AEADParameters(key, macSize * 8, nonce, null);//associatedText);
eaxCipher = new EAXBlockCipher(CipherStuff.getCipherAlgo());
eaxCipher.init(false, params);
// the number of processed bytes:
processedBytes = 0;
//----------------------------------------
// BLOCK LOOP: process the blocks ========
for (int blockCounter = 0; blockCounter < blockNumber; blockCounter++) { // only full blocks
//System.out.println("block counter: " + blockCounter);
// read one block from file and store in block
f.seek(blockCounter * FILE_BLOCK_SIZE);
f.read(block, 0, FILE_BLOCK_SIZE);
// we need the processedBytes value later to write at the right point of the file
int newProcessedBytes = eaxCipher.processBytes(block, 0, FILE_BLOCK_SIZE, out, 0);
// Not all bytes of first block are processed in decryption process!
// the rest will be written in the next step
// store the encrypted block in the file:
f.seek(processedBytes);
f.write(out, 0, FILE_BLOCK_SIZE );
// update processedBytes
processedBytes += newProcessedBytes;
showBlockProgress(filePanel);
} // END BLOCK LOOP
Zeroizer.zero(out);
// === process the last (maybe only) block: ===
// get the length of the ciphertext buffer:
byte[] lastBlock = new byte[(int) (fileSizeLong - (FILE_BLOCK_SIZE * blockNumber) )];
// read the last block and store in buffer:
f.seek(blockNumber * FILE_BLOCK_SIZE);
f.read(lastBlock, 0, lastBlock.length );
// get the length of the plaintext buffer (without MAC and padding)
lastOut = new byte[eaxCipher.getOutputSize(lastBlock.length)];
// get the number of new processedBytes, to use in doFinal
int lastProcessedBytes = eaxCipher.processBytes(lastBlock, 0, lastBlock.length, lastOut, 0);
// finalize the decryptioN. This may throw an InvalidCipherTextException,
// the last block then is written in handleAuthenticationError
lastProcessedBytes += eaxCipher.doFinal(lastOut, lastProcessedBytes); // + macSize
// seek at the end of the BLOCK LOOP
// this is != (FILE_BLOCK_SIZE * blockNumber) for decryption
f.seek(processedBytes);
// write the last block of the plaintext
f.write(lastOut, 0, lastOut.length );
// truncate the file (there are some zero bytes at the end)
f.setLength(processedBytes + lastOut.length);
f.close();
} catch (InvalidCipherTextException icte) {// Authentication failed
// this will occur after doFinal for the last block
// the last block must be written...
// if there is only one block: option to break
String errorMessage = handleAuthenticationError(
f, fileNames[fileCounter], filePanel,
processedBytes, lastOut, nonce, keyBytes);
errorMessages[fileCounter] = errorMessage;
if (errorMessage != null) {
errorMessages[fileCounter] = errorMessage;
}
System.out.println("Decryption continues with next file... ");
continue;
} catch (FileNotFoundException e) {
errorMessages[fileCounter] = "file_not_found";
file_not_found = true;
//System.err.println("EAXMode " + e.toString() + ", file: " + fileNames[fileCounter]);
closeAfterException(fileNames[fileCounter], f, eaxCipher);
continue;
} catch (IOException e) {
errorMessages[fileCounter] = "read_write_failed";
read_write_failed = true;
closeAfterException(fileNames[fileCounter], f, eaxCipher);
continue;
} catch (Exception e) {
errorMessages[fileCounter] = "unexpected_error";
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
unexpectedError = e.toString() + " - " + e.getMessage();
unexpected_error = true;
} else {
System.err.println(e.toString() + " - " + e.getMessage());
e.printStackTrace();
}
closeAfterException(fileNames[fileCounter], f, eaxCipher);
continue;
}
if (eaxCipher != null) {
try {
eaxCipher.reset();
} catch (Exception e1) {
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
unexpected_error = true;
unexpectedError = e1.toString() + " - " + e1.getMessage();
} else {
System.err.println(e1.toString() + " - " + e1.getMessage());
e1.printStackTrace();
}
}
}
} // end for files
//---------------
// Handle keys:
// encrypt or fill
//----------------
CipherStuff.getInstance().handleKey(key.getKey(), encryptKeyBySessionKey);
if (showProgressBar == true) {
filePanel.closeProgressTask();
}
printErrors("decrypting files"); // print all occurred errors to std.err
return errorMessages;
}
//================= Helper methods=====================
// handles InvalidCipherTextException in decryption process (MAC failed):
// The last part of the file is not written, it must be written here
private String handleAuthenticationError( RandomAccessFile f,
String fileName, FileTypePanel ftp,
int processedBytes, byte[] lastOut,
byte[] originalNonce, byte[] key) {
String errorMessage = null;
// close the progressBar, otherwise the program stops
if (showProgressBar == true) {
ftp.closeProgressTask();
}
System.out.println("!!!!!!!!!!!!!!!!! Data integrity violated !!!!!!!!!!!!!!!!!!!");
System.out.println(fileName + ", processed bytes: " + processedBytes);
System.err.println("Authentication failed!\n");
System.err.println("The file " + fileName + " was modified after encryption (processed bytes: " + processedBytes + ").");
if(PeaProperties.getWorkingMode().equals("-r")){ // rescue mode:
System.out.println("Rescue mode: Integrity checks are ignored...");
} else {
if (processedBytes < FILE_BLOCK_SIZE){ // file was not modified
// option to break...
int result = PeaDialog.showQuestion(ftp,
PeaProperties.getBundle().getString("file") + ": " + fileName + "\n"
+ PeaProperties.getBundle().getString("integrity_check_failed_message"),
PeaProperties.getBundle().getString("data_integrity_violated"),
2, 0); //OK_CANCEL_OPTION, ERROR_MESSAGE
if (result != 0){ // not ok: cancel or close
try {
System.out.println("Try to restore the previous (modified) state...");
byte[] pswIdenitifier = Attachments.createPswIdentifierAttachment(key, originalNonce);
Attachments.addBytes(f, pswIdenitifier);
addFileAttachments(f, originalNonce);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Decryption cancelled, file " + fileName + " remains "
+ "in encrypted (modified) state.");
return "### decryption cancelled: " + fileName;
}
} else {
// only error message:
PeaDialog.showMessage(ftp,
PeaProperties.getBundle().getString("file") + ": " + fileName + "\n"
+ PeaProperties.getBundle().getString("integrity_check_failed_message"),
PeaProperties.getBundle().getString("data_integrity_violated"),
0); // ERROR_MESSAGE
}
}
/* JOptionPane.showMessageDialog(PswDialogView.getView(),
PeaProperties.getBundle().getString("file") + ": " + fileName + "\n"
+ PeaProperties.getBundle().getString("integrity_check_failed_message"),
PeaProperties.getBundle().getString("data_integrity_violated"),
JOptionPane.ERROR_MESSAGE); */
// progressBar stops the program: stop automatically and initialize with progress value
if (showProgressBar == true) {
ftp.startProgressTask();
ftp.setProgressValue((int)progress);
}
// Write the last part of the file:
// seek at the end of the BLOCK LOOP
System.out.println("try to write the invalid last block...");
try {
f.seek(processedBytes);
// write the last block of the plaintext
f.write(lastOut, 0, lastOut.length );
// truncate the file (there are some zero bytes at the end)
f.setLength(processedBytes + lastOut.length);
f.close();
System.out.println("last block written");
System.out.println("Warning: Although the file is still in modified state, \n"
+ "this error message will not occur again");
} catch (IOException e) {
System.out.println("Writing of last block failed: " + e.toString());
errorMessage = "### writing of last block failed";
} catch (Exception e1) {
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
unexpectedError = e1.toString() + " - " + e1.getMessage();
unexpected_error = true;
} else {
System.err.println(e1.toString() + " - " + e1.getMessage());
e1.printStackTrace();
}
}
return errorMessage; // always null except for break decryption option
}
private void setProgressValues(long allSize, int fileNumber) {
progress = 0;
progressHelper = 0;
blockProgress = 0;
//System.out.println("allSize: " + allSize + " FILE_BLOCK_SIZE: " + FILE_BLOCK_SIZE + " fileNumber: " + fileNumber);
// Set the progressPro100 verticalBar for many or large files:
if (fileNumber > 64 || allSize > FILE_BLOCK_SIZE){
showProgressBar = true;
} else {
showProgressBar = false;
return;
}
if (showProgressBar == true) {
progressPro100 = (100.0 / allSize);
blockProgress = FILE_BLOCK_SIZE * progressPro100;
} else {
progressPro100 = 0;
}
//System.out.println("progressPro100: " + progressPro100 + ", blockProgress: " + blockProgress);
}
// show the progress for one file
private void showFileProgress(FileTypePanel filePanel, long fileSizeLong) {
// show the progress:
if (showProgressBar == true) {
double partOfProgress = fileSizeLong * progressPro100;
if (partOfProgress > 1) {
if (fileSizeLong < FILE_BLOCK_SIZE) {
progress += partOfProgress;
filePanel.setProgressValue((int)progress);
} else {
// show only last block progress:
// block-wise progress is shown in loop
progress += (fileSizeLong % FILE_BLOCK_SIZE) * progressPro100;
filePanel.setProgressValue((int)progress);
}
} else {
progress += partOfProgress;
if (progress - progressHelper > 1) {
filePanel.setProgressValue((int)progress);
progressHelper = (int) progress;
}
}
}
}
private void showBlockProgress(FileTypePanel filePanel){
if (showProgressBar == true) {
progress += blockProgress;
if (blockProgress > 1) {
filePanel.setProgressValue((int)progress);
} else {
if (progress - progressHelper > 5) { // show all steps of 5
filePanel.setProgressValue((int)progress);
progressHelper = (int) progress;
}
}
}
}
// add: 1. pswIdentifierAttachment, 2. nonce
private String addFileAttachments(
RandomAccessFile f,
byte[] pswIdentifierAttachment,
byte[] originalNonce){
String errorMessage = null;
try {
// add pswIdentifier to ciphertext file
// to check the password before decryption later
f.seek( f.length() ); // set file pointer to end
f.write(pswIdentifierAttachment, 0, pswIdentifierAttachment.length);
f.seek(f.length() );
errorMessage = addFileAttachments(f, originalNonce);
} catch (IOException ioe) {
read_write_failed = true;
errorMessage = "read_write_failed";
try {
f.close();
} catch (IOException e1) {
closing_file_failed = true;
read_write_failed = true;
errorMessage += " - " + e1.toString();
}
} catch (Exception e) {
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
unexpected_error = true;
unexpectedError = e.toString() + " - " + e.getMessage();
} else {
System.err.println(e.toString() + " - " + e.getMessage());
e.printStackTrace();
}
try {
f.close();
} catch (IOException e1) {
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
unexpected_error = true;
unexpectedError = e1.toString() + " - " + e1.getMessage();
} else {
System.err.println(e1.toString() + " - " + e1.getMessage());
e1.printStackTrace();
}
}
}
return errorMessage;
}
// add: 1. nonce, 2. salt, 3, fileIdentifier
private String addFileAttachments(
RandomAccessFile f,
byte[] originalNonce)
throws IOException, Exception {
String errorMessage = null;
f.seek(f.length() );
// add nonce to ciphertext file
Attachments.addNonce(f, originalNonce);
f.seek(f.length() );
Attachments.addSalt(f, KeyDerivation.getSalt());
f.seek(f.length() );
// add fileIdetifier to check later if this file was encrypted with this archive
Attachments.addFileIdentifier(f);
return errorMessage;
}
private void closeAfterException(String fileName, RandomAccessFile f, EAXBlockCipher eaxCipher) {
if (f != null) {
try {
// maybe the exception occurred after opening:
f.close();
} catch (IOException e) {
closing_file_failed = true;
// reset the cipher
if (eaxCipher != null) {
try {
eaxCipher.reset();
} catch (Exception e1) {
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
unexpected_error = true;
unexpectedError = e1.toString() + " - " + e1.getMessage();
} else {
System.err.println(e1.toString() + " - " + e1.getMessage());
e1.printStackTrace();
}
}
}
return;
} catch (NullPointerException npe) {
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
closing_file_failed = true;
unexpected_error = true;
unexpectedError = "File is probably used by another program";
} else {
System.err.println(npe.toString() + " - " + npe.getMessage());
npe.printStackTrace();
}
if (eaxCipher != null) {
try {
eaxCipher.reset();
} catch (Exception e1) {
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
unexpected_error = true;
unexpectedError = e1.toString() + " - " + e1.getMessage();
} else {
System.err.println(e1.toString() + " - " + e1.getMessage());
e1.printStackTrace();
}
}
}
return;
} catch (Exception e) {
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
unexpectedError = e.toString() + " - " + e.getMessage();
unexpected_error = true;
closing_file_failed = true;
} else {
System.err.println(e.toString() + " - " + e.getMessage());
e.printStackTrace();
}
if (eaxCipher != null) {
try {
eaxCipher.reset();
} catch (Exception e1) {
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
unexpectedError = e1.toString() + " - " + e1.getMessage();
unexpected_error = true;
closing_file_failed = true;
} else {
System.err.println(e1.toString() + " - " + e1.getMessage());
e1.printStackTrace();
}
}
}
return;
}
}
if (eaxCipher != null) {
try {
eaxCipher.reset();
} catch (Exception e) {
if ( ! PeaProperties.getWorkingMode().equals("-t")) {
unexpectedError = e.toString() + " - " + e.getMessage();
unexpected_error = true;
} else {
System.err.println(e.toString() + " - " + e.getMessage());
e.printStackTrace();
}
}
}
}
/*
* Collect errors. Avoid printing errors for each file
*/
private void printErrors(String processName){
if ( read_write_failed == true ||
unexpected_error == true ||
file_identifier_failed == true ||
inappropriate_file_length == true ||
closing_file_failed == true ||
salt_differs == true ||
password_failed == true ||
file_not_found == true) {
StringBuilder sb = new StringBuilder();
if (read_write_failed == true){
sb.append("Access denied, can not read or write files.\n");
}
if (file_not_found == true){
sb.append("A file was not found. This file is probably used by another program");
}
if (unexpected_error == true){
sb.append("An unexpected error occured:\n " + unexpectedError);
}
if (file_identifier_failed == true){
sb.append("File were not encrypted with this application.\n");
}
if (inappropriate_file_length == true){
sb.append("Files were too short to be encrypted with this application.\n");
}
if (closing_file_failed == true){
sb.append("Closing files failed.\n");
}
if (salt_differs == true){
sb.append("Different salt occurred compared to salt of first file.\n");
}
if (password_failed == true){
sb.append("Password failed for selected file.\n");
}
// print the errors:
System.err.println("The following errors occurred during the process (" + processName + "):" );
System.err.println(new String(sb));
// reset values:
read_write_failed = false;
file_not_found = false;
unexpected_error = false;
file_identifier_failed = false;
inappropriate_file_length = false;
closing_file_failed = false;
salt_differs = false;
password_failed = false;
unexpectedError = "";
}
}
}