# HG changeset patch # User Frederic Barthelery # Date 1447435045 -3600 # Node ID fde61b09cd8d5c9bc7e8d7f9802113dc33db45ef # Parent e47233095aabb6ea54ef782ba07f68743329b609 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. diff -r e47233095aab -r fde61b09cd8d app/src/main/java/com/beem/project/beem/BeemApplication.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"; diff -r e47233095aab -r fde61b09cd8d app/src/main/java/com/beem/project/beem/service/auth/PreferenceAuthenticator.java --- 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; + } + } diff -r e47233095aab -r fde61b09cd8d app/src/main/java/com/beem/project/beem/ui/wizard/AccountConfigureFragment.java --- 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(); } diff -r e47233095aab -r fde61b09cd8d app/src/main/java/com/beem/project/beem/utils/EncryptionManager.java --- /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; + } + } +}