Utils: Add a basic implementation of an EncryptionManager which allows to store the password encrypted.
authorFrederic Barthelery <da_risk@beem-project.com>
Fri, 13 Nov 2015 18:17:25 +0100
changeset 1074 fde61b09cd8d
parent 1073 e47233095aab
child 1075 af8866eba015
Utils: Add a basic implementation of an EncryptionManager which allows to store the password encrypted. This use the latest AndroidKeyStore from Android M and allow to store the key securely.
app/src/main/java/com/beem/project/beem/BeemApplication.java
app/src/main/java/com/beem/project/beem/service/auth/PreferenceAuthenticator.java
app/src/main/java/com/beem/project/beem/ui/wizard/AccountConfigureFragment.java
app/src/main/java/com/beem/project/beem/utils/EncryptionManager.java
--- a/app/src/main/java/com/beem/project/beem/BeemApplication.java	Fri Nov 13 18:15:37 2015 +0100
+++ b/app/src/main/java/com/beem/project/beem/BeemApplication.java	Fri Nov 13 18:17:25 2015 +0100
@@ -66,6 +66,10 @@
     public static final String ACCOUNT_USERNAME_KEY = "account_username";
     /** Preference key for account password. */
     public static final String ACCOUNT_PASSWORD_KEY = "account_password";
+    /** Preference key to know if the account password is encrypted. */
+    public static final String ACCOUNT_PASSWORD_IS_ENCRYPTED_KEY = "account_password_is_encrypted";
+    /** Preference key to store the the account password encryption IV. */
+    public static final String ACCOUNT_PASSWORD_ENCRYPTION_IV = "account_password_encryption_iv";
     /** Preference key set to true if using an Android account . */
     public static final String USE_SYSTEM_ACCOUNT_KEY = "use_system_account";
 
--- a/app/src/main/java/com/beem/project/beem/service/auth/PreferenceAuthenticator.java	Fri Nov 13 18:15:37 2015 +0100
+++ b/app/src/main/java/com/beem/project/beem/service/auth/PreferenceAuthenticator.java	Fri Nov 13 18:17:25 2015 +0100
@@ -33,6 +33,7 @@
 import android.preference.PreferenceManager;
 
 import com.beem.project.beem.BeemApplication;
+import com.beem.project.beem.utils.EncryptionManager;
 
 import org.apache.harmony.javax.security.auth.callback.Callback;
 import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
@@ -40,6 +41,7 @@
 import org.apache.harmony.javax.security.auth.callback.PasswordCallback;
 import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException;
 import org.apache.harmony.javax.security.sasl.RealmCallback;
+import org.jivesoftware.smack.util.Base64;
 import org.jivesoftware.smack.util.StringUtils;
 
 /**
@@ -48,6 +50,7 @@
 public class PreferenceAuthenticator implements CallbackHandler {
 
     private final SharedPreferences settings;
+	private final EncryptionManager encryptionManager;
 
     /**
      * Create a PreferenceAuthenticator.
@@ -56,7 +59,8 @@
      */
     public PreferenceAuthenticator(final Context context) {
 	settings = PreferenceManager.getDefaultSharedPreferences(context);
-    }
+		encryptionManager = new EncryptionManager();
+	}
 
     @Override
     public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
@@ -79,7 +83,7 @@
 		String prompt = pcb.getPrompt();
 		if (prompt != null && prompt.startsWith("PKCS11 Password:"))
 			continue;
-		String password = settings.getString(BeemApplication.ACCOUNT_PASSWORD_KEY, "");
+		String password = getPassword();
 		pcb.setPassword(password.toCharArray());
 	    } else if (callbacks[i] instanceof RealmCallback) {
 		RealmCallback rcb = (RealmCallback) callbacks[i];
@@ -90,4 +94,14 @@
 	}
     }
 
+	private String getPassword() {
+		String password = settings.getString(BeemApplication.ACCOUNT_PASSWORD_KEY, "");
+		if (settings.getBoolean(BeemApplication.ACCOUNT_PASSWORD_IS_ENCRYPTED_KEY, false)) {
+			byte[] encryptionIv = Base64.decode(settings.getString(BeemApplication.ACCOUNT_PASSWORD_ENCRYPTION_IV, ""));
+
+			password = encryptionManager.decryptString(password, "Beem-password-key", encryptionIv);
+		}
+		return password;
+	}
+
 }
--- a/app/src/main/java/com/beem/project/beem/ui/wizard/AccountConfigureFragment.java	Fri Nov 13 18:15:37 2015 +0100
+++ b/app/src/main/java/com/beem/project/beem/ui/wizard/AccountConfigureFragment.java	Fri Nov 13 18:17:25 2015 +0100
@@ -74,6 +74,7 @@
 import com.beem.project.beem.R;
 import com.beem.project.beem.ui.Login;
 import com.beem.project.beem.ui.Settings;
+import com.beem.project.beem.utils.EncryptionManager;
 
 import org.jivesoftware.smack.Connection;
 import org.jivesoftware.smack.ConnectionConfiguration;
@@ -81,6 +82,7 @@
 import org.jivesoftware.smack.XMPPException;
 import org.jivesoftware.smack.proxy.ProxyInfo;
 import org.jivesoftware.smack.proxy.ProxyInfo.ProxyType;
