Revert "Store passwords encrypted"

This reverts commit a3063c3da4.
pull/153/head
M66B 6 years ago
parent 285340244e
commit cb78dddb4e

@ -702,11 +702,10 @@ Long version:
<a name="faq37"></a>
**(37) How are passwords stored?**
On Android 6 Marshmallow and later passwords are stored encrypted in an app private database.
Passwords are encrypted with the cipher AES/GCM/NoPadding
and a generated secret key stored by the [Android keystore system](https://developer.android.com/training/articles/keystore).
On earlier Android versions passwords are stored in plain text.
Providers require passwords in plain text, so the background service that takes care of synchronizing messages needs to send passwords in plain text.
Since encrypting passwords would require a secret and the background service needs to know this secret, this could only be done by storing that secret.
Storing a secret together with encrypted passwords would not add anything, so passwords are stored in plain text in a safe, inaccessible place.
Recent Android versions encrypt all user data anyway.
<br />

File diff suppressed because it is too large Load Diff

@ -3,7 +3,6 @@ package eu.faircode.email;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Build;
import android.preference.PreferenceManager;
import android.text.TextUtils;
@ -50,7 +49,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 53,
version = 52,
entities = {
EntityIdentity.class,
EntityAccount.class,
@ -561,33 +560,6 @@ public abstract class DB extends RoomDatabase {
db.execSQL("ALTER TABLE `folder` ADD COLUMN `total` INTEGER");
}
})
.addMigrations(new Migration(52, 53) {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i("DB migration from version " + startVersion + " to " + endVersion);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Cursor cursor = db.query("SELECT id, password FROM account");
while (cursor.moveToNext()) {
long id = cursor.getLong(0);
String plain = cursor.getString(1);
db.execSQL("UPDATE account SET password = ? WHERE id = ?",
new Object[]{id, Helper.encryptPassword(plain)});
}
cursor.close();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Cursor cursor = db.query("SELECT id, password FROM identity");
while (cursor.moveToNext()) {
long id = cursor.getLong(0);
String plain = cursor.getString(1);
db.execSQL("UPDATE identity SET password = ? WHERE id = ?",
new Object[]{id, Helper.encryptPassword(plain)});
}
cursor.close();
}
}
})
.build();
}

