Java 在Android应用程序中存储用户设置最合适的方式是什么

Java 在Android应用程序中存储用户设置最合适的方式是什么,java,android,encryption,preferences,credentials,Java,Android,Encryption,Preferences,Credentials,我正在创建一个使用用户名/密码连接到服务器的应用程序,我希望启用“保存密码”选项,这样用户就不必在每次应用程序启动时键入密码 我试着用共享的偏好来做,但不确定这是否是最好的解决方案 如果您能就如何在Android应用程序中存储用户值/设置提供任何建议,我将不胜感激。一般来说,SharedReference是存储首选项的最佳选择,因此一般来说,我建议您使用这种方法来保存应用程序和用户设置 这里唯一值得关注的是你在储蓄什么。密码总是一件很难存储的事情,我会特别小心将它们存储为明文。Android体系

我正在创建一个使用用户名/密码连接到服务器的应用程序,我希望启用“保存密码”选项,这样用户就不必在每次应用程序启动时键入密码

我试着用共享的偏好来做,但不确定这是否是最好的解决方案


如果您能就如何在Android应用程序中存储用户值/设置提供任何建议,我将不胜感激。

一般来说,SharedReference是存储首选项的最佳选择,因此一般来说,我建议您使用这种方法来保存应用程序和用户设置

这里唯一值得关注的是你在储蓄什么。密码总是一件很难存储的事情,我会特别小心将它们存储为明文。Android体系结构是这样的:应用程序的SharedReference被沙盒化,以防止其他应用程序访问这些值,因此存在一定的安全性,但对手机的物理访问可能允许访问这些值


如果可能的话,我会考虑修改服务器来使用协商令牌来提供访问。或者,您可能需要构造某种加密存储,尽管这并不简单。至少,在将密码写入磁盘之前,请确保您对密码进行了加密。

您需要使用sqlite安全apit来存储密码。 这里是最好的例子,它存储密码,-passwordsafe。 以下是来源和解释的链接--

关于在Android活动中存储单个首选项的最简单方法是执行以下操作:

Editor e = this.getPreferences(Context.MODE_PRIVATE).edit();
e.putString("password", mPassword);
e.commit();
String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);

SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
    "secret_shared_prefs",
    masterKeyAlias,
    context,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

// use the shared preferences and editor as you normally would
SharedPreferences.Editor editor = sharedPreferences.edit();

如果您担心密码的安全性,那么您可以在存储密码之前对密码进行加密。

使用Richard提供的代码片段,您可以在保存密码之前对密码进行加密。然而,PreferencesAPI并没有提供一种简单的方法来截取并加密该值——您可以通过OnPreferenceChange侦听器阻止保存该值,理论上您可以通过preferenceChangeListener对其进行修改,但这会导致无休止的循环

我之前曾建议添加一个“隐藏”偏好以实现这一点。这绝对不是最好的办法。我将提出另外两个我认为更可行的选择。 首先,最简单的方法是在preferenceChangeListener中,您可以获取输入的值,对其进行加密,然后将其保存到另一个preferences文件中:

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

第二种方法,也是我现在更喜欢的方法,是创建您自己的自定义首选项,扩展EditTextPreference,@覆盖
setText()
getText()
方法,以便
setText()
加密密码,并且
getText()
返回空值。

我同意Reto和fiXedd。客观地说,在SharedReferences中加密密码花费大量时间和精力是没有意义的,因为任何能够访问您的首选项文件的攻击者很可能也能够访问您的应用程序的二进制文件,从而可以访问取消密码加密的密钥

然而,尽管如此,似乎仍有一项宣传活动正在进行,即识别那些将密码以明文形式存储在共享引用中的移动应用程序,并对这些应用程序发出不利的光线。有关一些示例,请参见和

虽然我们需要更多地关注总体安全问题,但我认为,对这一特定问题的关注实际上并没有显著提高我们的总体安全。然而,就目前情况而言,这里有一个解决方案可以加密您放置在SharedReference中的数据

只需将您自己的SharedReferences对象包装在此对象中,您读/写的任何数据都将自动加密和解密。例如

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);
下面是该类的代码:

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

}
/**
*警告,这给人一种虚假的安全感。如果攻击者有足够的权限访问
*获取你的密码存储,那么他几乎肯定有足够的权限获取你的密码
*获取二进制文件并计算出加密密钥。然而,这将防止随意的行为
*调查人员不必获取密码,从而可以防止意外的负面影响
*宣传。
*/
公共类UnclustedSharedReferences实现SharedReferences{
受保护的静态最终字符串UTF8=“utf-8”;
private static final char[]SEKRIT=…;//在此处插入随机密码。
//不要用任何你不想用的东西
//如果有人反编译了,就出去
//你的应用程序。
受保护的共享引用委托;
受保护的语境;
公共模糊SharedReferences(上下文上下文,SharedReferences委托){
this.delegate=委托;
this.context=上下文;
}
公共类编辑器实现SharedReferences.Editor{
受保护的SharedReferences.Editor委托;
公共编辑(){
this.delegate=obclustedSharedReferences.this.delegate.edit();
}
@凌驾
公共编辑器putBoolean(字符串键,布尔值){
delegate.putString(key,encrypt(Boolean.toString(value));
归还这个;
}
@凌驾
公共编辑器putFloat(字符串键、浮点值){
delegate.putString(key,encrypt(Float.toString(value));
归还这个;
}
@凌驾
公共编辑器putInt(字符串键,int值){
delegate.putString(key,encrypt(Integer.toString(value));
归还这个;
}
@凌驾
公共编辑器putLong(字符串键,长值){
delegate.putString(key,encrypt(Long.toString(value));
归还这个;
}
@凌驾
公共编辑器字符串(
<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>
String key = "myInt";
int valueToSave = 10;

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

SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
int savedValue = sharedPref.getInt(key, defaultValue);
  final static String PREF_MY_INT_KEY = "myInt";
implementation 'androidx.security:security-crypto:1.0.0-rc01'
String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);

SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
    "secret_shared_prefs",
    masterKeyAlias,
    context,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

// use the shared preferences and editor as you normally would
SharedPreferences.Editor editor = sharedPreferences.edit();