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

View file

@ -33,19 +33,28 @@ import android.text.InputType;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod; import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Base64;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import org.shadowice.flocke.andotp.R; 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 { implements View.OnClickListener, TextWatcher {
public enum Mode { public enum Mode {
PASSWORD, PIN PASSWORD, PIN
} }
public static final String KEY_ALIAS = "password";
private KeyPair key;
private static final String DEFAULT_VALUE = ""; private static final String DEFAULT_VALUE = "";
private Mode mode = Mode.PASSWORD; private Mode mode = Mode.PASSWORD;
@ -57,9 +66,15 @@ public class PasswordPreference extends DialogPreference
private String value = DEFAULT_VALUE; private String value = DEFAULT_VALUE;
public PasswordPreference(Context context, AttributeSet attrs) { public PasswordEncryptedPreference(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
try {
key = KeyStoreHelper.loadOrGenerateAsymmetricKeyPair(context, KEY_ALIAS);
} catch (Exception e) {
e.printStackTrace();
}
setDialogLayoutResource(R.layout.component_password); setDialogLayoutResource(R.layout.component_password);
} }
@ -120,13 +135,31 @@ public class PasswordPreference extends DialogPreference
return a.getString(index); 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 @Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
if (restorePersistedValue) { if (restorePersistedValue) {
value = getPersistedString(DEFAULT_VALUE); restoreAndDecrypt(getPersistedString(DEFAULT_VALUE));
} else { } else {
value = (String) defaultValue; value = (String) defaultValue;
persistString(value); encryptAndPersist(value);
} }
} }
@ -138,7 +171,7 @@ public class PasswordPreference extends DialogPreference
break; break;
case (R.id.btnSave): case (R.id.btnSave):
value = passwordInput.getText().toString(); value = passwordInput.getText().toString();
persistString(value); encryptAndPersist(value);
getDialog().dismiss(); getDialog().dismiss();
break; break;

View file

@ -29,6 +29,8 @@ import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Arrays; import java.util.Arrays;
@ -41,7 +43,9 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
public class EncryptionHelper { 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; private final static int IV_LENGTH = 12;
public static SecretKey generateSymmetricKeyFromPassword(String password) public static SecretKey generateSymmetricKeyFromPassword(String password)
@ -51,30 +55,14 @@ public class EncryptionHelper {
return new SecretKeySpec(sha.digest(password.getBytes(StandardCharsets.UTF_8)), "AES"); 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) public static byte[] encrypt(SecretKey secretKey, byte[] plaintext)
throws NoSuchPaddingException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidAlgorithmParameterException { throws NoSuchPaddingException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
final byte[] iv = new byte[IV_LENGTH]; final byte[] iv = new byte[IV_LENGTH];
new SecureRandom().nextBytes(iv); 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]; byte[] combined = new byte[iv.length + cipherText.length];
System.arraycopy(iv, 0, combined, 0, iv.length); System.arraycopy(iv, 0, combined, 0, iv.length);
@ -83,11 +71,30 @@ public class EncryptionHelper {
return combined; 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) public static byte[] decrypt(SecretKey secretKey, byte[] cipherText)
throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
byte[] iv = Arrays.copyOfRange(cipherText, 0, IV_LENGTH); 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.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.KeyStore; import java.security.KeyStore;
@ -41,7 +40,6 @@ import java.security.spec.AlgorithmParameterSpec;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
@ -49,13 +47,19 @@ import javax.security.auth.x500.X500Principal;
public class KeyStoreHelper { public class KeyStoreHelper {
private final static int KEY_LENGTH = 16; 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 start = new GregorianCalendar();
final Calendar end = new GregorianCalendar(); final Calendar end = new GregorianCalendar();
end.add(Calendar.YEAR, 100); end.add(Calendar.YEAR, 100);
AlgorithmParameterSpec spec;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 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)) .setCertificateSubject(new X500Principal("CN=" + alias))
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setCertificateSerialNumber(BigInteger.ONE) .setCertificateSerialNumber(BigInteger.ONE)
@ -63,7 +67,7 @@ public class KeyStoreHelper {
.setCertificateNotAfter(end.getTime()) .setCertificateNotAfter(end.getTime())
.build(); .build();
} else { } else {
return new KeyPairGeneratorSpec.Builder(context) spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(alias) .setAlias(alias)
.setSubject(new X500Principal("CN=" + alias)) .setSubject(new X500Principal("CN=" + alias))
.setSerialNumber(BigInteger.ONE) .setSerialNumber(BigInteger.ONE)
@ -71,17 +75,10 @@ public class KeyStoreHelper {
.setEndDate(end.getTime()) .setEndDate(end.getTime())
.build(); .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"); KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
gen.initialize(generateAlgorithmSpec(context, alias)); gen.initialize(spec);
gen.generateKeyPair(); gen.generateKeyPair();
} }
@ -89,21 +86,6 @@ public class KeyStoreHelper {
return new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey()); 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. * Load our symmetric secret key.
* The symmetric secret key is stored securely on disk by wrapping * 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.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Base64;
import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.shadowice.flocke.andotp.R; import org.shadowice.flocke.andotp.R;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import static org.shadowice.flocke.andotp.Preferences.PasswordEncryptedPreference.KEY_ALIAS;
public class Settings { public class Settings {
private Context context; private Context context;
private SharedPreferences settings; private SharedPreferences settings;
@ -70,6 +75,21 @@ public class Settings {
remove(R.string.settings_key_auth_pin); 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) { private String getResString(int resId) {
@ -195,6 +215,22 @@ public class Settings {
return getString(R.string.settings_key_backup_password, ""); 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() { public String getOpenPGPProvider() {
return getString(R.string.settings_key_openpgp_provider, ""); 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_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" 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_provider" translatable="false">pref_openpgp_provider</string>
<string name="settings_key_openpgp_keyid" translatable="false">pref_openpgp_keyid</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> <string name="settings_key_openpgp_sign" translatable="false">pref_openpgp_sign</string>

View file

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