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

发布于 2024-07-17 05:40:34 字数 164 浏览 7 评论 0 原文

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

我试图使用共享首选项来完成此操作,但不确定这是否是最佳解决方案。

如果有关于如何在 Android 应用程序中存储用户值/设置的建议,我将不胜感激。

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 the password each time the application starts.

I was trying to do it with Shared Preferences but am not sure if this is the best solution.

I would appreciate any suggestion on how to store user values/settings in Android application.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

梦里°也失望 2024-07-24 05:40:34

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

这里唯一关心的是你要节省什么。 密码的存储总是一件棘手的事情,我会特别谨慎地将它们存储为明文。 Android 架构中,您的应用程序的 SharedPreferences 被沙箱化,以防止其他应用程序能够访问这些值,因此存在一定的安全性,但对手机的物理访问可能会允许访问这些值。

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

In general SharedPreferences are your best bet for storing preferences, so in general I'd recommend that approach for saving application and user settings.

The only area of concern here is what you're saving. Passwords are always a tricky thing to store, and I'd be particularly wary of storing them as clear text. The Android architecture is such that your application's SharedPreferences are sandboxed to prevent other applications from being able to access the values so there's some security there, but physical access to a phone could potentially allow access to the values.

If possible I'd consider modifying the server to use a negotiated token for providing access, something like OAuth. Alternatively you may need to construct some sort of cryptographic store, though that's non-trivial. At the very least, make sure you're encrypting the password before writing it to disk.

转角预定愛 2024-07-24 05:40:34

我同意 Reto 和 fiXedd 的观点。 客观地说,投入大量时间和精力来加密 SharedPreferences 中的密码并没有多大意义,因为任何有权访问您的首选项文件的攻击者很可能也有权访问您的应用程序的二进制文件,因此解密密钥密码。

然而,话虽如此,似乎确实存在一项宣传活动,旨在识别将密码以明文形式存储在 SharedPreferences 中的移动应用程序,并对这些应用程序发出不利的声音。 请参阅 http://blogs .wsj.com/digits/2011/06/08/some-top-apps-put-data-at-risk/http://viaforensics.com/appwatchdog 获取一些示例。

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

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

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

}

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

}
旧时光的容颜 2024-07-24 05:40:34

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

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

如果您担心这些首选项的安全性,那么您始终可以在存储密码之前对其进行加密。

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.

最丧也最甜 2024-07-24 05:40:34

使用理查德提供的代码片段,您可以在保存密码之前对其进行加密。 然而,首选项 API 并没有提供一种简单的方法来拦截该值并对其进行加密 - 您可以通过 OnPreferenceChange 侦听器阻止保存它,理论上您可以通过首选项ChangeListener 修改它,但这会导致无限循环。

我之前曾建议添加“隐藏”首选项以实现此目的。 这绝对不是最好的方法。 我将提出另外两个我认为更可行的选择。

首先,最简单的是在首选项更改监听器中,您可以获取输入的值,对其进行加密,然后将其保存到备用首选项文件中:

  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,@Override'ing setText()getText() 方法,以便 setText() 加密密码,并且 getText() 返回 null。

Using the snippet provided by Richard, you can encrypt the password before saving it. The preferences API however doesn't provide an easy way to intercept the value and encrypt it - you can block it being saved via an OnPreferenceChange listener, and you theoretically could modify it through a preferenceChangeListener, but that results in an endless loop.

I had earlier suggested adding a "hidden" preference in order to accomplish this. It's definitely not the best way. I'm going to present two other options that I consider to be more viable.

First, the simplest, is in a preferenceChangeListener, you can grab the entered value, encrypt it, and then save it to an alternative preferences file:

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

The second way, and the way I now prefer, is to create your own custom preference, extending EditTextPreference, @Override'ing the setText() and getText() methods, so that setText() encrypts the password, and getText() returns null.

梦中的蝴蝶 2024-07-24 05:40:34

好的; 已经有一段时间了,答案有点混杂,但这里有一些常见的答案。 我疯狂地研究了这个问题,很难找到一个好的答案

  1. 如果您假设用户没有 root 设备,那么 MODE_PRIVATE 方法通常被认为是安全的。 您的数据以纯文本形式存储在文件系统的一部分中,只能由原始程序访问。 这使得在 root 设备上使用另一个应用程序轻松获取密码。 话又说回来,您想支持已取得 root 权限的设备吗?

  2. AES 仍然是最好的加密方式。 如果我发布这篇文章已经有一段时间了,如果您要开始新的实施,请记住查看此内容。 最大的问题是“如何处理加密密钥?”

