¿Cuál es la forma más adecuada de almacenar la configuración del usuario en la aplicación de Android?

Estoy creando una aplicación que se conecta al servidor usando el nombre de usuario / contraseña y me gustaría habilitar la opción “Guardar contraseña” para que el usuario no tenga que escribir la contraseña cada vez que se inicia la aplicación.

Intentaba hacerlo con las Preferencias Compartidas, pero no estoy seguro si esta es la mejor solución.

Agradecería cualquier sugerencia sobre cómo almacenar valores / configuraciones de usuario en la aplicación de Android.

En general, las Preferencias Compartidas son su mejor opción para almacenar preferencias, por lo tanto, en general, recomendaría ese enfoque para guardar la aplicación y la configuración del usuario.

La única área de preocupación aquí es lo que estás guardando. Las contraseñas son siempre algo complicado de almacenar, y sería particularmente cauteloso al almacenarlas como texto claro. La architecture de Android es tal que las Preferencias Compartidas de su aplicación se encuentran en la zona de pruebas para evitar que otras aplicaciones puedan acceder a los valores, de modo que hay algo de seguridad allí, pero el acceso físico a un teléfono podría permitir el acceso a los valores.

De ser posible, consideraría modificar el servidor para usar un token negociado para proporcionar acceso, algo así como OAuth . Alternativamente, puede que necesite construir algún tipo de almacén criptográfico, aunque eso no es trivial. Por lo menos, asegúrese de estar encriptando la contraseña antes de escribirla en el disco.

Estoy de acuerdo con Reto y FiXedd. Objetivamente hablando, no tiene mucho sentido invertir tiempo y esfuerzo significativos para cifrar contraseñas en SharedPreferences, ya que es probable que cualquier atacante que tenga acceso a su archivo de preferencias también tenga acceso al archivo binario de la aplicación y, por lo tanto, las claves para descifrar el contraseña.

Sin embargo, dicho esto, parece que hay una iniciativa de publicidad que identifica las aplicaciones móviles que almacenan sus contraseñas en texto claro en las Preferencias Compartidas y arroja luz desfavorable sobre esas aplicaciones. Consulte http://blogs.wsj.com/digits/2011/06/08/some-top-apps-put-data-at-risk/ y http://viaforensics.com/appwatchdog para ver algunos ejemplos.

Si bien necesitamos prestar más atención a la seguridad en general, yo diría que este tipo de atención en este tema en particular no aumenta significativamente nuestra seguridad general. Sin embargo, como las percepciones son como son, aquí hay una solución para encriptar los datos que colocas en SharedPreferences.

Simplemente envuelva su propio objeto SharedPreferences en este y todos los datos que lea / escriba se cifrarán y descifrarán automáticamente. p.ej.

 final SharedPreferences prefs = new ObscuredSharedPreferences( this, this.getSharedPreferences(MY_PREFS_FILE_NAME, Context.MODE_PRIVATE) ); // eg. prefs.edit().putString("foo","bar").commit(); prefs.getString("foo", null); 