@ -95,20 +95,6 @@ public class EntityAccount implements Serializable {
return "imap" + (starttls ? "" : "s");
}
String getPassword() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return this.password;
else
return Helper.decryptPassword(this.password);
}
void setPassword(String plain) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
this.password = plain;
else
this.password = Helper.encryptPassword(plain);
}
static String getNotificationChannelName(long account) {
return "notification" + (account == 0 ? "" : "." + account);
}
@ -138,7 +124,7 @@ public class EntityAccount implements Serializable {
json.put("insecure", insecure);
json.put("port", port);
json.put("user", user);
json.put("password", getPassword());
json.put("password", password);
json.put("realm", realm);
json.put("name", name);
@ -170,7 +156,7 @@ public class EntityAccount implements Serializable {
account.insecure = (json.has("insecure") && json.getBoolean("insecure"));
account.port = json.getInt("port");
account.user = json.getString("user");
account.setPassword(json.getString("password"));
account.password = json.getString("password");
if (json.has("realm"))
account.realm = json.getString("realm");
@ -208,7 +194,7 @@ public class EntityAccount implements Serializable {
this.insecure == other.insecure &&
this.port.equals(other.port) &&
this.user.equals(other.user) &&
this.getPassword().equals(other.getPassword()) &&
this.password.equals(other.password) &&
Objects.equals(this.realm, other.realm) &&
Objects.equals(this.name, other.name) &&
Objects.equals(this.color, other.color) &&

@ -19,8 +19,6 @@ package eu.faircode.email;
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
import android.os.Build;
import org.json.JSONException;
import org.json.JSONObject;
@ -101,20 +99,6 @@ public class EntityIdentity {
return (starttls ? "smtp" : "smtps");
}
String getPassword() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return this.password;
else
return Helper.decryptPassword(this.password);
}
void setPassword(String plain) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
this.password = plain;
else
this.password = Helper.encryptPassword(plain);
}
public JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
json.put("id", id);
@ -132,7 +116,7 @@ public class EntityIdentity {
json.put("insecure", insecure);
json.put("port", port);
json.put("user", user);
json.put("password", getPassword());
json.put("password", password);
json.put("realm", realm);
json.put("use_ip", use_ip);
@ -170,7 +154,7 @@ public class EntityIdentity {
identity.insecure = (json.has("insecure") && json.getBoolean("insecure"));
identity.port = json.getInt("port");
identity.user = json.getString("user");
identity.setPassword(json.getString("password"));
identity.password = json.getString("password");
if (json.has("realm"))
identity.realm = json.getString("realm");
if (json.has("use_ip"))
@ -215,7 +199,7 @@ public class EntityIdentity {
this.insecure.equals(other.insecure) &&
this.port.equals(other.port) &&
this.user.equals(other.user) &&
this.getPassword().equals(other.getPassword()) &&
this.password.equals(other.password) &&
Objects.equals(this.realm, other.realm) &&
this.use_ip == other.use_ip &&
this.synchronize.equals(other.synchronize) &&

@ -845,7 +845,7 @@ public class FragmentAccount extends FragmentBase {
boolean check = (synchronize && (account == null ||
auth_type != account.auth_type ||
!host.equals(account.host) || Integer.parseInt(port) != account.port ||
!user.equals(account.user) || !password.equals(account.getPassword()) ||
!user.equals(account.user) || !password.equals(account.password) ||
!Objects.equals(realm, accountRealm)));
boolean reload = (check || account == null ||
!Objects.equals(account.prefix, prefix) ||
@ -914,7 +914,7 @@ public class FragmentAccount extends FragmentBase {
account.insecure = insecure;
account.port = Integer.parseInt(port);
account.user = user;
account.setPassword(password);
account.password = password;
account.realm = realm;
account.name = name;
@ -1135,7 +1135,7 @@ public class FragmentAccount extends FragmentBase {
etUser.setTag(account == null || auth_type == Helper.AUTH_TYPE_PASSWORD ? null : account.user);
etUser.setText(account == null ? null : account.user);
tilPassword.getEditText().setText(account == null ? null : account.getPassword());
tilPassword.getEditText().setText(account == null ? null : account.password);
etRealm.setText(account == null ? null : account.realm);
etName.setText(account == null ? null : account.name);

@ -237,7 +237,7 @@ public class FragmentIdentity extends FragmentBase {
etEmail.setText(account.user);
etUser.setTag(auth_type == Helper.AUTH_TYPE_PASSWORD ? null : account.user);
etUser.setText(account.user);
tilPassword.getEditText().setText(account.getPassword());
tilPassword.getEditText().setText(account.password);
etRealm.setText(account.realm);
tilPassword.setEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD);
etRealm.setEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD);
@ -589,7 +589,7 @@ public class FragmentIdentity extends FragmentBase {
boolean check = (synchronize && (identity == null ||
auth_type != identity.auth_type ||
!host.equals(identity.host) || Integer.parseInt(port) != identity.port ||
!user.equals(identity.user) || !password.equals(identity.getPassword()) ||
!user.equals(identity.user) || !password.equals(identity.password) ||
!Objects.equals(realm, identityRealm) ||
use_ip != identity.use_ip));
boolean reload = (identity == null || identity.synchronize != synchronize || check);
@ -655,7 +655,7 @@ public class FragmentIdentity extends FragmentBase {
identity.insecure = insecure;
identity.port = Integer.parseInt(port);
identity.user = user;
identity.setPassword(password);
identity.password = password;
identity.realm = realm;
identity.use_ip = use_ip;
identity.synchronize = synchronize;
@ -759,7 +759,7 @@ public class FragmentIdentity extends FragmentBase {
etPort.setText(identity == null ? null : Long.toString(identity.port));
etUser.setTag(identity == null || auth_type == Helper.AUTH_TYPE_PASSWORD ? null : identity.user);
etUser.setText(identity == null ? null : identity.user);
tilPassword.getEditText().setText(identity == null ? null : identity.getPassword());
tilPassword.getEditText().setText(identity == null ? null : identity.password);
etRealm.setText(identity == null ? null : identity.realm);
cbUseIp.setChecked(identity == null ? true : identity.use_ip);
cbSynchronize.setChecked(identity == null ? true : identity.synchronize);
@ -864,7 +864,7 @@ public class FragmentIdentity extends FragmentBase {
spAccount.setSelection(pos);
// OAuth token could be updated
if (pos > 0 && accounts.get(pos).auth_type != Helper.AUTH_TYPE_PASSWORD)
tilPassword.getEditText().setText(accounts.get(pos).getPassword());
tilPassword.getEditText().setText(accounts.get(pos).password);
break;
}
}

@ -335,7 +335,7 @@ public class FragmentQuickSetup extends FragmentBase {
account.insecure = false;
account.port = provider.imap_port;
account.user = user;
account.setPassword(password);
account.password = password;
account.name = provider.name;
account.color = null;
@ -389,7 +389,7 @@ public class FragmentQuickSetup extends FragmentBase {
identity.insecure = false;
identity.port = provider.smtp_port;
identity.user = user;
identity.setPassword(password);
identity.password = password;
identity.synchronize = true;
identity.primary = true;

@ -42,10 +42,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.text.TextUtils;
import android.util.Base64;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
@ -80,8 +77,6 @@ import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
@ -96,10 +91,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.FolderClosedException;
@ -109,7 +100,6 @@ import javax.mail.internet.InternetAddress;
import javax.net.ssl.HttpsURLConnection;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.Lifecycle;
@ -819,12 +809,12 @@ public class Helper {
static void connect(Context context, IMAPStore istore, EntityAccount account) throws MessagingException {
try {
istore.connect(account.host, account.port, account.user, account.getPassword());
istore.connect(account.host, account.port, account.user, account.password);
} catch (AuthenticationFailedException ex) {
if (account.auth_type == AUTH_TYPE_GMAIL) {
account.setPassword(refreshToken(context, "com.google", account.user, account.getPassword()));
account.password = refreshToken(context, "com.google", account.user, account.password);
DB.getInstance(context).account().setAccountPassword(account.id, account.password);
istore.connect(account.host, account.port, account.user, account.getPassword());
istore.connect(account.host, account.port, account.user, account.password);
} else
throw ex;
}
@ -1053,56 +1043,4 @@ public class Helper {
return organization;
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private static SecretKey getSecretKey() throws Throwable {
final String alias = BuildConfig.APPLICATION_ID + ".key";
KeyStore store = KeyStore.getInstance("AndroidKeyStore");
store.load(null);
KeyStore.SecretKeyEntry entry = (KeyStore.SecretKeyEntry) store.getEntry(alias, null);
if (entry != null)
return entry.getSecretKey();
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build();
generator.init(spec);
return generator.generateKey();
}
@RequiresApi(api = Build.VERSION_CODES.M)
static String decryptPassword(String secret) {
try {
int slash = secret.indexOf('/');
byte[] iv = Base64.decode(secret.substring(0, slash), Base64.URL_SAFE);
byte[] encrypted = Base64.decode(secret.substring(slash + 1), Base64.URL_SAFE);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), spec);
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Throwable ex) {
Log.e(ex);
return secret;
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
static String encryptPassword(String plain) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());
byte[] iv = cipher.getIV();
byte[] encrypted = cipher.doFinal(plain.getBytes(StandardCharsets.UTF_8));
return Base64.encodeToString(iv, Base64.URL_SAFE) + "/" + Base64.encodeToString(encrypted, Base64.URL_SAFE);
} catch (Throwable ex) {
Log.e(ex);
return plain;
}
}
}

@ -294,13 +294,13 @@ public class ServiceSend extends LifecycleService {
// Connect transport
db.identity().setIdentityState(ident.id, "connecting");
try {
itransport.connect(ident.host, ident.port, ident.user, ident.getPassword());
itransport.connect(ident.host, ident.port, ident.user, ident.password);
} catch (AuthenticationFailedException ex) {
if (ident.auth_type == Helper.AUTH_TYPE_GMAIL) {
EntityAccount account = db.account().getAccount(ident.account);
ident.setPassword(Helper.refreshToken(this, "com.google", ident.user, account.getPassword()));
ident.password = Helper.refreshToken(this, "com.google", ident.user, account.password);
DB.getInstance(this).identity().setIdentityPassword(ident.id, ident.password);
itransport.connect(ident.host, ident.port, ident.user, ident.getPassword());
itransport.connect(ident.host, ident.port, ident.user, ident.password);
} else
throw ex;
}

Loading…
Cancel
Save