那么,现在我们面临“如何处理钥匙?”的问题。 部分。 这是最难的部分。 事实证明,拿到钥匙并不是那么糟糕。 您可以使用密钥派生函数获取一些密码并使其成为非常安全的密钥。 您确实会遇到诸如“您使用 PKFDF2 进行了多少次传递?”之类的问题,但这是另一个主题

  1. 理想情况下,您将 AES 密钥存储在设备之外。 您必须找到一种安全、可靠、安全地从服务器检索密钥的好方法,但

  2. 尽管您有某种登录序列(甚至是用于远程访问的原始登录序列), 。 您可以使用同一个密码运行两次密钥生成器。 其工作原理是,您使用新的盐和新的安全初始化向量两次派生密钥。 您将生成的密码之一存储在设备上,并使用第二个密码作为 AES 密钥。

当您登录时,您在本地登录上重新派生密钥并将其与存储的密钥进行比较。 完成后,您可以使用 AES 的派生密钥 #2。

  1. 使用“一般安全”方法,您可以使用 AES 加密数据并将密钥存储在 MODE_PRIVATE 中。 这是最近一篇 Android 博客文章推荐的。 不是非常安全,但对于某些人来说比纯文本更好。

您可以对这些进行很多变体。 例如,您可以执行快速 PIN(派生),而不是完整的登录序列。 快速 PIN 可能不如完整的登录序列安全,但它比纯文本安全许多倍

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

月亮坠入山谷 2024-07-24 05:40:34

我知道这有点死灵术,但你应该使用 Android AccountManager。 它是专门针对这种情况而构建的。 这有点麻烦,但它所做的事情之一是,如果 SIM 卡发生变化,本地凭据就会失效,因此,如果有人刷了您的手机并放入了新的 SIM 卡,您的凭据不会受到损害。

这还为用户提供了一种快速、简单的方法来访问(并可能删除)他们在设备上拥有的任何帐户的存储凭据,所有这些都从一个地方进行。

SampleSyncAdapter 是一个使用存储的帐户凭据的示例。

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.

枫以 2024-07-24 05:40:34

我将亲自谈谈 Android 上的一般密码保护。 在 Android 上,设备二进制文件应被视为受到损害 - 这对于任何直接由用户控制的最终应用程序都是相同的。 从概念上讲,黑客可以使用对二进制文件的必要访问权限来反编译它并根除您的加密密码等。

因此,如果安全性是您主要关心的问题,我想提出两个建议:

1)不要' t 存储实际密码。 存储授予的访问令牌,并使用访问令牌和电话的签名对会话服务器端进行身份验证。 这样做的好处是,您可以使令牌具有有限的持续时间,您不会泄露原始密码,并且您拥有一个良好的签名,您可以使用它来与以后的流量关联(例如,检查入侵尝试并使令牌无效)。令牌使其无用)。

2) 使用两因素身份验证。 这可能更烦人且更具侵入性,但对于某些合规情况来说是不可避免的。

I'll throw my hat into the ring just to talk about securing passwords in general on Android. On Android, the device binary should be considered compromised - this is the same for any end application which is in direct user control. Conceptually, a hacker could use the necessary access to the binary to decompile it and root out your encrypted passwords and etc.

As such there's two suggestions I'd like to throw out there if security is a major concern for you:

1) Don't store the actual password. Store a granted access token and use the access token and the signature of the phone to authenticate the session server-side. The benefit to this is that you can make the token have a limited duration, you're not compromising the original password and you have a good signature that you can use to correlate to traffic later (to for instance check for intrusion attempts and invalidate the token rendering it useless).

2) Utilize 2 factor authentication. This may be more annoying and intrusive but for some compliance situations unavoidable.

远昼 2024-07-24 05:40:34

这是对那些根据问题标题到达这里的人(就像我一样)的补充答案,不需要处理与保存密码相关的安全问题。

如何使用共享首选项

用户设置是通常使用 SharedPreferences 和键值对在 Android 中本地保存。 您可以使用String键来保存或查找关联的值。

写入共享首选项

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

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

使用 apply() 而不是 commit() 在后台保存而不是立即保存。

从共享首选项中读取

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

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

如果未找到密钥,则使用默认值。

