Backup encryption feature

- Add encryption option when exporting and importing backup
- Add wipe data option when importing backup
This commit is contained in:
Tanguy Herbron 2018-07-18 23:54:04 +02:00
parent f8bb7b0487
commit 47c4fb0daf
10 changed files with 176 additions and 49 deletions

View File

@ -28,6 +28,7 @@ import com.herbron.moodl.Activities.HomeActivityFragments.Summary;
import com.herbron.moodl.Activities.HomeActivityFragments.Watchlist; import com.herbron.moodl.Activities.HomeActivityFragments.Watchlist;
import com.herbron.moodl.BalanceSwitchManagerInterface; import com.herbron.moodl.BalanceSwitchManagerInterface;
import com.herbron.moodl.BalanceUpdateInterface; import com.herbron.moodl.BalanceUpdateInterface;
import com.herbron.moodl.DataManagers.DatabaseManager;
import com.herbron.moodl.DataManagers.PreferencesManager; import com.herbron.moodl.DataManagers.PreferencesManager;
import com.herbron.moodl.PlaceholderManager; import com.herbron.moodl.PlaceholderManager;
import com.herbron.moodl.R; import com.herbron.moodl.R;
@ -59,7 +60,6 @@ public class HomeActivity extends AppCompatActivity implements BalanceUpdateInte
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
/**Interface setup**/ /**Interface setup**/
Window w = getWindow();
setContentView(R.layout.activity_currency_summary); setContentView(R.layout.activity_currency_summary);

View File

@ -70,7 +70,7 @@ public class Summary extends Fragment implements BalanceSwitchManagerInterface,
private LinearLayout currencyLayout; private LinearLayout currencyLayout;
private PreferencesManager preferencesManager; private PreferencesManager preferencesManager;
private com.herbron.moodl.DataManagers.BalanceManager balanceManager; private BalanceManager balanceManager;
private SwipeRefreshLayout refreshLayout; private SwipeRefreshLayout refreshLayout;
private Dialog loadingDialog; private Dialog loadingDialog;
private String defaultCurrency; private String defaultCurrency;

View File