+import org.jivesoftware.smack.util.Base64;
 import org.jivesoftware.smack.util.StringUtils;
 
 /**
@@ -110,6 +112,7 @@
     private String mSelectedAccountType;
     private SharedPreferences settings;
     private boolean useSystemAccount;
+	private EncryptionManager encryptionManager;
 
     private com.beem.project.beem.ui.wizard.AccountConfigureFragment.ConnectionTestTask task;
 
@@ -129,7 +132,8 @@
     	setRetainInstance(true);
 
     	settings =  PreferenceManager.getDefaultSharedPreferences(getActivity());
-    }
+		encryptionManager = new EncryptionManager();
+	}
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -288,9 +292,24 @@
      *
      */
     private void saveCredential(String jid, String pass) {
-	SharedPreferences.Editor edit = settings.edit();
+		SharedPreferences.Editor edit = settings.edit();
+		String encryptedPass = pass;
+		boolean isEncryptedPass = false;
+		if (encryptionManager.isEncryptionAvailable()) {
+			if (!encryptionManager.hasEncryptionKey("Beem-password-key")) {
+				encryptionManager.generateEncryptionKey("Beem-password-key");
+			}
+			encryptedPass = encryptionManager.encryptString(encryptedPass, "Beem-password-key");
+			if (encryptedPass != null) {
+				String encryptionIV = Base64.encodeBytes(encryptionManager.getLatestEncryptionIv());
+				edit.putString(BeemApplication.ACCOUNT_PASSWORD_ENCRYPTION_IV, encryptionIV);
+				isEncryptedPass = true;
+			}
+		}
 	edit.putString(BeemApplication.ACCOUNT_USERNAME_KEY, jid);
-	edit.putString(BeemApplication.ACCOUNT_PASSWORD_KEY, pass);
+	edit.putString(BeemApplication.ACCOUNT_PASSWORD_KEY, encryptedPass);
+
+		edit.putBoolean(BeemApplication.ACCOUNT_PASSWORD_IS_ENCRYPTED_KEY, isEncryptedPass);
 	edit.putBoolean(BeemApplication.USE_SYSTEM_ACCOUNT_KEY, false);
 	edit.commit();
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/src/main/java/com/beem/project/beem/utils/EncryptionManager.java	Fri Nov 13 18:17:25 2015 +0100
@@ -0,0 +1,128 @@
+package com.beem.project.beem.utils;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+
+import com.google.android.apps.iosched.util.LogUtils;
+
+import org.jivesoftware.smack.util.Base64;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertificateException;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+
+import static com.google.android.apps.iosched.util.LogUtils.LOGD;
+import static com.google.android.apps.iosched.util.LogUtils.LOGW;
+
+public class EncryptionManager {
+
+    private static final String TAG = LogUtils.makeLogTag(EncryptionManager.class);
+    private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
+    private static final String CIPHER_TRANSFORMATION = String.format("%s/%s/%s",
+            KeyProperties.KEY_ALGORITHM_AES, KeyProperties.BLOCK_MODE_CBC,
+            KeyProperties.ENCRYPTION_PADDING_PKCS7);
+    private boolean isEncryptionAvailable;
+    private KeyStore keystore;
+    private KeyGenerator keyGenerator;
+    private Cipher aesCipher;
+    private byte[] latestEncryptionIv;
+
+    public EncryptionManager() {
+
+        try {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                keystore = KeyStore.getInstance(ANDROID_KEY_STORE);
+                keystore.load(null);
+                aesCipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
+                keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
+                isEncryptionAvailable = true;
+            } else {
+                isEncryptionAvailable = false;
+            }
+        } catch (NoSuchPaddingException | KeyStoreException | IOException | NoSuchProviderException | NoSuchAlgorithmException | CertificateException e) {
+            LOGW(TAG, "Unable to load AndroidKeyStore", e);
+            isEncryptionAvailable = false;
+        }
+    }
+
+    public boolean isEncryptionAvailable() {
+        return isEncryptionAvailable;
+    }
+
+    public boolean hasEncryptionKey(String alias) {
+        if (!isEncryptionAvailable) {
+            return false;
+        }
+        try {
+            return keystore.getKey(alias, null) != null;
+        } catch (GeneralSecurityException e) {
+            LOGD(TAG, "Unable to get key", e);
+            return false;
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    public boolean generateEncryptionKey(String alias) {
+        if (!isEncryptionAvailable)
+            return false;
+        KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+                .setUserAuthenticationRequired(false)
+                .build();
+        try {
+            keyGenerator.init(spec);
+            return keyGenerator.generateKey() != null;
+        } catch (InvalidAlgorithmParameterException e) {
+            LOGW(TAG, "Unable to generate key", e);
+            return false;
+        }
+    }
+
+    public String encryptString(String cleartext, String keyAlias) {
+        if (!isEncryptionAvailable())
+            return null;
+        try {
+            Key key = keystore.getKey(keyAlias, null);
+            aesCipher.init(Cipher.ENCRYPT_MODE, key);
+            byte[] encrypted = aesCipher.doFinal(cleartext.getBytes());
+            latestEncryptionIv = aesCipher.getIV();
+            return Base64.encodeBytes(encrypted);
+        } catch (GeneralSecurityException e) {
+            LOGW(TAG, "Unable to encrypt text", e);
+            return null;
+        }
+    }
+
+    public byte[] getLatestEncryptionIv() {
+        return latestEncryptionIv;
+    }
+
+    public String decryptString(String password, String keyAlias, byte[] encryptionIv) {
+        if (!isEncryptionAvailable())
+            return null;
+        try {
+            Key key = keystore.getKey(keyAlias, null);
+            byte[] passwordByte = Base64.decode(password);
+            aesCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(encryptionIv));
+            byte[] clear = aesCipher.doFinal(passwordByte);
+            return new String(clear);
+        } catch (GeneralSecurityException e) {
+            LOGW(TAG, "Unable to decrypt text", e);
+            return null;
+        }
+    }
+}