What is the most appropriate way to store user settings in Android application

后端 未结 14 2054
隐瞒了意图╮
隐瞒了意图╮ 2020-11-22 05:38

I am creating an application which connects to the server using username/password and I would like to enable the option \"Save password\" so the user wouldn\'t have to type

相关标签:
14条回答
  • 2020-11-22 06:02

    About the simplest way to store a single preference in an Android Activity is to do something like this:

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

    If you're worried about the security of these then you could always encrypt the password before storing it.

    0 讨论(0)
  • 2020-11-22 06:03

    I agree with Reto and fiXedd. Objectively speaking it doesn't make a lot of sense investing significant time and effort into encrypting passwords in SharedPreferences since any attacker that has access to your preferences file is fairly likely to also have access to your application's binary, and therefore the keys to unencrypt the password.

    However, that being said, there does seem to be a publicity initiative going on identifying mobile applications that store their passwords in cleartext in SharedPreferences and shining unfavorable light on those applications. See http://blogs.wsj.com/digits/2011/06/08/some-top-apps-put-data-at-risk/ and http://viaforensics.com/appwatchdog for some examples.

    While we need more attention paid to security in general, I would argue that this sort of attention on this one particular issue doesn't actually significantly increase our overall security. However, perceptions being as they are, here's a solution to encrypt the data you place in SharedPreferences.

    Simply wrap your own SharedPreferences object in this one, and any data you read/write will be automatically encrypted and decrypted. eg.

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

    Here's the code for the class:

    /**
     * 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);
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 06:06

    I use the Android KeyStore to encrypt the password using RSA in ECB mode and then save it in the SharedPreferences.

    When I want the password back I read the encrypted one from the SharedPreferences and decrypt it using the KeyStore.

    With this method you generate a public/private Key-pair where the private one is safely stored and managed by Android.

    Here is a link on how to do this: Android KeyStore Tutorial

    0 讨论(0)
  • 2020-11-22 06:08

    Okay; it's been a while since the answer is kind-of mixed, but here's a few common answers. I researched this like crazy and it was hard to build a good answer

    1. The MODE_PRIVATE method is considered generally safe, if you assume that the user didn't root the device. Your data is stored in plain text in a part of the file system that can only be accessed by the original program. This makings grabbing the password with another app on a rooted device easy. Then again, do you want to support rooted devices?

    2. AES is still the best encryption you can do. Remember to look this up if you are starting a new implementation if it's been a while since I posted this. The largest issue with this is "What to do with the encryption key?"

    So, now we are at the "What to do with the key?" portion. This is the hard part. Getting the key turns out to be not that bad. You can use a key derivation function to take some password and make it a pretty secure key. You do get into issues like "how many passes do you do with PKFDF2?", but that's another topic

    1. Ideally, you store the AES key off the device. You have to figure out a good way to retrieve the key from the server safely, reliably, and securely though

    2. You have a login sequence of some sort (even the original login sequence you do for remote access). You can do two runs of your key generator on the same password. How this works is that you derive the key twice with a new salt and a new secure initialization vector. You store one of those generated passwords on the device, and you use the second password as the AES key.

    When you log in, you re-derive the key on the local login and compare it to the stored key. Once that is done, you use derive key #2 for AES.

    1. Using the "generally safe" approach, you encrypt the data using AES and store the key in MODE_PRIVATE. This is recommended by a recent-ish Android blog post. Not incredibly secure, but way better for some people over plain text

    You can do a lot of variations of these. For example, instead of a full login sequence, you can do a quick PIN (derived). The quick PIN might not be as secure as a full login sequence, but it's many times more secure than plain text

    0 讨论(0)
  • 2020-11-22 06:11

    I know this is a little bit of necromancy, but you should use the Android AccountManager. It's purpose-built for this scenario. It's a little bit cumbersome but one of the things it does is invalidate the local credentials if the SIM card changes, so if somebody swipes your phone and throws a new SIM in it, your credentials won't be compromised.

    This also gives the user a quick and easy way to access (and potentially delete) the stored credentials for any account they have on the device, all from one place.

    SampleSyncAdapter is an example that makes use of stored account credentials.

    0 讨论(0)
  • 2020-11-22 06:14

    First of all I think User's data shouldn't be stored on phone, and if it is must to store data somewhere on the phone it should be encrypted with in the apps private data. Security of users credentials should be the priority of the application.

    The sensitive data should be stored securely or not at all. In the event of a lost device or malware infection, data stored insecurely can be compromised.

    0 讨论(0)
提交回复
热议问题