Improve security of the password-protected backups

This adds a new encryption for the password-protected backups that uses a proper key derivation function (PBKDF2).
Import of the old backups is still supported, new backups are forced to use the new encryption.
This commit is contained in:
Jakob Nixdorf 2019-09-16 12:25:43 +02:00
parent 2f77fe4644
commit d96b0373d9
No known key found for this signature in database
GPG key ID: BE99BF86574A7DBC
5 changed files with 108 additions and 29 deletions

View file

@ -36,7 +36,6 @@ import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewStub;
import android.widget.LinearLayout;
@ -60,8 +59,10 @@ import org.shadowice.flocke.andotp.Utilities.Tools;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import javax.crypto.SecretKey;
@ -120,6 +121,7 @@ public class BackupActivity extends BaseActivity {
TextView cryptSetup = v.findViewById(R.id.msg_crypt_setup);
LinearLayout backupCrypt = v.findViewById(R.id.button_backup_crypt);
LinearLayout restoreCrypt = v.findViewById(R.id.button_restore_crypt);
LinearLayout restoreCryptOld = v.findViewById(R.id.button_restore_crypt_old);
if (settings.getBackupPasswordEnc().isEmpty()) {
cryptSetup.setVisibility(View.VISIBLE);
@ -141,6 +143,13 @@ public class BackupActivity extends BaseActivity {
}
});
restoreCryptOld.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
openFileWithPermissions(Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT_OLD, Constants.PERMISSIONS_BACKUP_READ_IMPORT_CRYPT_OLD);
}
});
// OpenPGP
String PGPProvider = settings.getOpenPGPProvider();
@ -231,6 +240,12 @@ public class BackupActivity extends BaseActivity {
} else {
Toast.makeText(this, R.string.backup_toast_storage_permissions, Toast.LENGTH_LONG).show();
}
} else if (requestCode == Constants.PERMISSIONS_BACKUP_READ_IMPORT_CRYPT_OLD) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showOpenFileSelector(Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT_OLD);
} else {
Toast.makeText(this, R.string.backup_toast_storage_permissions, Toast.LENGTH_LONG).show();
}
} else if (requestCode == Constants.PERMISSIONS_BACKUP_WRITE_EXPORT_CRYPT) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showSaveFileSelector(Constants.BACKUP_MIMETYPE_CRYPT, Constants.BackupType.ENCRYPTED, Constants.INTENT_BACKUP_SAVE_DOCUMENT_CRYPT);
@ -269,7 +284,11 @@ public class BackupActivity extends BaseActivity {
}
} else if (requestCode == Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT && resultCode == RESULT_OK) {
if (intent != null) {
doRestoreCrypt(intent.getData());
doRestoreCrypt(intent.getData(), false);
}
} else if (requestCode == Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT_OLD && resultCode == RESULT_OK) {
if (intent != null) {
doRestoreCrypt(intent.getData(), true);
}
} else if (requestCode == Constants.INTENT_BACKUP_SAVE_DOCUMENT_CRYPT && resultCode == RESULT_OK) {
if (intent != null) {
@ -300,7 +319,9 @@ public class BackupActivity extends BaseActivity {
if (intentId == Constants.INTENT_BACKUP_OPEN_DOCUMENT_PLAIN)
doRestorePlain(Tools.buildUri(settings.getBackupDir(), Constants.BACKUP_FILENAME_PLAIN));
else if (intentId == Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT)
doRestoreCrypt(Tools.buildUri(settings.getBackupDir(), Constants.BACKUP_FILENAME_CRYPT));
doRestoreCrypt(Tools.buildUri(settings.getBackupDir(), Constants.BACKUP_FILENAME_CRYPT), false);
else if (intentId == Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT_OLD)
doRestoreCrypt(Tools.buildUri(settings.getBackupDir(), Constants.BACKUP_FILENAME_CRYPT), true);
else if (intentId == Constants.INTENT_BACKUP_OPEN_DOCUMENT_PGP)
restoreEncryptedWithPGP(Tools.buildUri(settings.getBackupDir(), Constants.BACKUP_FILENAME_PGP), null);
}
@ -415,34 +436,47 @@ public class BackupActivity extends BaseActivity {
/* Encrypted backup functions */
private void doRestoreCrypt(final Uri uri) {
private void doRestoreCrypt(final Uri uri, final boolean old_format) {
String password = settings.getBackupPasswordEnc();
if (password.isEmpty()) {
PasswordEntryDialog pwDialog = new PasswordEntryDialog(this, PasswordEntryDialog.Mode.ENTER, new PasswordEntryDialog.PasswordEnteredCallback() {
@Override
public void onPasswordEntered(String newPassword) {
doRestoreCryptWithPassword(uri, newPassword);
doRestoreCryptWithPassword(uri, newPassword, old_format);
}
});
pwDialog.show();
} else {
doRestoreCryptWithPassword(uri, password);
doRestoreCryptWithPassword(uri, password, old_format);
}
}
private void doRestoreCryptWithPassword(Uri uri, String password) {
private void doRestoreCryptWithPassword(Uri uri, String password, boolean old_format) {
if (Tools.isExternalStorageReadable()) {
boolean success = true;
String decryptedString = "";
try {
byte[] encrypted = FileHelper.readFileToBytes(this, uri);
byte[] data = FileHelper.readFileToBytes(this, uri);
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password);
byte[] decrypted = EncryptionHelper.decrypt(key, encrypted);
if (old_format) {
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password);
byte[] decrypted = EncryptionHelper.decrypt(key, data);
decryptedString = new String(decrypted, StandardCharsets.UTF_8);
decryptedString = new String(decrypted, StandardCharsets.UTF_8);
} else {
byte[] iterBytes = Arrays.copyOfRange(data, 0, Constants.INT_LENGTH);
byte[] salt = Arrays.copyOfRange(data, Constants.INT_LENGTH, Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH);
byte[] encrypted = Arrays.copyOfRange(data, Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH, data.length);
int iter = ByteBuffer.wrap(iterBytes).getInt();
SecretKey key = EncryptionHelper.generateSymmetricKeyPBKDF2(password, iter, salt);
byte[] decrypted = EncryptionHelper.decrypt(key, encrypted);
decryptedString = new String(decrypted, StandardCharsets.UTF_8);
}
} catch (Exception e) {
success = false;
e.printStackTrace();
@ -482,10 +516,20 @@ public class BackupActivity extends BaseActivity {
boolean success = true;
try {
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password);
int iter = EncryptionHelper.generateRandomIterations();
byte[] salt = EncryptionHelper.generateRandom(Constants.ENCRYPTION_IV_LENGTH);
SecretKey key = EncryptionHelper.generateSymmetricKeyPBKDF2(password, iter, salt);
byte[] encrypted = EncryptionHelper.encrypt(key, plain.getBytes(StandardCharsets.UTF_8));
FileHelper.writeBytesToFile(this, uri, encrypted);
byte[] iterBytes = ByteBuffer.allocate(Constants.INT_LENGTH).putInt(iter).array();
byte[] data = new byte[Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH + encrypted.length];
System.arraycopy(iterBytes, 0, data, 0, Constants.INT_LENGTH);
System.arraycopy(salt, 0, data, Constants.INT_LENGTH, Constants.ENCRYPTION_IV_LENGTH);
System.arraycopy(encrypted, 0, data, Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH, encrypted.length);
FileHelper.writeBytesToFile(this, uri, data);
} catch (Exception e) {
e.printStackTrace();
success = false;
@ -510,7 +554,6 @@ public class BackupActivity extends BaseActivity {
decryptIntent = new Intent(OpenPgpApi.ACTION_DECRYPT_VERIFY);
String input = FileHelper.readFileToString(this, uri);
Log.d("OpenPGP", input);
InputStream is = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));
ByteArrayOutputStream os = new ByteArrayOutputStream();

View file

@ -58,24 +58,26 @@ public class Constants {
public final static int INTENT_MAIN_BACKUP = 102;
public final static int INTENT_MAIN_INTRO = 103;
public final static int INTENT_BACKUP_OPEN_DOCUMENT_PLAIN = 200;
public final static int INTENT_BACKUP_SAVE_DOCUMENT_PLAIN = 201;
public final static int INTENT_BACKUP_OPEN_DOCUMENT_CRYPT = 202;
public final static int INTENT_BACKUP_SAVE_DOCUMENT_CRYPT = 203;
public final static int INTENT_BACKUP_OPEN_DOCUMENT_PGP = 204;
public final static int INTENT_BACKUP_SAVE_DOCUMENT_PGP = 205;
public final static int INTENT_BACKUP_ENCRYPT_PGP = 206;
public final static int INTENT_BACKUP_DECRYPT_PGP = 207;
public final static int INTENT_BACKUP_OPEN_DOCUMENT_PLAIN = 200;
public final static int INTENT_BACKUP_SAVE_DOCUMENT_PLAIN = 201;
public final static int INTENT_BACKUP_OPEN_DOCUMENT_CRYPT = 202;
public final static int INTENT_BACKUP_SAVE_DOCUMENT_CRYPT = 203;
public final static int INTENT_BACKUP_OPEN_DOCUMENT_PGP = 204;
public final static int INTENT_BACKUP_SAVE_DOCUMENT_PGP = 205;
public final static int INTENT_BACKUP_ENCRYPT_PGP = 206;
public final static int INTENT_BACKUP_DECRYPT_PGP = 207;
public final static int INTENT_BACKUP_OPEN_DOCUMENT_CRYPT_OLD = 208;
public static final int INTENT_SETTINGS_AUTHENTICATE = 300;
// Permission requests (Format: A1x with A = parent Activity, x = number of the request)
public final static int PERMISSIONS_BACKUP_READ_IMPORT_PLAIN = 210;
public final static int PERMISSIONS_BACKUP_WRITE_EXPORT_PLAIN = 211;
public final static int PERMISSIONS_BACKUP_READ_IMPORT_CRYPT = 212;
public final static int PERMISSIONS_BACKUP_WRITE_EXPORT_CRYPT = 213;
public final static int PERMISSIONS_BACKUP_READ_IMPORT_PGP = 214;
public final static int PERMISSIONS_BACKUP_WRITE_EXPORT_PGP = 215;
public final static int PERMISSIONS_BACKUP_READ_IMPORT_PLAIN = 210;
public final static int PERMISSIONS_BACKUP_WRITE_EXPORT_PLAIN = 211;
public final static int PERMISSIONS_BACKUP_READ_IMPORT_CRYPT = 212;
public final static int PERMISSIONS_BACKUP_WRITE_EXPORT_CRYPT = 213;
public final static int PERMISSIONS_BACKUP_READ_IMPORT_PGP = 214;
public final static int PERMISSIONS_BACKUP_WRITE_EXPORT_PGP = 215;
public final static int PERMISSIONS_BACKUP_READ_IMPORT_CRYPT_OLD = 216;
// Intent extras
public final static String EXTRA_AUTH_PASSWORD_KEY = "password_key";
@ -92,7 +94,9 @@ public class Constants {
final static String ALGORITHM_ASYMMETRIC = "RSA/ECB/PKCS1Padding";
final static int ENCRYPTION_KEY_LENGTH = 16; // 128-bit encryption key (KeyStore-mode)
final static int ENCRYPTION_IV_LENGTH = 12;
public final static int ENCRYPTION_IV_LENGTH = 12;
public final static int INT_LENGTH = 4;
final static int PBKDF2_MIN_ITERATIONS = 1000;
final static int PBKDF2_MAX_ITERATIONS = 5000;

View file

@ -89,6 +89,14 @@ public class EncryptionHelper {
return new SecretKeySpec(data, 0, data.length, "AES");
}
public static SecretKey generateSymmetricKeyPBKDF2(String password, int iter, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iter, Constants.PBKDF2_LENGTH);
return secretKeyFactory.generateSecret(keySpec);
}
public static SecretKey generateSymmetricKeyFromPassword(String password)
throws NoSuchAlgorithmException {
MessageDigest sha = MessageDigest.getInstance("SHA-256");

View file

@ -129,6 +129,28 @@
</LinearLayout>
<LinearLayout
android:id="@+id/button_restore_crypt_old"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/activity_margin"
android:background="?android:attr/selectableItemBackground" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:text="@string/backup_title_import_crypt_old" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/backup_desc_import_crypt_old"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -11,6 +11,7 @@
<string name="backup_title_export_openpgp">Backup (OpenPGP)</string>
<string name="backup_title_import_plain">Restore (plain-text)</string>
<string name="backup_title_import_crypt">Restore (encrypted)</string>
<string name="backup_title_import_crypt_old">Restore (encrypted, old encryption)</string>
<string name="backup_title_import_openpgp">Restore (OpenPGP)</string>
<string name="backup_title_replace">Replace existing entries</string>
@ -20,6 +21,7 @@
<string name="backup_desc_export_openpgp">Backup all accounts in an OpenPGP-encrypted JSON file</string>
<string name="backup_desc_import_plain">Restore accounts from a plain-text JSON file</string>
<string name="backup_desc_import_crypt">Restore accounts from a password-protected JSON file</string>
<string name="backup_desc_import_crypt_old">Restore accounts from a password-protected JSON file using the old, insecure encryption</string>
<string name="backup_desc_import_openpgp">Restore accounts from an OpenPGP-encrypted JSON file</string>
<string name="backup_desc_crypt_setup">Failed to load the backup password from the <b>Settings</b>,