Aquí está el código para la clase:

 /** * Warning, this gives a false sense of security. If an attacker has enough access to * acquire your password store, then he almost certainly has enough access to acquire your * source binary and figure out your encryption key. However, it will prevent casual * investigators from acquiring passwords, and thereby may prevent undesired negative * publicity. */ public class ObscuredSharedPreferences implements SharedPreferences { protected static final String UTF8 = "utf-8"; private static final char[] SEKRIT = ... ; // INSERT A RANDOM PASSWORD HERE. // Don't use anything you wouldn't want to // get out there if someone decompiled // your app. protected SharedPreferences delegate; protected Context context; public ObscuredSharedPreferences(Context context, SharedPreferences delegate) { this.delegate = delegate; this.context = context; } public class Editor implements SharedPreferences.Editor { protected SharedPreferences.Editor delegate; public Editor() { this.delegate = ObscuredSharedPreferences.this.delegate.edit(); } @Override public Editor putBoolean(String key, boolean value) { delegate.putString(key, encrypt(Boolean.toString(value))); return this; } @Override public Editor putFloat(String key, float value) { delegate.putString(key, encrypt(Float.toString(value))); return this; } @Override public Editor putInt(String key, int value) { delegate.putString(key, encrypt(Integer.toString(value))); return this; } @Override public Editor putLong(String key, long value) { delegate.putString(key, encrypt(Long.toString(value))); return this; } @Override public Editor putString(String key, String value) { delegate.putString(key, encrypt(value)); return this; } @Override public void apply() { delegate.apply(); } @Override public Editor clear() { delegate.clear(); return this; } @Override public boolean commit() { return delegate.commit(); } @Override public Editor remove(String s) { delegate.remove(s); return this; } } public Editor edit() { return new Editor(); } @Override public Map getAll() { throw new UnsupportedOperationException(); // left as an exercise to the reader } @Override public boolean getBoolean(String key, boolean defValue) { final String v = delegate.getString(key, null); return v!=null ? Boolean.parseBoolean(decrypt(v)) : defValue; } @Override public float getFloat(String key, float defValue) { final String v = delegate.getString(key, null); return v!=null ? Float.parseFloat(decrypt(v)) : defValue; } @Override public int getInt(String key, int defValue) { final String v = delegate.getString(key, null); return v!=null ? Integer.parseInt(decrypt(v)) : defValue; } @Override public long getLong(String key, long defValue) { final String v = delegate.getString(key, null); return v!=null ? Long.parseLong(decrypt(v)) : defValue; } @Override public String getString(String key, String defValue) { final String v = delegate.getString(key, null); return v != null ? decrypt(v) : defValue; } @Override public boolean contains(String s) { return delegate.contains(s); } @Override public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { delegate.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener); } @Override public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { delegate.unregisterOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener); } protected String encrypt( String value ) { try { final byte[] bytes = value!=null ? value.getBytes(UTF8) : new byte[0]; SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(new PBEKeySpec(SEKRIT)); Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(Settings.Secure.getString(context.getContentResolver(),Settings.Secure.ANDROID_ID).getBytes(UTF8), 20)); return new String(Base64.encode(pbeCipher.doFinal(bytes), Base64.NO_WRAP),UTF8); } catch( Exception e ) { throw new RuntimeException(e); } } protected String decrypt(String value){ try { final byte[] bytes = value!=null ? Base64.decode(value,Base64.DEFAULT) : new byte[0]; SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(new PBEKeySpec(SEKRIT)); Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(Settings.Secure.getString(context.getContentResolver(),Settings.Secure.ANDROID_ID).getBytes(UTF8), 20)); return new String(pbeCipher.doFinal(bytes),UTF8); } catch( Exception e) { throw new RuntimeException(e); } } } 

La forma más sencilla de almacenar una preferencia única en una actividad de Android es hacer algo como esto:

 Editor e = this.getPreferences(Context.MODE_PRIVATE).edit(); e.putString("password", mPassword); e.commit(); 

Si le preocupa la seguridad de estos, entonces siempre puede encriptar la contraseña antes de almacenarla.

Con el fragmento proporcionado por Richard, puede encriptar la contraseña antes de guardarla. Sin embargo, la API de preferencias no proporciona una manera fácil de interceptar el valor y encriptarlo; puedes bloquearlo para que se guarde a través de un detector OnPreferenceChange y, teóricamente, podrías modificarlo a través de un preferenceChangeListener, pero eso da como resultado un bucle infinito.

Antes había sugerido agregar una preferencia “oculta” para lograr esto. Definitivamente no es la mejor manera. Voy a presentar otras dos opciones que considero más viables.

Primero, el más simple, está en un preferenceChangeListener, puede tomar el valor ingresado, encriptarlo y luego guardarlo en un archivo de preferencias alternativo:

  public boolean onPreferenceChange(Preference preference, Object newValue) { // get our "secure" shared preferences file. SharedPreferences secure = context.getSharedPreferences( "SECURE", Context.MODE_PRIVATE ); String encryptedText = null; // encrypt and set the preference. try { encryptedText = SimpleCrypto.encrypt(Preferences.SEED,(String)newValue); Editor editor = secure.getEditor(); editor.putString("encryptedPassword",encryptedText); editor.commit(); } catch (Exception e) { e.printStackTrace(); } // always return false. return false; } 

La segunda forma, y ​​la forma en que ahora prefiero, es crear tu propia preferencia personalizada, extendiendo EditTextPreference, @ getText() métodos setText() y getText() , de modo que setText() cifra la contraseña y getText() regresa nulo.

Sé que esto es un poco de nigromancia, pero debes usar el Android AccountManager . Está especialmente diseñado para este escenario. Es un poco engorroso, pero una de las cosas que hace es invalidar las credenciales locales si la tarjeta SIM cambia, por lo que si alguien desliza su teléfono y arroja una nueva tarjeta SIM, sus credenciales no se verán comprometidas.

Esto también le brinda al usuario una forma rápida y fácil de acceder (y posiblemente eliminar) las credenciales almacenadas para cualquier cuenta que tengan en el dispositivo, todo desde un solo lugar.

SampleSyncAdapter es un ejemplo que hace uso de credenciales de cuenta almacenadas.

Bueno; Ha pasado un tiempo desde que la respuesta es mixta, pero aquí hay algunas respuestas comunes. Investigué esto como loco y fue difícil construir una buena respuesta

  1. El método MODE_PRIVATE se considera generalmente seguro, si se supone que el usuario no rooteó el dispositivo. Sus datos se almacenan en texto plano en una parte del sistema de archivos a la que solo puede acceder el progtwig original. Esto facilita la tarea de obtener la contraseña con otra aplicación en un dispositivo rooteado. Por otra parte, ¿quieres admitir dispositivos rooteados?

  2. AES sigue siendo la mejor encriptación que puede hacer. Recuerde buscar esto si está comenzando una nueva implementación si ha pasado un tiempo desde que publiqué esto. El mayor problema con esto es “¿Qué hacer con la clave de cifrado?”

Entonces, ahora estamos en el “¿Qué hacer con la llave?” parte. Esta es la parte difícil. Obtener la clave no es tan malo. Puede usar una función de derivación de clave para tomar una contraseña y convertirla en una clave bastante segura. Entra en problemas como “¿cuántos pases hace con PKFDF2?”, Pero ese es otro tema

  1. Lo ideal es que almacene la clave AES fuera del dispositivo. Tienes que encontrar una buena manera de recuperar la clave del servidor de forma segura, confiable y segura

  2. Tiene una secuencia de inicio de sesión de algún tipo (incluso la secuencia de inicio de sesión original que realiza para el acceso remoto). Puede hacer dos ejecuciones de su generador de claves con la misma contraseña. Cómo funciona esto es que deriva la clave dos veces con un nuevo sal y un nuevo vector de inicialización seguro. Almacena una de esas contraseñas generadas en el dispositivo y usa la segunda contraseña como clave AES.

Cuando inicia sesión, deriva la clave en el inicio de sesión local y la compara con la clave almacenada. Una vez hecho esto, usa la clave derive # 2 para AES.

  1. Usando el enfoque “generalmente seguro”, cifra los datos usando AES y almacena la clave en MODE_PRIVATE. Esto es recomendado por una reciente publicación de blog de Android. No es increíblemente seguro, pero es mucho mejor para algunas personas que el texto sin formato

Puedes hacer muchas variaciones de estos. Por ejemplo, en lugar de una secuencia de inicio de sesión completa, puede hacer un PIN rápido (derivado). El PIN rápido puede no ser tan seguro como una secuencia de inicio de sesión completa, pero es mucho más seguro que el texto sin formato

Pondré mi sombrero en el cuadrilátero solo para hablar sobre la seguridad de las contraseñas en general en Android. En Android, el dispositivo binario debe considerarse comprometido: es el mismo para cualquier aplicación final que esté bajo el control directo del usuario. Conceptualmente, un hacker podría usar el acceso necesario al binario para descomstackrlo y eliminar sus contraseñas encriptadas, etc.

Como tal, hay dos sugerencias que me gustaría lanzar allí si la seguridad es una gran preocupación para usted:

1) No almacene la contraseña real. Almacene un token de acceso otorgado y use el token de acceso y la firma del teléfono para autenticar la sesión del servidor. El beneficio de esto es que puede hacer que el token tenga una duración limitada, no está comprometiendo la contraseña original y tiene una buena firma que puede usar para correlacionarse con el tráfico más tarde (por ejemplo, verificar los bashs de intrusión e invalidar el token que lo hace inútil).

2) Utiliza autenticación de 2 factores. Esto puede ser más molesto e intrusivo, pero para algunas situaciones de cumplimiento es inevitable.

