Store the backup password encrypted

Closes #49
This commit is contained in:
Jakob Nixdorf 2017-10-06 14:10:06 +02:00
parent de2485e56f
commit 2e09eb42ad
No known key found for this signature in database
GPG key ID: BE99BF86574A7DBC
8 changed files with 134 additions and 75 deletions

View file

@ -133,7 +133,7 @@ public class BackupActivity extends BaseActivity {
LinearLayout backupCrypt = v.findViewById(R.id.button_backup_crypt);
LinearLayout restoreCrypt = v.findViewById(R.id.button_restore_crypt);
if (settings.getBackupPassword().isEmpty()) {
if (settings.getBackupPasswordEnc().isEmpty()) {
cryptSetup.setVisibility(View.VISIBLE);
backupCrypt.setVisibility(View.GONE);
restoreCrypt.setVisibility(View.GONE);
@ -392,7 +392,7 @@ public class BackupActivity extends BaseActivity {
/* Encrypted backup functions */
private void doRestoreCrypt(Uri uri) {
String password = settings.getBackupPassword();
String password = settings.getBackupPasswordEnc();
if (! password.isEmpty()) {
if (StorageHelper.isExternalStorageReadable()) {
@ -428,7 +428,7 @@ public class BackupActivity extends BaseActivity {
}
private void doBackupCrypt(Uri uri) {
String password = settings.getBackupPassword();
String password = settings.getBackupPasswordEnc();
if (! password.isEmpty()) {
if (StorageHelper.isExternalStorageWritable()) {

View file

@ -33,19 +33,28 @@ import android.text.InputType;
import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Utilities.EncryptionHelper;
import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper;
public class PasswordPreference extends DialogPreference
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
public class PasswordEncryptedPreference extends DialogPreference
implements View.OnClickListener, TextWatcher {
public enum Mode {
PASSWORD, PIN
}
public static final String KEY_ALIAS = "password";
private KeyPair key;
private static final String DEFAULT_VALUE = "";
private Mode mode = Mode.PASSWORD;
@ -57,9 +66,15 @@ public class PasswordPreference extends DialogPreference
private String value = DEFAULT_VALUE;
public PasswordPreference(Context context, AttributeSet attrs) {
public PasswordEncryptedPreference(Context context, AttributeSet attrs) {
super(context, attrs);
try {
key = KeyStoreHelper.loadOrGenerateAsymmetricKeyPair(context, KEY_ALIAS);
} catch (Exception e) {
e.printStackTrace();
}
setDialogLayoutResource(R.layout.component_password);
}
@ -120,13 +135,31 @@ public class PasswordPreference extends DialogPreference
return a.getString(index);
}
private void encryptAndPersist(String value) {
try {
byte[] encBytes = EncryptionHelper.encrypt(key.getPublic(), value.getBytes(StandardCharsets.UTF_8));
persistString(Base64.encodeToString(encBytes, Base64.URL_SAFE));
} catch (Exception e) {
e.printStackTrace();
}
}
private void restoreAndDecrypt(String encValue) {
try {
byte[] encBytes = Base64.decode(encValue, Base64.URL_SAFE);
value = new String(EncryptionHelper.decrypt(key.getPrivate(), encBytes), StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
if (restorePersistedValue) {
value = getPersistedString(DEFAULT_VALUE);
restoreAndDecrypt(getPersistedString(DEFAULT_VALUE));
} else {
value = (String) defaultValue;
persistString(value);
encryptAndPersist(value);
}
}
@ -138,7 +171,7 @@ public class PasswordPreference extends DialogPreference
break;
case (R.id.btnSave):
value = passwordInput.getText().toString();
persistString(value);
encryptAndPersist(value);
getDialog().dismiss();
break;

View file

@ -47,7 +47,7 @@ public class DatabaseHelper {
byte[] data = jsonString.getBytes();
SecretKey key = KeyStoreHelper.loadOrGenerateWrappedKey(context, new File(context.getFilesDir() + "/" + KEY_FILE));
data = EncryptionHelper.encrypt(key,data);
data = EncryptionHelper.encrypt(key, data);
FileHelper.writeBytesToFile(new File(context.getFilesDir() + "/" + SETTINGS_FILE), data);

View file

@ -29,6 +29,8 @@ import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.Arrays;
@ -41,7 +43,9 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class EncryptionHelper {
private final static String ALGORITHM = "AES/GCM/NoPadding";
private final static String ALGORITHM_SYMMETRIC = "AES/GCM/NoPadding";
private final static String ALGORITHM_ASYMMETRIC = "RSA/ECB/PKCS1Padding";
private final static int IV_LENGTH = 12;
public static SecretKey generateSymmetricKeyFromPassword(String password)
@ -51,30 +55,14 @@ public class EncryptionHelper {
return new SecretKeySpec(sha.digest(password.getBytes(StandardCharsets.UTF_8)), "AES");
}
public static byte[] encrypt(SecretKey secretKey, IvParameterSpec iv, byte[] plainText)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
return cipher.doFinal(plainText);
}
public static byte[] decrypt(SecretKey secretKey, IvParameterSpec iv, byte[] cipherText)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
return cipher.doFinal(cipherText);
}
public static byte[] encrypt(SecretKey secretKey, byte[] plaintext)
throws NoSuchPaddingException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
final byte[] iv = new byte[IV_LENGTH];
new SecureRandom().nextBytes(iv);
byte[] cipherText = encrypt(secretKey, new IvParameterSpec(iv), plaintext);
Cipher cipher = Cipher.getInstance(ALGORITHM_SYMMETRIC);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] cipherText = cipher.doFinal(plaintext);
byte[] combined = new byte[iv.length + cipherText.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
@ -83,11 +71,30 @@ public class EncryptionHelper {
return combined;
}
public static byte[] encrypt(PublicKey publicKey, byte[] plaintext)
throws NoSuchPaddingException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
Cipher cipher = Cipher.getInstance(ALGORITHM_ASYMMETRIC);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(plaintext);
}
public static byte[] decrypt(SecretKey secretKey, byte[] cipherText)
throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
byte[] iv = Arrays.copyOfRange(cipherText, 0, IV_LENGTH);
byte[] cipher = Arrays.copyOfRange(cipherText, IV_LENGTH,cipherText.length);
byte[] encrypted = Arrays.copyOfRange(cipherText, IV_LENGTH, cipherText.length);
return decrypt(secretKey,new IvParameterSpec(iv), cipher );
Cipher cipher = Cipher.getInstance(ALGORITHM_SYMMETRIC);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
return cipher.doFinal(encrypted);
}
public static byte[] decrypt(PrivateKey privateKey, byte[] cipherText)
throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
Cipher cipher = Cipher.getInstance(ALGORITHM_ASYMMETRIC);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(cipherText);
}
}

View file

@ -32,7 +32,6 @@ import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
@ -41,7 +40,6 @@ import java.security.spec.AlgorithmParameterSpec;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal;
@ -49,13 +47,19 @@ import javax.security.auth.x500.X500Principal;
public class KeyStoreHelper {
private final static int KEY_LENGTH = 16;
private static AlgorithmParameterSpec generateAlgorithmSpec(Context context, String alias) {
public static KeyPair loadOrGenerateAsymmetricKeyPair(Context context, String alias)
throws GeneralSecurityException, IOException {
final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
if (! keyStore.containsAlias(alias)) {
final Calendar start = new GregorianCalendar();
final Calendar end = new GregorianCalendar();
end.add(Calendar.YEAR, 100);
AlgorithmParameterSpec spec;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
spec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setCertificateSubject(new X500Principal("CN=" + alias))
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setCertificateSerialNumber(BigInteger.ONE)
@ -63,7 +67,7 @@ public class KeyStoreHelper {
.setCertificateNotAfter(end.getTime())
.build();
} else {
return new KeyPairGeneratorSpec.Builder(context)
spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(alias)
.setSubject(new X500Principal("CN=" + alias))
.setSerialNumber(BigInteger.ONE)
@ -71,17 +75,10 @@ public class KeyStoreHelper {
.setEndDate(end.getTime())
.build();
}
}
public static KeyPair loadOrGenerateAsymmetricKeyPair(Context context, String alias)
throws GeneralSecurityException, IOException {
final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
if (! keyStore.containsAlias(alias)) {
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
gen.initialize(generateAlgorithmSpec(context, alias));
gen.initialize(spec);
gen.generateKeyPair();
}
@ -89,21 +86,6 @@ public class KeyStoreHelper {
return new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey());
}
public static Key loadOrGenerateSymmetricKey(Context context, String alias)
throws GeneralSecurityException, IOException {
final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
if (! keyStore.containsAlias(alias)) {
KeyGenerator gen = KeyGenerator.getInstance("AES", "AndroidKeyStore");
gen.init(generateAlgorithmSpec(context, alias));
gen.generateKey();
}
return keyStore.getKey(alias, null);
}
/**
* Load our symmetric secret key.
* The symmetric secret key is stored securely on disk by wrapping

View file

@ -25,14 +25,19 @@ package org.shadowice.flocke.andotp.Utilities;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.shadowice.flocke.andotp.R;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.Collections;
import java.util.Set;
import static org.shadowice.flocke.andotp.Preferences.PasswordEncryptedPreference.KEY_ALIAS;
public class Settings {
private Context context;
private SharedPreferences settings;
@ -70,6 +75,21 @@ public class Settings {
remove(R.string.settings_key_auth_pin);
}
if (settings.contains(getResString(R.string.settings_key_backup_password))) {
String plainPassword = getBackupPassword();
try {
KeyPair key = KeyStoreHelper.loadOrGenerateAsymmetricKeyPair(context, KEY_ALIAS);
byte[] encPassword = EncryptionHelper.encrypt(key.getPublic(), plainPassword.getBytes(StandardCharsets.UTF_8));
setString(R.string.settings_key_backup_password_enc, Base64.encodeToString(encPassword, Base64.URL_SAFE));
remove(R.string.settings_key_backup_password);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private String getResString(int resId) {
@ -195,6 +215,22 @@ public class Settings {
return getString(R.string.settings_key_backup_password, "");
}
public String getBackupPasswordEnc() {
String base64Password = getString(R.string.settings_key_backup_password_enc, "");
byte[] encPassword = Base64.decode(base64Password, Base64.URL_SAFE);
String password = "";
try {
KeyPair key = KeyStoreHelper.loadOrGenerateAsymmetricKeyPair(context, KEY_ALIAS);
password = new String(EncryptionHelper.decrypt(key.getPrivate(), encPassword), StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return password;
}
public String getOpenPGPProvider() {
return getString(R.string.settings_key_openpgp_provider, "");
}

View file

@ -15,6 +15,7 @@
<string name="settings_key_label_size" translatable="false">pref_label_size</string>
<string name="settings_key_backup_password" translatable="false">pref_backup_password</string>
<string name="settings_key_backup_password_enc" translatable="false">pref_backup_password_enc</string>
<string name="settings_key_openpgp_provider" translatable="false">pref_openpgp_provider</string>
<string name="settings_key_openpgp_keyid" translatable="false">pref_openpgp_keyid</string>
<string name="settings_key_openpgp_sign" translatable="false">pref_openpgp_sign</string>

View file

@ -57,8 +57,8 @@
<PreferenceCategory
android:title="@string/settings_category_title_backup">
<org.shadowice.flocke.andotp.Preferences.PasswordPreference
android:key="@string/settings_key_backup_password"
<org.shadowice.flocke.andotp.Preferences.PasswordEncryptedPreference
android:key="@string/settings_key_backup_password_enc"
android:title="@string/settings_title_backup_password"
android:summary="@string/settings_desc_backup_password" />