@ -46,6 +46,10 @@ import com.herbron.moodl.FingerprintToolkit.FingerprintHandler;
import com.herbron.moodl.MoodlBox; import com.herbron.moodl.MoodlBox;
import com.herbron.moodl.R; import com.herbron.moodl.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
@ -468,21 +472,34 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
checkPermissions(); checkPermissions();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault()); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault());
Date currentDate = new Date(); Date currentDate = new Date();
String fileName = getString(R.string.app_name) + "_" + formatter.format(currentDate) + ".backup"; String fileName = "Bakup_" + formatter.format(currentDate) + ".moodl";
DatabaseManager databaseManager = new DatabaseManager(getContext()); DatabaseManager databaseManager = new DatabaseManager(getContext());
if(enterPasswordCheckbox.isChecked())
{
DataCrypter.updateKey(textInputLayoutPassword.getEditText().getText().toString());
}
File backupFile = new File(textViewFilePath.getText() + "/" + fileName); File backupFile = new File(textViewFilePath.getText() + "/" + fileName);
try (PrintWriter printWriter = new PrintWriter(new FileWriter(backupFile, true))) { try (PrintWriter printWriter = new PrintWriter(new FileWriter(backupFile, true))) {
if(enterPasswordCheckbox.isChecked()) try {
{ JSONObject backupJson = new JSONObject();
DataCrypter.updateKey(textInputLayoutPassword.getEditText().getText().toString());
printWriter.write(DataCrypter.encrypt(getActivity(), databaseManager.getBackupData())); if(enterPasswordCheckbox.isChecked())
} {
else backupJson.put("encodeChecker", DataCrypter.encrypt(getContext(), "NaukVerification"));
{ }
printWriter.write(databaseManager.getBackupData()); else
{
backupJson.put("encodeChecker", "NaukVerification");
}
backupJson.put("transactions", databaseManager.getBackupData(getContext(),enterPasswordCheckbox.isChecked()));
printWriter.write(backupJson.toString());
} catch (JSONException e) {
Log.d("moodl", "Error while creating backup json " + e.getMessage());
} }
printWriter.close(); printWriter.close();
@ -542,6 +559,10 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
} }
}); });
final CheckBox enterPasswordCheckbox = dialogView.findViewById(R.id.checkboxEnterPassword);
final CheckBox wipeCheckbox = dialogView.findViewById(R.id.checkboxWipeData);
final TextInputLayout textInputLayoutPassword = dialogView.findViewById(R.id.textInputLayoutPassword);
dialogBuilder.setTitle(getString(R.string.restoreBackup)); dialogBuilder.setTitle(getString(R.string.restoreBackup));
dialogBuilder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() { dialogBuilder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() {
@Override @Override
@ -551,6 +572,16 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
DatabaseManager databaseManager = new DatabaseManager(context); DatabaseManager databaseManager = new DatabaseManager(context);
if(enterPasswordCheckbox.isChecked())
{
DataCrypter.updateKey(textInputLayoutPassword.getEditText().getText().toString());
}
if(wipeCheckbox.isChecked())
{
databaseManager.wipeTransactions();
}
File backupFile = new File(textViewFilePath.getText().toString()); File backupFile = new File(textViewFilePath.getText().toString());
try { try {
@ -564,13 +595,37 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
completeFile += str; completeFile += str;
} }
String[] results = completeFile.split(Pattern.quote("]")); try {
JSONObject backupJson = new JSONObject(completeFile);
String checker;
for(int i = 0; i < results.length; i++) if(enterPasswordCheckbox.isChecked())
{ {
String[] columnValues = results[i].split(Pattern.quote(";@")); checker = DataCrypter.decrypt(getContext(), backupJson.getString("encodeChecker"));
}
else
{
checker = backupJson.getString("encodeChecker");
}
databaseManager.addRowTransaction(columnValues); if(checker.equals("NaukVerification"))
{
JSONArray transactionsArray = backupJson.getJSONArray("transactions");
for(int i = 0; i < transactionsArray.length(); i++)
{
JSONObject transactionObject = transactionsArray.getJSONObject(i);
databaseManager.addRowTransaction(transactionObject, getContext(), enterPasswordCheckbox.isChecked());
}
}
else
{
textInputLayoutPassword.setError("Wrong password");
}
} catch (JSONException e) {
Log.d("moodl", "Error while creating backup json " + e);
} }
} catch (IOException e) { } catch (IOException e) {

View File

@ -56,7 +56,6 @@ public class CurrencyDetailsList {
if (response.length() > 0) { if (response.length() > 0) {
processDetailResult(response, callBack); processDetailResult(response, callBack);
upToDate = true;
} }
} }
}, },
@ -109,6 +108,8 @@ public class CurrencyDetailsList {
sortDetails(); sortDetails();
upToDate = true;
callBack.onSuccess(); callBack.onSuccess();
} }

View File

@ -4,10 +4,10 @@ import android.content.Context;
import android.util.Log; import android.util.Log;
import com.herbron.moodl.R; import com.herbron.moodl.R;
import android.util.Base64;
import org.apache.commons.codec.binary.Base64;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.Key; import java.security.Key;
@ -20,19 +20,19 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
public class DataCrypter { public class DataCrypter {
private static Key aesKey; private static Key aesKey;
public static void updateKey(String key) public static void updateKey(String key)
{ {
for(int i = 0; key.getBytes().length < 32; i++)
{
key += "0";
}
try { try {
aesKey = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); byte[] keyByte = key.getBytes("UTF-8");
byte[] finalKey = new byte[32];
System.arraycopy(keyByte, 0, finalKey, 0, keyByte.length);
aesKey = new SecretKeySpec(finalKey, "AES");
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
Log.d("moodl", "Error while creating encryption key " + e.getMessage()); Log.d("moodl", "Error while creating encryption key " + e.getMessage());
} }
@ -49,9 +49,10 @@ public class DataCrypter {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivParameterSpec); cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivParameterSpec);
byte[] encryptedBytes = cipher.doFinal(data.getBytes()); byte[] encryptedBytes = cipher.doFinal(data.getBytes("UTF-8"));
encryptedData = Base64.encodeToString(encryptedBytes, Base64.DEFAULT);
encryptedData = Base64.encodeBase64String(encryptedBytes);
} catch (NoSuchPaddingException | NoSuchAlgorithmException } catch (NoSuchPaddingException | NoSuchAlgorithmException
| InvalidKeyException | BadPaddingException | InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | UnsupportedEncodingException | IllegalBlockSizeException | UnsupportedEncodingException
@ -63,4 +64,26 @@ public class DataCrypter {
return encryptedData; return encryptedData;
} }
public static String decrypt(Context context, String data)
{
String decryptedData = null;
try {
IvParameterSpec ivParameterSpec = new IvParameterSpec(context.getString(R.string.ivKey).getBytes("UTF-8"));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivParameterSpec);
byte[] dataBytes = Base64.decode(data, Base64.DEFAULT);
decryptedData = new String(dataBytes, StandardCharsets.UTF_8);
} catch(NoSuchPaddingException | NoSuchAlgorithmException
| InvalidKeyException | UnsupportedEncodingException
| InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return decryptedData;
}
} }

