Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add util class for encryption and decryption #36

Merged
merged 6 commits into from Oct 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -19,3 +19,4 @@ src/test/data/sandbox/

# MacOS custom attributes files created by Finder
.DS_Store
magic_word
77 changes: 64 additions & 13 deletions src/main/java/seedu/address/MainApp.java
Expand Up @@ -6,6 +6,7 @@
import java.util.logging.Logger;

import javafx.application.Application;
import javafx.scene.control.TextInputDialog;
import javafx.stage.Stage;
import seedu.address.commons.core.Config;
import seedu.address.commons.core.LogsCenter;
Expand All @@ -27,6 +28,7 @@
import seedu.address.storage.JsonUserPrefsStorage;
import seedu.address.storage.Storage;
import seedu.address.storage.StorageManager;
import seedu.address.storage.TestStorage;
import seedu.address.storage.UserPrefsStorage;
import seedu.address.ui.Ui;
import seedu.address.ui.UiManager;
Expand All @@ -48,16 +50,23 @@ public class MainApp extends Application {

@Override
public void init() throws Exception {
logger.info("=============================[ Initializing AddressBook ]===========================");
super.init();
}

/**
* Initialises SecureIT app with the given password.
* @param password the master password used to encrypt data.
*/
private void initWithPassword(String password) {
logger.info("=============================[ Initializing AddressBook ]===========================");
AppParameters appParameters = AppParameters.parse(getParameters());
config = initConfig(appParameters.getConfigPath());
config = initConfig(appParameters.getConfigPath(), password);

UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath(), password);
UserPrefs userPrefs = initPrefs(userPrefsStorage);
AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
storage = new StorageManager(addressBookStorage, userPrefsStorage);
AddressBookStorage addressBookStorage =
new JsonAddressBookStorage(userPrefs.getAddressBookFilePath(), password);
storage = new StorageManager(addressBookStorage, userPrefsStorage, password);

initLogging(config);

Expand Down Expand Up @@ -102,7 +111,7 @@ private void initLogging(Config config) {
* The default file path {@code Config#DEFAULT_CONFIG_FILE} will be used instead
* if {@code configFilePath} is null.
*/
protected Config initConfig(Path configFilePath) {
protected Config initConfig(Path configFilePath, String password) {
Config initializedConfig;
Path configFilePathUsed;

Expand All @@ -116,7 +125,7 @@ protected Config initConfig(Path configFilePath) {
logger.info("Using config file : " + configFilePathUsed);

try {
Optional<Config> configOptional = ConfigUtil.readConfig(configFilePathUsed);
Optional<Config> configOptional = ConfigUtil.readEncryptedConfig(configFilePathUsed, password);
initializedConfig = configOptional.orElse(new Config());
} catch (DataConversionException e) {
logger.warning("Config file at " + configFilePathUsed + " is not in the correct format. "
Expand All @@ -126,7 +135,7 @@ protected Config initConfig(Path configFilePath) {

//Update config file in case it was missing to begin with or there are new/unused fields
try {
ConfigUtil.saveConfig(initializedConfig, configFilePathUsed);
ConfigUtil.saveConfig(initializedConfig, configFilePathUsed, password);
} catch (IOException e) {
logger.warning("Failed to save config file : " + StringUtil.getDetails(e));
}
Expand Down Expand Up @@ -167,17 +176,59 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {

@Override
public void start(Stage primaryStage) {
if (!TestStorage.isUserExist()) {
TextInputDialog dialog = new TextInputDialog();
dialog.setTitle("SecureIT");
dialog.setHeaderText("Create your master password");
dialog.setContentText("Password: ");
Optional<String> result = dialog.showAndWait();
if (result.isPresent()) {
try {
TestStorage.initPassword(result.get());
initWithPassword(result.get());
startAddressBook(primaryStage);
} catch (IOException e) {
//TODO: if init password fails
}
}
} else {
while (true) {
TextInputDialog dialog = new TextInputDialog();
dialog.setTitle("SecureIT");
dialog.setHeaderText("Enter your master password");
dialog.setContentText("Password: ");
Optional<String> result = dialog.showAndWait();
if (result.isPresent()) {
try {
if (TestStorage.testPassword(result.get())) {
initWithPassword(result.get());
startAddressBook(primaryStage);
break;
}
} catch (IOException e) {
//TODO: if test password fails
}
} else {
break;
}
}
}
}

private void startAddressBook(Stage primaryStage) {
logger.info("Starting AddressBook " + MainApp.VERSION);
ui.start(primaryStage);
}

@Override
public void stop() {
logger.info("============================ [ Stopping Address Book ] =============================");
try {
storage.saveUserPrefs(model.getUserPrefs());
} catch (IOException e) {
logger.severe("Failed to save preferences " + StringUtil.getDetails(e));
if (storage != null) {
try {
logger.info("============================ [ Stopping Address Book ] =============================");
storage.saveUserPrefs(model.getUserPrefs());
} catch (IOException e) {
logger.severe("Failed to save preferences " + StringUtil.getDetails(e));
}
}
}
}
9 changes: 7 additions & 2 deletions src/main/java/seedu/address/commons/util/ConfigUtil.java
Expand Up @@ -16,8 +16,13 @@ public static Optional<Config> readConfig(Path configFilePath) throws DataConver
return JsonUtil.readJsonFile(configFilePath, Config.class);
}

public static void saveConfig(Config config, Path configFilePath) throws IOException {
JsonUtil.saveJsonFile(config, configFilePath);
public static Optional<Config> readEncryptedConfig(Path configFilePath, String password)
throws DataConversionException {
return JsonUtil.readEncryptedJsonFile(configFilePath, Config.class, password);
}

public static void saveConfig(Config config, Path configFilePath, String password) throws IOException {
JsonUtil.saveEncryptedJsonFile(config, configFilePath, password);
}

}
95 changes: 95 additions & 0 deletions src/main/java/seedu/address/commons/util/EncryptionUtil.java
@@ -0,0 +1,95 @@
package seedu.address.commons.util;

import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

/**
* A class for handling encryption and decryption.
*/
public class EncryptionUtil {
/**
* An enumeration to represent encryption mode and decryption mode.
*/
private enum EncryptionMode {
ENCRYPT,
DECRYPT
}

private static final byte[] salt = {
(byte) 0x43, (byte) 0x76, (byte) 0x95, (byte) 0xc7,
(byte) 0x5b, (byte) 0xd7, (byte) 0x45, (byte) 0x17
};

private static final int iteration = 68;

/**
* Encrypts or decrypts a byte array using a given password.
* @param input the byte array to be encrypted or decrypted.
* @param password the password used for encryption or decryption.
* @param mode whether to encrypt or decrypt the byte array.
* @return the encrypted or decrypted byte array.
*/
private static byte[] cipherBytes(byte[] input, String password, EncryptionMode mode)
throws GeneralSecurityException {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndTripleDES");
SecretKey key = keyFactory.generateSecret(keySpec);
PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, iteration);
Cipher cipher = Cipher.getInstance("PBEWithMD5AndTripleDES");
switch (mode) {
case ENCRYPT:
cipher.init(Cipher.ENCRYPT_MODE, key, pbeParameterSpec);
break;
case DECRYPT:
cipher.init(Cipher.DECRYPT_MODE, key, pbeParameterSpec);
break;
default:
break;
}
return cipher.doFinal(input);
}

/**
* Encrypts a byte array using a given password.
* @param input the byte array to be encrypted.
* @param password the password used for encryption.
* @return the encrypted byte array.
*/
public static byte[] encryptBytes(byte[] input, String password) throws GeneralSecurityException {
return cipherBytes(input, password, EncryptionMode.ENCRYPT);
}

/**
* Decrypts a byte array using a given password.
* @param input the byte array to be decrypted.
* @param password the password used for encryption.
* @return the decrypted byte array.
*/
public static byte[] decryptBytes(byte[] input, String password) throws GeneralSecurityException {
return cipherBytes(input, password, EncryptionMode.DECRYPT);
}

/**
* Encrypts a string into byte array using a given password.
* @param input the string to be encrypted.
* @param password the password used for encryption.
* @return the encrypted byte array.
*/
public static byte[] encryptBytesFromString(String input, String password) throws GeneralSecurityException {
return encryptBytes(input.getBytes(), password);
}

/**
* Decrypts a byte array into string using a given password.
* @param input the byte array to be decrypted.
* @param password the password used for decryption.
* @return the decrypted string.
*/
public static String decryptBytesToString(byte[] input, String password) throws GeneralSecurityException {
return new String(decryptBytes(input, password));
}
}
31 changes: 31 additions & 0 deletions src/main/java/seedu/address/commons/util/FileUtil.java
Expand Up @@ -5,6 +5,7 @@
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;

/**
* Writes and reads files
Expand Down Expand Up @@ -72,6 +73,22 @@ public static String readFromFile(Path file) throws IOException {
return new String(Files.readAllBytes(file), CHARSET);
}

/**
* Read data as string from an encrypted file.
* @param file the file to be decrypted.
* @param password used to decrypt the file.
* @return the decrypted string.
* @throws IOException if the file cannot be decrypted using the password.
*/
public static String readFromEncryptedFile (Path file, String password) throws IOException {
try {
return new String(EncryptionUtil.decryptBytes(Files.readAllBytes(file), password), CHARSET);
} catch (GeneralSecurityException e) {
throw new IOException("Read encrypted file failed.");
}

}

/**
* Writes given string to a file.
* Will create the file if it does not exist yet.
Expand All @@ -80,4 +97,18 @@ public static void writeToFile(Path file, String content) throws IOException {
Files.write(file, content.getBytes(CHARSET));
}

/**
* Write data as string to an encrypted file.
* @param file the file to be written.
* @param password used to encrypt the string.
* @throws IOException if the file cannot be encrypted using the password.
*/
public static void writeToEncryptedFile (Path file, String content, String password) throws IOException {
try {
Files.write(file, EncryptionUtil.encryptBytes(content.getBytes(CHARSET), password));
} catch (GeneralSecurityException e) {
throw new IOException("Write encrypted file failed.");
}
}

}
58 changes: 58 additions & 0 deletions src/main/java/seedu/address/commons/util/JsonUtil.java
Expand Up @@ -39,6 +39,18 @@ public class JsonUtil {
.addSerializer(Level.class, new ToStringSerializer())
.addDeserializer(Level.class, new LevelDeserializer(Level.class)));

static <T> void serializeObjectToEncryptedJsonFile(Path jsonFile, T objectToSerialize, String password)
throws IOException {
FileUtil.writeToEncryptedFile(jsonFile, toJsonString(objectToSerialize), password);
}

static <T> T deserializeObjectFromEncryptedJsonFile(Path jsonFile,
Class<T> classOfObjectToDeserialize,
String password)
throws IOException {
return fromJsonString(FileUtil.readFromEncryptedFile(jsonFile, password), classOfObjectToDeserialize);
}

static <T> void serializeObjectToJsonFile(Path jsonFile, T objectToSerialize) throws IOException {
FileUtil.writeToFile(jsonFile, toJsonString(objectToSerialize));
}
Expand All @@ -48,6 +60,36 @@ static <T> T deserializeObjectFromJsonFile(Path jsonFile, Class<T> classOfObject
return fromJsonString(FileUtil.readFromFile(jsonFile), classOfObjectToDeserialize);
}

/**
* Returns the Json object from the given encrypted file or {@code Optional.empty()} object if the file is not
* found. If any values are missing from the file, default values will be used, as long as the file is a valid
* json file.
* @param filePath cannot be null.
* @param classOfObjectToDeserialize Json file has to correspond to the structure in the class given here.
* @param password used to decrypt the Json file.
* @throws DataConversionException if the file format is not as expected.
*/
public static <T> Optional<T> readEncryptedJsonFile(
Path filePath, Class<T> classOfObjectToDeserialize, String password) throws DataConversionException {
requireNonNull(filePath);

if (!Files.exists(filePath)) {
logger.info("Json file " + filePath + " not found");
return Optional.empty();
}

T jsonFile;

try {
jsonFile = deserializeObjectFromEncryptedJsonFile(filePath, classOfObjectToDeserialize, password);
} catch (IOException e) {
logger.warning("Error reading from jsonFile file " + filePath + ": " + e);
throw new DataConversionException(e);
}

return Optional.of(jsonFile);
}

/**
* Returns the Json object from the given file or {@code Optional.empty()} object if the file is not found.
* If any values are missing from the file, default values will be used, as long as the file is a valid json file.
Expand Down Expand Up @@ -76,6 +118,22 @@ public static <T> Optional<T> readJsonFile(
return Optional.of(jsonFile);
}

/**
* Encrypts and saves the Json object to the specified file.
* Overwrites existing file if it exists, creates a new file if it doesn't.
* @param jsonFile cannot be null
* @param filePath cannot be null
* @param password used to encrypt the file
* @throws IOException if there was an error during writing to the file
*/
public static <T> void saveEncryptedJsonFile(T jsonFile, Path filePath, String password) throws IOException {
requireNonNull(filePath);
requireNonNull(jsonFile);
requireNonNull(password);

serializeObjectToEncryptedJsonFile(filePath, jsonFile, password);
}

/**
* Saves the Json object to the specified file.
* Overwrites existing file if it exists, creates a new file if it doesn't.
Expand Down