También puede consultar esta pequeña publicación, que contiene la funcionalidad que menciona.

https://github.com/kovmarci86/android-secure-preferences

Es similar a algunos de los otros enfoques aquí. Hope ayuda 🙂

Esta es una respuesta complementaria para quienes llegan aquí en función del título de la pregunta (como lo hice yo) y no es necesario que se ocupen de los problemas de seguridad relacionados con el guardado de contraseñas.

Cómo usar Preferencias Compartidas

La configuración del usuario generalmente se guarda localmente en Android usando SharedPreferences con un par clave-valor. Utiliza la tecla de String para guardar o buscar el valor asociado.

Escribir en las preferencias compartidas

 String key = "myInt"; int valueToSave = 10; SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = sharedPref.edit(); editor.putInt(key, valueToSave).commit(); 

Utilice apply() lugar de commit() para guardar en segundo plano en lugar de guardarlo inmediatamente.

Leer desde las preferencias compartidas

 String key = "myInt"; int defaultValue = 0; SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); int savedValue = sharedPref.getInt(key, defaultValue); 

El valor predeterminado se usa si la clave no se encuentra.

Notas

  • En lugar de utilizar una clave de cadena local en varios lugares como lo hice anteriormente, sería mejor usar una constante en una sola ubicación. Puede usar algo como esto en la parte superior de la actividad de configuración:

     final static String PREF_MY_INT_KEY = "myInt"; 
  • putString() un int en mi ejemplo, pero también puedes usar putString() , putBoolean() , getString() , getBoolean() , etc.

  • Ver la documentación para más detalles.
  • Hay varias formas de obtener SharedPreferences. Vea esta respuesta para saber qué buscar.