注释

  • 与其像上面那样在多个位置使用本地键字符串,不如在单个位置使用常量。 您可以在设置活动的顶部使用类似的内容:

     最终静态字符串 PREF_MY_INT_KEY = "myInt"; 
      
  • 我在示例中使用了 int,但您也可以使用 putString()putBoolean()getString()getBoolean()

  • 请参阅文档了解更多详细信息。

  • 有多种方法可以获取 SharedPreferences。 请参阅此答案了解需要注意的事项。

This is a supplemental answer for those arriving here based on the question title (like I did) and don't need to deal with the security issues related to saving passwords.

How to use Shared Preferences

User settings are generally saved locally in Android using SharedPreferences with a key-value pair. You use the String key to save or look up the associated value.

Write to Shared Preferences

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

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

Use apply() instead of commit() to save in the background rather than immediately.

Read from Shared Preferences

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

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

The default value is used if the key isn't found.

Notes

  • Rather than using a local key String in multiple places like I did above, it would be better to use a constant in a single location. You could use something like this at the top of your settings activity:

      final static String PREF_MY_INT_KEY = "myInt";
    
  • I used an int in my example, but you can also use putString(), putBoolean(), getString(), getBoolean(), etc.

  • See the documentation for more details.

  • There are multiple ways to get SharedPreferences. See this answer for what to look out for.

南烟 2024-07-24 05:40:34

您还可以查看这个小库,其中包含您提到的功能。

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

它类似于这里还有一些其他方法。 希望有帮助:)

You can also check out this little lib, containing the functionality you mention.

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

It is similar to some of the other aproaches here. Hope helps :)

笑着哭最痛 2024-07-24 05:40:34

这个答案基于马克建议的方法。 创建了 EditTextPreference 类的自定义版本,该类在视图中看到的纯文本和首选项存储中存储的密码的加密版本之间来回转换。

正如大多数在此线程上回答的人所指出的那样,这不是一种非常安全的技术,尽管安全程度部分取决于所使用的加密/解密代码。 但它相当简单和方便,并且会阻止大多数随意的窥探。

以下是自定义 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);
    }
}

这显示了如何使用它 - 这是驱动首选项显示的“items”文件。 请注意,它包含三个普通的 EditTextPreference 视图和一个自定义的 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>

至于实际的加密/解密,留给读者练习。 我目前正在使用基于本文的一些代码 http://zenu.wordpress.com/2011/09/21/aes-128bit-cross-platform-java-and-c-encryption-compatibility/,尽管具有不同的值为密钥和初始化向量。

This answer is based on a suggested approach by Mark. A custom version of the EditTextPreference class is created which converts back and forth between the plain text seen in the view and an encrypted version of the password stored in the preferences storage.

As has been pointed out by most who have answered on this thread, this is not a very secure technique, although the degree of security depends partly on the encryption/decryption code used. But it's fairly simple and convenient, and will thwart most casual snooping.

Here is the code for the custom EditTextPreference class:

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

This shows how it can be used - this is the "items" file that drives the preferences display. Note it contains three ordinary EditTextPreference views and one of the custom EditPasswordPreference views.

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

As for the actual encryption/decryption, that is left as an exercise for the reader. I'm currently using some code based on this article http://zenu.wordpress.com/2011/09/21/aes-128bit-cross-platform-java-and-c-encryption-compatibility/, although with different values for the key and the initialization vector.

ㄖ落Θ余辉 2024-07-24 05:40:34

首先,我认为用户的数据不应该存储在手机上,如果必须将数据存储在手机上的某个位置,则应该使用应用程序的私人数据进行加密。 用户凭证的安全性应该是应用程序的首要任务。

敏感数据应安全存储或根本不存储。 如果设备丢失或恶意软件感染,不安全存储的数据可能会受到损害。

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.

零時差 2024-07-24 05:40:34

我使用Android KeyStore在ECB模式下使用RSA对密码进行加密,然后将其保存在SharedPreferences中。

当我想要取回密码时,我从 SharedPreferences 中读取加密的密码并使用 KeyStore 对其进行解密。

使用此方法,您可以生成一个公钥/私钥对,其中私钥由 Android 安全地存储和管理。

以下是有关如何执行此操作的链接: Android密钥库教程

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

草莓味的萝莉 2024-07-24 05:40:34

正如其他人已经指出的那样,您通常可以使用 SharedPreferences,但如果您想存储加密的数据,那就有点不方便了。 幸运的是,现在有一种更简单、更快捷的方法来加密数据,因为有一个 SharedPreferences 的实现可以加密键和值。 您可以在 Android JetPack Security 中使用 EncryptedSharedPreferences