View File

@ -5,10 +5,15 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.herbron.moodl.DataManagers.CurrencyData.Currency; import com.herbron.moodl.DataManagers.CurrencyData.Currency;
import com.herbron.moodl.DataManagers.CurrencyData.Transaction; import com.herbron.moodl.DataManagers.CurrencyData.Transaction;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -152,36 +157,82 @@ public class DatabaseManager extends SQLiteOpenHelper{
db.close(); db.close();
} }
public String getBackupData() public JSONArray getBackupData(Context context, boolean encryptData)
{ {
String selectQuerry = "SELECT * FROM " + TABLE_MANUAL_CURRENCIES; String selectQuerry = "SELECT * FROM " + TABLE_MANUAL_CURRENCIES;
SQLiteDatabase db = this.getWritableDatabase(); SQLiteDatabase db = this.getWritableDatabase();
Cursor result = db.rawQuery(selectQuerry, null); Cursor result = db.rawQuery(selectQuerry, null);
String backupData = ""; JSONArray transactionsArray = new JSONArray();
while(result.moveToNext()) while(result.moveToNext())
{ {
JSONObject transactionJson = new JSONObject();
for(int i = 0; i < result.getColumnCount(); i++) for(int i = 0; i < result.getColumnCount(); i++)
{ {
backupData += result.getString(i) + ";@"; try {
if(result.getString(i) != null)
{
if(encryptData)
{
transactionJson.put(result.getColumnName(i), DataCrypter.encrypt(context, result.getString(i)));
}
else
{
transactionJson.put(result.getColumnName(i), result.getString(i));
}
}
else
{
transactionJson.put(result.getColumnName(i), "");
}
} catch (JSONException e) {
Log.d("moodl", "Error while creating a json transaction");
}
} }
backupData += "\n"; transactionsArray.put(transactionJson);
} }
return backupData; return transactionsArray;
} }
public void addRowTransaction(String[] rowValues) public void wipeTransactions()
{
SQLiteDatabase db = this.getWritableDatabase();
db.execSQL("DELETE FROM "+ TABLE_MANUAL_CURRENCIES);
}
public void addRowTransaction(JSONObject rawValues, Context context, boolean decrypt)
{ {
SQLiteDatabase db = this.getWritableDatabase(); SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(KEY_CURRENCY_SYMBOL, rowValues[1]); try {
values.put(KEY_CURRENCY_BALANCE, rowValues[3]); if(decrypt)
values.put(KEY_CURRENCY_DATE, rowValues[4]); {
values.put(KEY_CURRENCY_PURCHASED_PRICE, rowValues[5]); values.put(KEY_CURRENCY_SYMBOL, DataCrypter.decrypt(context, rawValues.getString(KEY_CURRENCY_SYMBOL)));
values.put(KEY_CURRENCY_NAME, DataCrypter.decrypt(context, rawValues.getString(KEY_CURRENCY_NAME)));
values.put(KEY_CURRENCY_BALANCE, DataCrypter.decrypt(context, rawValues.getString(KEY_CURRENCY_BALANCE)));
values.put(KEY_CURRENCY_DATE, DataCrypter.decrypt(context, rawValues.getString(KEY_CURRENCY_DATE)));
values.put(KEY_CURRENCY_PURCHASED_PRICE, DataCrypter.decrypt(context, rawValues.getString(KEY_CURRENCY_PURCHASED_PRICE)));
values.put(KEY_CURRENCY_IS_MINED, DataCrypter.decrypt(context, rawValues.getString(KEY_CURRENCY_IS_MINED)));
values.put(KEY_CURRENCY_FEES, DataCrypter.decrypt(context, rawValues.getString(KEY_CURRENCY_FEES)));
}
else
{
values.put(KEY_CURRENCY_SYMBOL, rawValues.getString(KEY_CURRENCY_SYMBOL));
values.put(KEY_CURRENCY_NAME, rawValues.getString(KEY_CURRENCY_NAME));
values.put(KEY_CURRENCY_BALANCE, rawValues.getString(KEY_CURRENCY_BALANCE));
values.put(KEY_CURRENCY_DATE, rawValues.getString(KEY_CURRENCY_DATE));
values.put(KEY_CURRENCY_PURCHASED_PRICE, rawValues.getString(KEY_CURRENCY_PURCHASED_PRICE));
values.put(KEY_CURRENCY_IS_MINED, rawValues.getString(KEY_CURRENCY_IS_MINED));
values.put(KEY_CURRENCY_FEES, rawValues.getString(KEY_CURRENCY_FEES));
}
} catch (JSONException e) {
Log.d("moodl", "Error while inserting transaction");
}
db.insert(TABLE_MANUAL_CURRENCIES, null, values); db.insert(TABLE_MANUAL_CURRENCIES, null, values);
db.close(); db.close();

View File

@ -17,10 +17,10 @@
android:background="@drawable/background_filepath"/> android:background="@drawable/background_filepath"/>
<CheckBox <CheckBox
android:id="@+id/checkboxConserveData" android:id="@+id/checkboxWipeData"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/conserve_data" android:text="@string/wipe_data"
android:enabled="false" /> android:enabled="false" />
<CheckBox <CheckBox
@ -41,8 +41,7 @@
android:id="@+id/checkboxEnterPassword" android:id="@+id/checkboxEnterPassword"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/enter_password" android:text="@string/enter_password"/>
android:enabled="false"/>
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
android:id="@+id/textInputLayoutPassword" android:id="@+id/textInputLayoutPassword"

View File

@ -17,11 +17,10 @@
android:background="@drawable/background_filepath"/> android:background="@drawable/background_filepath"/>
<CheckBox <CheckBox
android:id="@+id/checkboxConserveData" android:id="@+id/checkboxWipeData"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/conserve_data" android:text="@string/wipe_data"/>
android:enabled="false"/>
<CheckBox <CheckBox
android:id="@+id/checkboxRestoreEntries" android:id="@+id/checkboxRestoreEntries"
@ -41,8 +40,7 @@
android:id="@+id/checkboxEnterPassword" android:id="@+id/checkboxEnterPassword"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/enter_password" android:text="@string/enter_password"/>
android:enabled="false"/>
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
android:id="@+id/textInputLayoutPassword" android:id="@+id/textInputLayoutPassword"

View File

@ -87,10 +87,10 @@
<string name="pref_title_export">Exporter les entrées manuelles</string> <string name="pref_title_export">Exporter les entrées manuelles</string>
<string name="pref_title_import">Importer de nouvelles entrées</string> <string name="pref_title_import">Importer de nouvelles entrées</string>
<string name="pref_title_category_synchronization">Synchronisation</string> <string name="pref_title_category_synchronization">Synchronisation</string>
<string name="conserve_data">Conserver les données</string> <string name="wipe_data">Supprimer les données actuelles</string>
<string name="restore_manual_entries">Restaurer les transactions manuelles</string> <string name="restore_manual_entries">Restaurer les transactions manuelles</string>
<string name="restore_keys">Restaurer les clefs d\'API</string> <string name="restore_keys">Restaurer les clefs d\'API</string>
<string name="enter_password">Entrerr un mot de passe</string> <string name="enter_password">Entrer un mot de passe</string>
<string name="password">Mot de passe</string> <string name="password">Mot de passe</string>
<string name="title_activity_exchange_settings">Réglages</string> <string name="title_activity_exchange_settings">Réglages</string>
<string name="save">Sauvegarder</string> <string name="save">Sauvegarder</string>

View File

@ -203,7 +203,7 @@
<string name="field_empty">This field cannot be blank</string> <string name="field_empty">This field cannot be blank</string>
<string name="field_nan">This field must be a number</string> <string name="field_nan">This field must be a number</string>
<string name="field_negative">This field must be positive</string> <string name="field_negative">This field must be positive</string>
<string name="conserve_data">Conserve current data</string> <string name="wipe_data">Wipe current data</string>
<string name="restore_manual_entries">Restore manual entries</string> <string name="restore_manual_entries">Restore manual entries</string>
<string name="restore_keys">Restore API keys</string> <string name="restore_keys">Restore API keys</string>
<string name="enter_password">Enter password</string> <string name="enter_password">Enter password</string>