Esta respuesta se basa en un enfoque sugerido por Mark. Se crea una versión personalizada de la clase EditTextPreference que convierte el texto sin formato visto en la vista y una versión cifrada de la contraseña almacenada en el almacenamiento de preferencias.

Como ha señalado la mayoría de quienes respondieron este hilo, esta no es una técnica muy segura, aunque el grado de seguridad depende en parte del código de cifrado / descifrado utilizado. Pero es bastante simple y conveniente, y frustrará la mayoría de los espías informales.

Aquí está el código para la clase personalizada EditTextPreference:

 package com.Merlinia.OutBack_Client; import android.content.Context; import android.preference.EditTextPreference; import android.util.AttributeSet; import android.util.Base64; import com.Merlinia.MEncryption_Main.MEncryptionUserPassword; /** * This class extends the EditTextPreference view, providing encryption and decryption services for * OutBack user passwords. The passwords in the preferences store are first encrypted using the * MEncryption classes and then converted to string using Base64 since the preferences store can not * store byte arrays. * * This is largely copied from this article, except for the encryption/decryption parts: * https://groups.google.com/forum/#!topic/android-developers/pMYNEVXMa6M */ public class EditPasswordPreference extends EditTextPreference { // Constructor - needed despite what compiler says, otherwise app crashes public EditPasswordPreference(Context context) { super(context); } // Constructor - needed despite what compiler says, otherwise app crashes public EditPasswordPreference(Context context, AttributeSet attributeSet) { super(context, attributeSet); } // Constructor - needed despite what compiler says, otherwise app crashes public EditPasswordPreference(Context context, AttributeSet attributeSet, int defaultStyle) { super(context, attributeSet, defaultStyle); } /** * Override the method that gets a preference from the preferences storage, for display by the * EditText view. This gets the base64 password, converts it to a byte array, and then decrypts * it so it can be displayed in plain text. * @return OutBack user password in plain text */ @Override public String getText() { String decryptedPassword; try { decryptedPassword = MEncryptionUserPassword.aesDecrypt( Base64.decode(getSharedPreferences().getString(getKey(), ""), Base64.DEFAULT)); } catch (Exception e) { e.printStackTrace(); decryptedPassword = ""; } return decryptedPassword; } /** * Override the method that gets a text string from the EditText view and stores the value in * the preferences storage. This encrypts the password into a byte array and then encodes that * in base64 format. * @param passwordText OutBack user password in plain text */ @Override public void setText(String passwordText) { byte[] encryptedPassword; try { encryptedPassword = MEncryptionUserPassword.aesEncrypt(passwordText); } catch (Exception e) { e.printStackTrace(); encryptedPassword = new byte[0]; } getSharedPreferences().edit().putString(getKey(), Base64.encodeToString(encryptedPassword, Base64.DEFAULT)) .commit(); } @Override protected void onSetInitialValue(boolean restreValue, Object defaultValue) { if (restreValue) getEditText().setText(getText()); else super.onSetInitialValue(restreValue, defaultValue); } } 

Esto muestra cómo se puede usar: este es el archivo de “elementos” que controla la visualización de preferencias. Tenga en cuenta que contiene tres vistas ordinarias EditTextPreference y una de las vistas EditPasswordPreference personalizadas.

       

En cuanto al cifrado / descifrado real, eso queda como un ejercicio para el lector. Actualmente estoy usando un código basado en este artículo http://zenu.wordpress.com/2011/09/21/aes-128bit-cross-platform-java-and-c-encryption-compatibility/ , aunque con diferentes valores para la clave y el vector de inicialización.

En primer lugar, creo que los datos del usuario no se deben almacenar en el teléfono, y si se debe almacenar datos en algún lugar del teléfono, se deben cifrar con los datos privados de la aplicación. La seguridad de las credenciales de los usuarios debe ser la prioridad de la aplicación.

Los datos confidenciales deben almacenarse de forma segura o no almacenarse en absoluto. En caso de que se pierda un dispositivo o una infección de malware, los datos almacenados inseguramente pueden verse comprometidos.

necesita usar la aplicación de seguridad sqlite para almacenar las contraseñas. aquí está el mejor ejemplo, que almacena contraseñas, – contraseña segura. aquí hay un enlace para la fuente y la explicación: http://code.google.com/p/android-passwordsafe/

preferencias compartidas es la manera más fácil de almacenar nuestros datos de aplicación. pero es posible que cualquiera pueda borrar nuestros datos de preferencias compartidas a través del administrador de aplicaciones. Por lo tanto, no creo que sea completamente seguro para nuestra aplicación.