parent
de2485e56f
commit
2e09eb42ad
8 changed files with 134 additions and 75 deletions
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, "");
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
||||
|
|
Loading…
Reference in a new issue