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

Resuelto Niko Gamulin asked hace 54 años • 16 respuestas

Estoy creando una aplicación que se conecta al servidor usando 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.

Estaba intentando hacerlo con Preferencias compartidas pero no estoy seguro de si esta es la mejor solución.

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

Niko Gamulin avatar Jan 01 '70 08:01 Niko Gamulin
Aceptado

En general, SharedPreferences es la mejor opción para almacenar preferencias, por lo que, en general, recomendaría ese enfoque para guardar la configuración de la aplicación y del usuario.

La única área de preocupación aquí es lo que estás ahorrando. Las contraseñas siempre son difíciles de almacenar y yo sería especialmente cauteloso a la hora de almacenarlas como texto sin cifrar. La arquitectura de Android es tal que las SharedPreferences de su aplicación están protegidas para evitar que otras aplicaciones puedan acceder a los valores, por lo que hay cierta seguridad allí, pero el acceso físico a un teléfono podría permitir el acceso a los valores.

Si es posible, consideraría modificar el servidor para utilizar un token negociado para proporcionar acceso, algo así como OAuth . Alternativamente, es posible que necesites construir algún tipo de almacén criptográfico, aunque eso no es trivial. Como mínimo, asegúrese de cifrar la contraseña antes de escribirla en el disco.

Reto Meier avatar Apr 24 '2009 16:04 Reto Meier

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

Sin embargo, dicho esto, parece haber una iniciativa publicitaria para identificar aplicaciones móviles que almacenan sus contraseñas en texto sin cifrar en SharedPreferences y arrojar 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 que se preste más atención a la seguridad en general, yo diría que este tipo de atención a este tema en particular no aumenta significativamente nuestra seguridad general. Sin embargo, siendo las percepciones como son, aquí hay una solución para cifrar los datos que coloca en SharedPreferences.

Simplemente envuelva su propio objeto SharedPreferences en este, y cualquier dato que lea/escriba se cifrará y descifrará 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 de 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<String, ?> 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);
        }
    }

}
emmby avatar Jun 18 '2011 02:06 emmby

La forma más sencilla de almacenar una única preferencia 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, siempre puede cifrar la contraseña antes de almacenarla.

Jeremy Logan avatar May 04 '2009 08:05 Jeremy Logan

Con el fragmento proporcionado por Richard, puede cifrar la contraseña antes de guardarla. Sin embargo, la API de preferencias no proporciona una manera fácil de interceptar el valor y cifrarlo: puede bloquear su guardado a través de un oyente OnPreferenceChange y, en teoría, podría modificarlo a través de un preferenciaChangeListener, pero eso da como resultado un bucle sin fin.

Anteriormente sugerí 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 preferenciaChangeListener, puede tomar el valor ingresado, cifrarlo 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 que prefiero ahora, es crear su propia preferencia personalizada, extendiendo EditTextPreference, @Override los métodos setText()y , de modo que cifre la contraseña y devuelva nulo.getText()setText()getText()

Mark avatar Mar 02 '2011 14:03 Mark