只需将 AndroidX Security 添加到您的 build.gradle 中即可:

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

查看更多详细信息:https://android-developers.googleblog.com/2020/02/data-encryption-on-android-with-jetpack.html

官方文档: https://developer.android.com/reference/androidx/security/crypto/加密共享首选项

As others already pointed out you can use SharedPreferences generally but if you would like to store data encrypted it's a bit inconvenient. Fortunately, there is an easier and quicker way to encrypt data now since there is an implementation of SharedPreferences that encrypts keys and values. You can use EncryptedSharedPreferences in Android JetPack Security.

Just add AndroidX Security into your build.gradle:

implementation 'androidx.security:security-crypto:1.0.0-rc01'

And you can use it like this:

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

See more details: https://android-developers.googleblog.com/2020/02/data-encryption-on-android-with-jetpack.html

Official docs: https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences

仲春光 2024-07-24 05:40:34

我就是这样做的。

这在严格模式下不会给出错误。

public class UserPreferenceUtil
{

    private static final String  THEME = "THEME";
    private static final String  LANGUAGE = "LANGUAGE";

    public static String getLanguagePreference(Context context)
    {

        String lang = getPreferenceByKey(context,LANGUAGE);

        if( lang==null || "System".equalsIgnoreCase(lang))
        {
            return null;
        }


        return lang;
    }

    public static void saveLanguagePreference(Context context,String value)
    {
        savePreferenceKeyValue(context, LANGUAGE,value);
    }

    public static String getThemePreference(Context context)
    {

        return getPreferenceByKey(context,THEME);
    }

    public static void saveThemePreference(Context context, String value)
    {
        savePreferenceKeyValue(context,THEME,value);

    }

    public static String getPreferenceByKey(Context context, String preferenceKey )
    {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);

        String value = sharedPreferences.getString(preferenceKey, null);

        return value;
    }

    private static void savePreferenceKeyValue(Context context, String preferenceKey, String value)
    {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString(preferenceKey,value);
        editor.apply();

    }


}

我的应用程序不需要密码。 然而,我不会保存密码或加密密码,而是保存单向哈希。 当用户登录时,我将以相同的方式对输入进行哈希处理,并将其与存储的哈希值进行匹配。

This is how I am doing it.

This does not give errors in strict mode.

public class UserPreferenceUtil
{

    private static final String  THEME = "THEME";
    private static final String  LANGUAGE = "LANGUAGE";

    public static String getLanguagePreference(Context context)
    {

        String lang = getPreferenceByKey(context,LANGUAGE);

        if( lang==null || "System".equalsIgnoreCase(lang))
        {
            return null;
        }


        return lang;
    }

    public static void saveLanguagePreference(Context context,String value)
    {
        savePreferenceKeyValue(context, LANGUAGE,value);
    }

    public static String getThemePreference(Context context)
    {

        return getPreferenceByKey(context,THEME);
    }

    public static void saveThemePreference(Context context, String value)
    {
        savePreferenceKeyValue(context,THEME,value);

    }

    public static String getPreferenceByKey(Context context, String preferenceKey )
    {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);

        String value = sharedPreferences.getString(preferenceKey, null);

        return value;
    }

    private static void savePreferenceKeyValue(Context context, String preferenceKey, String value)
    {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString(preferenceKey,value);
        editor.apply();

    }


}

My app does not need a password. However, rather than saving passwords or encrypted passwords, I would save a one-way hash. When the user logs in, I will hash the input the same way and match it with the stored hash.

坠似风落 2024-07-24 05:40:34

您需要使用 sqlite、安全 API 来存储密码。
这是存储密码的最佳示例——passwordsafe。
这是来源和解释的链接——
http://code.google.com/p/android-passwordsafe/

you need to use the sqlite, security apit to store the passwords.
here is best example, which stores passwords, -- passwordsafe.
here is link for the source and explanation --
http://code.google.com/p/android-passwordsafe/

总攻大人 2024-07-24 05:40:34

共享首选项是存储应用程序数据的最简单方法。 但任何人都可以通过应用程序管理器清除我们共享的首选项数据。所以我认为这对我们的应用程序来说并不完全安全。

shared preferences is easiest way to store our application data. but it is possible that anyone can clear our shared preferences data through application manager.so i don't think it is completely safe for our application.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文