热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

android开发分享在Android应用程序中存储用户设置的最合适的方式是什么?

我正在创build一个使用用户T

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

我正在尝试使用共享首选项,但不知道这是否是最佳解决scheme。

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

    一般来说,SharedPreferences是存储首选项的最佳select,所以通常我会推荐这种方法来保存应用程序和用户设置。

    这里唯一值得关注的是你正在保存的东西。 密码始终是一个棘手的事情要存储,我会特别谨慎的存储他们作为明文。 Android体系结构是这样的,你的应用程序的SharedPreferences被沙箱化,以防止其他应用程序访问这些值,所以这里有一些安全性,但是物理访问手机可能允许访问这些值。

    如果可能的话,我会考虑修改服务器以使用协商令牌来提供访问权限,如OAuth 。 另外,您可能需要构build某种types的encryption存储,尽pipe这不是微不足道的。 至less,确保在将密码写入磁盘之前对密码进行encryption。

    我同意Reto和fiXedd。 客观地说,在SharedPreferences中对密码进行encryption没有太多的意义,因为任何攻击者都可以访问你的应用程序的二进制文件,因此也可以使用这些密钥来解密密码。

    然而,话虽如此,似乎有一个宣传倡议继续确定移动应用程序,在SharedPreferences明文存储他们的密码,并对这些应用程序的光照不利。 有关示例,请参阅 。

    虽然我们需要更多地关注安全问题,但我认为对这一问题的这种关注实际上并没有显着增加我们的整体安全。 然而,感觉是一样的,这里有一个解决scheme来encryption你放置在SharedPreferences中的数据。

    只需将自己的SharedPreferences对象封装在这个对象中,任何读取/写入的数据都将被自动encryption和解密。 例如。

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

    关于在Android Activity中存储单个首选项的最简单的方法是做这样的事情:

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

    如果您担心这些密码的安全性,那么在存储密码之前,您总是可以encryption密码。

    使用Richard提供的代码片段,您可以在保存密码之前encryption密码。 然而,首选项API并不提供简单的方法来截取值并对其进行encryption – 您可以通过OnPreferenceChange侦听器来阻止它被保存,理论上可以通过preferenceChangeListener对其进行修改,但是会导致无限循环。

    我曾经build议增加一个“隐藏”的偏好,以完成这一点。 这绝对不是最好的方法。 我将介绍另外两个我认为更可行的选项。

    首先,最简单的是,在preferenceChangeListener中,可以获取input的值,对其进行encryption,然后将其保存到备用首选项文件中:

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

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

    好的; 已经有一段时间了,因为答案是混合的,但是这里有一些常见的答案。 我研究这个很疯狂,很难build立一个好的答案

    所以,现在我们在“关键是怎么做?” 一部分。 这是困难的部分。 获得钥匙原来并不是那么糟糕。 您可以使用密钥派生函数来获取一些密码,并使其成为一个非常安全的密钥。 你确实遇到了“你用PKFDF2做多less次传球?”这样的问题,但这是另一个话题

    login时,您将重新派生本地login的密钥并将其与存储的密钥进行比较。 一旦完成,您使用派生密钥#2进行AES。

    你可以做很多这些变化。 例如,而不是一个完整的login序列,你可以做一个快速的PIN(派生)。 快速PIN可能不像完整的login序列那么安全,但是它比纯文本多得多

    我会把我的帽子扔进戒指,只是为了谈论在Android安全密码。 在Android上,设备二进制文件应该被认为是被攻破的 – 对于直接用户控制的任何terminal应用程序来说,这是相同的。 从概念上讲,黑客可以使用对二进制文件的必要访问来对其进行反编译并根据您的encryption密码等进行反编译。

    因此,如果安全性是您的主要担忧,那么我想要抛出两条build议:

    1)不要存储实际的密码。 存储授予的访问令牌,并使用访问令牌和电话的签名来validation会话服务器端。 这样做的好处是,您可以使令牌的持续时间有限,不会损害原始密码,并且您可以使用以后用于关联stream量的良好签名(例如,检查入侵企图并使无效令牌渲染它没用)。

    2)利用2因素authentication。 这可能更令人讨厌和侵入,但对于一些遵守情况是不可避免的。

    我知道这有一点necromancy,但你应该使用Android AccountManager 。 这是专门为这种情况build立的。 这有点麻烦,但是它所做的一件事情是,如果SIM卡改变,则本地凭证无效,所以如果有人在你的手机上刷了一个新的SIM,那么你的凭证就不会受到威胁。

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

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

    你也可以看看这个小小的库,包含你提到的function。

    这和其他的一些问题类似。 希望帮助:)

    这个答案是基于马克build议的方法。 创buildEditTextPreference类的自定义版本,该视图在视图中显示的纯文本与存储在首选项存储中的密码的encryption版本之间来回转换。

    正如大多数人在这个线程中已经指出的那样,这不是一个非常安全的技术,虽然安全程度部分取决于所使用的encryption/解密代码。 但它相当简单和方便,并将阻止大多数随意窥探。

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

    这显示了如何使用它 – 这是驱动首选项显示的“项目”文件。 注意它包含三个普通的EditTextPreference视图和一个自定义的EditPasswordPreference视图。

           

    至于实际的encryption/解密,这是留给读者的一个练习。 我目前正在使用一些基于这篇文章的代码 ,虽然有不同的值为密钥和初始化向量。

    首先,我认为用户的数据不应该存储在手机上,如果必须在手机上某处存储数据,则应该使用应用程序的私人数据进行encryption。 用户证书的安全性应该是应用程序的优先级。

    敏感数据应该安全地存储或根本不存储。 在设备丢失或恶意软件感染的情况下,不安全地存储的数据可能被损害。

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

    如何使用共享首选项

    用户设置通常使用带有键值对的SharedPreferences在本地保存。 您使用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); 

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

    笔记

    你需要使用sqlite,安全apit来存储密码。 这里是最好的例子,它存储密码, – 密码保护。 这里是链接的来源和解释 –

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

      以上就是android开发分享在Android应用程序中存储用户设置的最合适的方式是什么?相关内容,想了解更多android开发(异常处理)及android游戏开发关注(编程笔记)。


      推荐阅读
      • 如何使用Java获取服务器硬件信息和磁盘负载率
        本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
      • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
      • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
        本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
      • CF:3D City Model(小思维)问题解析和代码实现
        本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
      • 高质量SQL书写的30条建议
        本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
      • Nginx使用AWStats日志分析的步骤及注意事项
        本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
      • Monkey《大话移动——Android与iOS应用测试指南》的预购信息发布啦!
        Monkey《大话移动——Android与iOS应用测试指南》的预购信息已经发布,可以在京东和当当网进行预购。感谢几位大牛给出的书评,并呼吁大家的支持。明天京东的链接也将发布。 ... [详细]
      • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
      • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
        本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
      • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
      • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
        本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
      • PHP设置MySQL字符集的方法及使用mysqli_set_charset函数
        本文介绍了PHP设置MySQL字符集的方法,详细介绍了使用mysqli_set_charset函数来规定与数据库服务器进行数据传送时要使用的字符集。通过示例代码演示了如何设置默认客户端字符集。 ... [详细]
      • 计算机存储系统的层次结构及其优势
        本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
      • 也就是|小窗_卷积的特征提取与参数计算
        篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
      • JDK源码学习之HashTable(附带面试题)的学习笔记
        本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
      author-avatar
      三个人999
      这个家伙很懒,什么也没留下!
      PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
      Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有