web-dev-qa-db-fra.com

Quel est le moyen le plus approprié de stocker les paramètres utilisateur dans une application Android

Je crée une application qui se connecte au serveur à l'aide du nom d'utilisateur/mot de passe et je souhaite activer l'option "Enregistrer le mot de passe" afin que l'utilisateur n'ait pas à saisir le mot de passe à chaque démarrage de l'application.

J'essayais de le faire avec les préférences partagées mais je ne suis pas sûr que ce soit la meilleure solution.

J'apprécierais toute suggestion sur la façon de stocker les valeurs/paramètres de l'utilisateur dans l'application Android.

291
Niko Gamulin

En général, les préférences partagées sont votre meilleur moyen de stocker les préférences. C'est pourquoi, en général, je recommanderais cette approche pour enregistrer les paramètres de l'application et de l'utilisateur.

Le seul sujet de préoccupation ici est ce que vous économisez. Les mots de passe sont toujours difficiles à stocker, et je serais particulièrement prudent de les conserver sous forme de texte clair. L’architecture Android est telle que les SharedPreferences de votre application sont mises en sandbox pour empêcher d’autres applications d’être en mesure d’accéder aux valeurs. Il existe donc une certaine sécurité, mais un accès physique à un téléphone pourrait éventuellement permettre l’accès à ces valeurs.

Si possible, j'aimerais envisager de modifier le serveur pour utiliser un jeton négocié pour fournir un accès, quelque chose comme OAuth . Sinon, vous devrez peut-être créer une sorte de magasin cryptographique, bien que ce ne soit pas trivial. À tout le moins, assurez-vous de chiffrer le mot de passe avant de l'écrire sur le disque.

226
Reto Meier

Je suis d'accord avec Reto et FiXedd. Objectivement, il n’est pas logique d’investir beaucoup de temps et d’efforts dans le chiffrement des mots de passe dans SharedPreferences, car tout attaquant ayant accès à votre fichier de préférences aura probablement également accès au binaire de votre application, et donc aux clés permettant de déchiffrer le fichier. mot de passe.

Cependant, cela dit, il semble y avoir une initiative de publicité en cours pour identifier les applications mobiles qui stockent leurs mots de passe en texte clair dans SharedPreferences et pour éclairer de manière défavorable ces applications. Voir http://blogs.wsj.com/digits/2011/06/08/some-top-apps-put-data-at-risk/ et http://viaforensics.com/appwatchdog pour quelques exemples. 

Bien que nous ayons besoin de plus d'attention portée à la sécurité en général, je dirais que ce type d'attention sur cette question particulière n'augmente pas de manière significative notre sécurité globale. Cependant, les perceptions étant ce qu’elles sont, voici une solution pour chiffrer les données que vous placez dans SharedPreferences.

Enroulez simplement votre propre objet SharedPreferences dans celui-ci, et toutes les données que vous lisez/écrivez seront automatiquement cryptées et décryptées. par exemple.

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);

Voici le code pour la classe:

/**
 * 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);
        }
    }

}
204
emmby

La manière la plus simple de stocker une seule préférence dans une activité Android consiste à procéder comme suit:

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

Si vous vous inquiétez de la sécurité de ceux-ci, vous pouvez toujours chiffrer le mot de passe avant de le stocker.

28
Jeremy Logan

En utilisant l'extrait fourni par Richard, vous pouvez chiffrer le mot de passe avant de l'enregistrer. Cependant, l’API des préférences ne fournit pas un moyen facile d’intercepter la valeur et de la chiffrer. Vous pouvez empêcher son enregistrement via un écouteur OnPreferenceChange et vous pouvez théoriquement la modifier via un preferenceChangeListener, mais cela entraîne une boucle sans fin.

J'avais déjà suggéré d'ajouter une préférence "cachée" afin d'accomplir cela. Ce n'est certainement pas la meilleure façon. Je vais présenter deux autres options que je considère plus viables. 

Tout d'abord, le plus simple est dans un preferenceChangeListener, vous pouvez récupérer la valeur entrée, la chiffrer, puis l'enregistrer dans un autre fichier de préférences:

  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 deuxième façon, et celle que je préfère maintenant, est de créer votre propre préférence personnalisée, en étendant EditTextPreference, @ Remplacer les méthodes setText() et getText(), de sorte que setText() chiffre le mot de passe et que getText() renvoie null.

9
Mark

Je sais que c'est un peu de nécromancie, mais vous devriez utiliser Android AccountManager . Il est spécialement conçu pour ce scénario. C'est un peu fastidieux, mais l'une des choses qu'il fait est d'invalider les identifiants locaux si la carte SIM change. Si quelqu'un glisse votre téléphone et y insère une nouvelle carte SIM, vos identifiants ne seront pas compromis.

Cela donne également à l'utilisateur un moyen rapide et facile d'accéder (et éventuellement de supprimer) les informations d'identification stockées pour tout compte qu'il possède sur le périphérique, le tout à partir d'un seul endroit.

SampleSyncAdapter est un exemple qui utilise les informations d'identification du compte stocké.

5
Jon O

D'accord; Cela fait un moment que la réponse est un peu mixte, mais voici quelques réponses communes. J'ai cherché cela comme un fou et il était difficile de construire une bonne réponse

  1. La méthode MODE_PRIVATE est généralement considérée comme étant sûre, si vous supposez que l'utilisateur n'a pas root le périphérique. Vos données sont stockées en texte brut dans une partie du système de fichiers accessible uniquement par le programme d'origine. Cela fait saisir le mot de passe avec une autre application sur un appareil enraciné facile. Encore une fois, voulez-vous prendre en charge les périphériques enracinés?

  2. AES reste le meilleur cryptage que vous puissiez faire. N'oubliez pas de rechercher ceci si vous démarrez une nouvelle implémentation si cela fait longtemps que je n'ai pas posté ceci. Le plus gros problème avec ceci est "Que faire avec la clé de cryptage?"

Nous sommes donc maintenant au "Que faire avec la clé?" portion. C'est la partie difficile. Obtenir la clé s'avère être pas si mal. Vous pouvez utiliser une fonction de dérivation de clé pour prendre un mot de passe et en faire une clé assez sécurisée. Vous vous posez des questions comme "combien de passes faites-vous avec PKFDF2?", Mais c'est un autre sujet

  1. Idéalement, vous stockez la clé AES hors de l'appareil. Vous devez trouver un bon moyen de récupérer la clé du serveur en toute sécurité, de manière fiable et sécurisée.

  2. Vous avez une séquence de connexion (même la séquence de connexion originale que vous avez pour l'accès à distance). Vous pouvez effectuer deux exécutions de votre générateur de clé avec le même mot de passe. La façon dont cela fonctionne est que vous dérivez la clé deux fois avec un nouveau sel et un nouveau vecteur d'initialisation sécurisé. Vous stockez l'un de ces mots de passe générés sur le périphérique et vous utilisez le deuxième mot de passe comme clé AES.

Lorsque vous vous connectez, vous re-dérivez la clé sur la connexion locale et vous la comparez à la clé stockée. Une fois que cela est fait, vous utilisez la clé dérivée n ° 2 pour AES.

  1. En utilisant l'approche "généralement sans danger", vous chiffrez les données à l'aide d'AES et stockez la clé dans MODE_PRIVATE. Ceci est recommandé par un article de blog Android récent. Pas incroyablement sécurisé, mais bien meilleur pour certaines personnes que le texte brut

Vous pouvez faire beaucoup de variations de ceux-ci. Par exemple, au lieu d’une séquence de connexion complète, vous pouvez effectuer un rapide PIN (dérivé). Le PIN rapide n'est peut-être pas aussi sécurisé qu'une séquence de connexion complète, mais il est plusieurs fois plus sécurisé que le texte brut

5
Joe Plante

Je jetterai mon chapeau sur le ring juste pour parler de la sécurisation des mots de passe en général sur Android. Sur Android, le fichier binaire du périphérique doit être considéré comme compromis. Il en va de même pour toute application finale sous le contrôle direct de l'utilisateur. Conceptuellement, un pirate informatique pourrait utiliser l'accès nécessaire au fichier binaire pour le décompiler et extraire vos mots de passe cryptés, etc.

À ce titre, il y a deux suggestions que j'aimerais formuler si la sécurité est une préoccupation majeure pour vous:

1) Ne stockez pas le mot de passe actuel. Stockez un jeton d'accès accordé et utilisez le jeton d'accès et la signature du téléphone pour authentifier la session côté serveur. L’avantage de ceci est que vous pouvez donner une durée limitée au jeton, vous ne compromettez pas le mot de passe original et vous disposez d’une bonne signature que vous pouvez utiliser pour corréler au trafic ultérieurement (pour vérifier par exemple les tentatives d’intrusion et invalider le mot de passe). jeton le rendant inutile).

2) Utiliser une authentification à 2 facteurs. Cela peut être plus gênant et intrusif, mais inévitable pour certaines situations de conformité.

5
dcgregorya

Vous pouvez également consulter cette petite bibliothèque contenant les fonctionnalités que vous avez mentionnées.

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

C'est semblable à certains des autres approches ici. L'espoir aide :)

2
Marcell

Ceci est une réponse supplémentaire pour ceux qui arrivent ici en fonction du titre de la question (comme je l'ai fait) et n'a pas besoin de traiter les problèmes de sécurité liés à la sauvegarde des mots de passe.

Comment utiliser les préférences partagées

Les paramètres utilisateur sont généralement enregistrés localement dans Android à l'aide de SharedPreferences avec une paire clé-valeur. Vous utilisez la clé String pour enregistrer ou rechercher la valeur associée.

Écrire dans les préférences partagées

String key = "myInt";
int valueToSave = 10;

SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(key, valueToSave).commit();

Utilisez apply() au lieu de commit() pour enregistrer en arrière-plan plutôt qu'immédiatement.

Lecture à partir de préférences partagées

String key = "myInt";
int defaultValue = 0;

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

La valeur par défaut est utilisée si la clé n'est pas trouvée.

Remarques

  • Plutôt que d'utiliser une chaîne de clé locale à plusieurs endroits comme je l'ai fait ci-dessus, il serait préférable d'utiliser une constante dans un seul endroit. Vous pouvez utiliser quelque chose comme ceci en haut de l'activité Paramètres:

    final static String PREF_MY_INT_KEY = "myInt";
    
  • J'ai utilisé un int dans mon exemple, mais vous pouvez également utiliser putString(), putBoolean(), getString(), getBoolean(), etc.

  • Voir le documentation pour plus de détails.
  • Il existe plusieurs manières d'obtenir des références partagées. Voir cette réponse pour savoir ce qu'il faut rechercher.
2
Suragch

Cette réponse est basée sur une approche suggérée par Mark. Une version personnalisée de la classe EditTextPreference est créée. Elle convertit le texte en clair affiché dans la vue en une version chiffrée du mot de passe stocké dans le stockage des préférences. 

Comme l'ont souligné la plupart des personnes ayant répondu sur ce fil, cette technique n'est pas très sécurisée, bien que le degré de sécurité dépend en partie du code de cryptage/décryptage utilisé. Mais c'est assez simple et pratique, et va contrarier la plupart des espions occasionnels. 

Voici le code de la classe personnalisée 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 restoreValue, Object defaultValue) {
        if (restoreValue)
            getEditText().setText(getText());
        else
            super.onSetInitialValue(restoreValue, defaultValue);
    }
}

Cela montre comment il peut être utilisé - c’est le fichier "items" qui contrôle l’affichage des préférences. Notez qu'il contient trois vues ordinaires EditTextPreference et une des vues personnalisées EditPasswordPreference.

<PreferenceScreen xmlns:Android="http://schemas.Android.com/apk/res/Android">

    <EditTextPreference
        Android:key="@string/useraccountname_key"
        Android:title="@string/useraccountname_title"
        Android:summary="@string/useraccountname_summary"
        Android:defaultValue="@string/useraccountname_default"
        />

    <com.Merlinia.OutBack_Client.EditPasswordPreference
        Android:key="@string/useraccountpassword_key"
        Android:title="@string/useraccountpassword_title"
        Android:summary="@string/useraccountpassword_summary"
        Android:defaultValue="@string/useraccountpassword_default"
        />

    <EditTextPreference
        Android:key="@string/outbackserverip_key"
        Android:title="@string/outbackserverip_title"
        Android:summary="@string/outbackserverip_summary"
        Android:defaultValue="@string/outbackserverip_default"
        />

    <EditTextPreference
        Android:key="@string/outbackserverport_key"
        Android:title="@string/outbackserverport_title"
        Android:summary="@string/outbackserverport_summary"
        Android:defaultValue="@string/outbackserverport_default"
        />

</PreferenceScreen>

Quant au cryptage/décryptage proprement dit, cela reste un exercice pour le lecteur. J'utilise actuellement du code basé sur cet article http://zenu.wordpress.com/2011/09/21/aes-128bit-cross-platform-Java-and-c-encryption-compatibility/ , bien qu'avec des valeurs différentes pour la clé et le vecteur d'initialisation.

1
RenniePet

Tout d'abord, je pense que les données de l'utilisateur ne doivent pas être stockées sur le téléphone. S'il est indispensable de stocker des données quelque part sur le téléphone, il convient de les chiffrer dans les données privées des applications. La sécurité des informations d'identification des utilisateurs doit être la priorité de l'application.

Les données sensibles doivent être stockées de manière sécurisée ou pas du tout. En cas de perte du périphérique ou d'infection par un logiciel malveillant, les données stockées de manière non sécurisée peuvent être compromises.

1
Gauraw Yadav

J'utilise le magasin de clés Android pour chiffrer le mot de passe à l'aide de RSA en mode ECB, puis l'enregistrer dans les références partagées.

Lorsque je veux récupérer le mot de passe, je lis celui chiffré dans les références partagées et le déchiffre à l'aide du magasin de clés.

Avec cette méthode, vous générez une paire de clés publique/privée dans laquelle la clé privée est stockée et gérée en toute sécurité par Android.

Voici un lien sur la façon de le faire: Tutorial Android KeyStore

0
Braveblacklion