【问题标题】:Best option to store username and password in android app在安卓应用中存储用户名和密码的最佳选择
【发布时间】:2012-03-03 05:34:46
【问题描述】:

我正在开发一个 Android 应用,用户需要在其中登录才能执行操作。但大多数在安卓手机上,人们使用“让我登录”,在这种情况下,我必须保持 UsernamePassword 在我的应用程序中。我应该使用SharedPreferences,还是 SQLite 数据库,或者还有其他我可以使用的东西。
我怎样才能使它安全?

【问题讨论】:

标签: android android-sqlite sharedpreferences android-preferences


【解决方案1】:

Google 提供了AccountManager 的机制。这是用于创建帐户的标准机制。然后将登录数据存储在 Android 认为合适的位置,例如如果设备提供安全区域,则将使用它。当然,root 设备仍然是一个问题,但至少这是使用标准机制,而不是自创的东西,它也没有从 Android 系统更新中受益。这还有一个好处是该帐户在 Android 设置中列出,另一个积极的功能是“同步”功能,它使帐户能够在应用程序和后端系统之间同步数据,因此您获得的不仅仅是登录。

除此之外,使用用户名和密码不再是最佳选择。现在所有更好的应用程序都在使用 OAuth。这里值得注意的区别是密码在登录期间只传输一次以交换访问令牌。访问令牌通常有一个到期日期,也可以在服务器上撤销。这降低了密码被截获且未存储在设备上的风险。你的后端应该支持这个。

【讨论】:

    【解决方案2】:

    是的,这在 Android 上很棘手。您不想将明文密码存储在首选项中,因为任何拥有根设备的人基本上都会向全世界显示他们的密码。另一方面,您不能使用加密密码,因为您必须将加密/解密密钥存储在设备上的某个位置,同样容易受到根攻击。

    我不久前使用的一个解决方案是让服务器生成一个“票证”,然后将其传回设备,这在一段时间内是有效的。设备使用此票证进行所有通信,当然使用 SSL,因此人们无法窃取您的票证。这样,用户在服务器上验证他们的密码一次,服务器发回一个过期的票,密码永远不会存储在设备上的任何地方。

    一些三足身份验证机制,如 OpenID、Facebook,甚至 Google API,都使用这种机制。缺点是每隔一段时间,当票过期时,用户需要重新登录。

    最终,这取决于您希望您的应用程序有多安全。如果这只是为了区分用户,并且没有像银行账户或血型那样存储超级机密信息,那么将 PWD 以明文形式保存在设备上就可以了 :)

    祝你好运,无论您决定哪种方法最适合您的特定情况!

    编辑:我应该注意,这种技术将安全责任转移到服务器 - 您将希望使用加盐哈希在服务器上进行密码比较,您将在其他一些 cmets 中看到这个问题的想法.这可以防止明文密码出现在除设备上的 EditText 视图、与服务器的 SSL 通信以及服务器的 RAM 之外的任何地方,同时对密码进行加盐和哈希处理。它永远不会存储在磁盘上,这是一件好事™。

    【讨论】:

    • 从技术上讲,我同意你所说的一切。但我觉得你的血型越公开,你就越有可能在紧急情况下得救;)
    • 数据不是超级机密也没关系,用户可以在很多地方使用相同的用户名/密码,攻击者可以获取这些用户名/密码并尝试不同的服务。
    • I should note that this technique transfers the responsibility of security to the server 实际上服务器已经这个责任,因为不管你是否使用这种技术,它都需要处理登录。
    【解决方案3】:

    您可以使用 Jetpack 安全库中的 EncryptedSharedPreferences。它非常适合键值类型设置。

    它封装了SharedPreferences,提供安全的加密/解密,同时保持与SharedPreferences 相同的API。

    在他们的例子中:

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

    【讨论】:

    • 是否有任何值被泄露的可能性,一个获得设备或根设备的人?
    • 极不可能@Girish。如果它以硬件加密设备为后盾,即使不是不可能,也确实很困难。
    • 此外,您可以使用密钥证明来确定设备仍然受信任(至少受 Google 信任)的额外确定性。
    • 请举一个关键证明的例子。
    • @Girish 我不能比文档更好地说明它:developer.android.com/training/articles/…
    【解决方案4】:
    Follow below steps :
    
    1> create checkbox in xml file.
     <CheckBox
                    android:id="@+id/cb_remember"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_marginTop="@dimen/_25sdp"
                    android:background="@drawable/rememberme_background"
                    android:buttonTint="@android:color/white"
                    android:paddingLeft="@dimen/_10sdp"
                    android:paddingTop="@dimen/_5sdp"
                    android:paddingRight="@dimen/_10sdp"
                    android:paddingBottom="@dimen/_5sdp"
                    android:text="REMEMBER ME"
                    android:textColor="@android:color/white"
                    android:textSize="@dimen/_12sdp" />
    
    2> put this below code in java file.
      cb_remember.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                    if(b){
                        Log.d("mytag","checkbox is-----true----");
                        Prefs.getPrefInstance().setValue(LoginActivity.this, Const.CHECKBOX_STATUS, "1");
                        String userName =Prefs.getPrefInstance().getValue(context, Const.LOGIN_USERNAME, "");
                        String password =Prefs.getPrefInstance().getValue(context, Const.LOGIN_PASSWORD, "");
                        Log.d("mytag","userName and password id----"+userName +"         "+password);
                        edt_user_name.setText(userName);
                        edt_pwd.setText(password);
    
                    }else{
                        Log.d("mytag","checkbox is-----false----");
                        Prefs.getPrefInstance().setValue(LoginActivity.this, Const.CHECKBOX_STATUS, "0");
                    }
                }
            });
    
    3> add this below code in java file before we check the checkbox.
      String stst =Prefs.getPrefInstance().getValue(LoginActivity.this, Const.CHECKBOX_STATUS, "");
            Log.d("mytag","statyus of the checkbox is----"+stst);
            if(stst.equals("1")){
                cb_remember.setChecked(true);
            }else{
                cb_remember.setChecked(false);
            }
    

    【讨论】:

      【解决方案5】:

      我想把密码保存在 SharedPreferences 中,所以我先像下面的代码一样私下实现了

      public class PrefManager {
      
        private SharedPreferences pref;
        private SharedPreferences.Editor editor;
      
        public PrefManager(Context context) {
          pref = context.getSharedPreferences("PROJECT_NAME", Context.MODE_PRIVATE);
          editor = pref.edit();
        }
      
      }
      

      为了保存密码,我使用了一种算法来加密和解密

      加密算法

       public void setPassword(String password) {
            int len = password.length();
            len /= 2;
            StringBuilder b1 = new StringBuilder(password.substring(0, len));
            StringBuilder b2 = new StringBuilder(password.substring(len));
            b1.reverse();
            b2.reverse();
            password = b1.toString() + b2.toString();
      
          editor.putString("password", password);
          editor.apply();
        }
      

      解密算法

        public String getPassword() {
          String password = pref.getString("password", null);
          int len = password.length();
          len /= 2;
          StringBuilder b1 = new StringBuilder(password.substring(0, len));
          StringBuilder b2 = new StringBuilder(password.substring(len));
          password = b1.reverse().toString() + b2.reverse().toString();
          return password;
        }
      

      注意:

      在这个简单的算法中,我把密码从中间分成两部分,把它倒过来,然后放回去。这只是一个想法,您可以使用您自己的算法来更改密码的保存方式。 p>

      完整代码

      import android.content.Context;
      import android.content.SharedPreferences;
      
      public class PrefManager {
      
        private SharedPreferences pref;
        private SharedPreferences.Editor editor;
      
        public PrefManager(Context context) {
          pref = context.getSharedPreferences("PROJECT_NAME", Context.MODE_PRIVATE);
          editor = pref.edit();
        }
        public String getPassword() {
          String password = pref.getString("password", null);
          int len = password.length();
          len /= 2;
          StringBuilder b1 = new StringBuilder(password.substring(0, len));
          StringBuilder b2 = new StringBuilder(password.substring(len));
          password = b1.reverse().toString() + b2.reverse().toString();
          return password;
        }
      
        public void setPassword(String password) {
            int len = password.length();
            len /= 2;
            StringBuilder b1 = new StringBuilder(password.substring(0, len));
            StringBuilder b2 = new StringBuilder(password.substring(len));
            b1.reverse();
            b2.reverse();
            password = b1.toString() + b2.toString();
      
          editor.putString("password", password);
          editor.apply();
        }
      }
      

      【讨论】:

      • 您的伪算法不安全,因为该算法要求它是秘密的,否则它将无法工作,并且这不会增加安全性,因为没有秘密算法比开源算法更安全。无论如何,对字符串的这种类型的操作是可以预测的。
      【解决方案6】:

      使用 NDK 进行加密和解密以及在此处定义字符串密钥变量而不是将其保存在共享首选项中或将其定义在字符串 xml 中,这将有助于防止大多数脚本小子窃取密钥。然后将生成的密文存储在共享首选项中。 This link may help about the sample code

      【讨论】:

        【解决方案7】:
         //encode password
         pass_word_et = (EditText) v.findViewById(R.id.password_et);
         String pwd = pass_word_et.getText().toString();
                        byte[] data = new byte[0];
                        try {
                            data = pwd.getBytes("UTF-8");
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                        String base64 = Base64.encodeToString(data, Base64.DEFAULT);
                        hbha_pref_helper.saveStringValue("pass_word", base64);
        
         //decode password
         String base64=hbha_pref_helper.getStringValue("pass_word");
                    byte[] data = Base64.decode(base64, Base64.DEFAULT);
                    String decrypt_pwd="";
                    try {
                         decrypt_pwd = new String(data, "UTF-8");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
        

        【讨论】:

        • 欢迎来到 StackOverflow!您能否也提供解释以帮助 PO 理解您的回答?
        • Base64不是加密,是编码算法,很容易识别和解码。
        【解决方案8】:

        正如其他人所说,没有安全的方法可以在 Android 中存储完全保护数据的密码。散列/加密密码是一个好主意,但它所做的只是减慢“破解者”的速度。

        话虽如此,这就是我所做的:

        1) 我使用了这个simplecryto.java class,它接受一个种子和一个文本并对其进行加密。 2) 我在私人模式下使用了SharedPreferences,它保护了非根设备中保存的文件。 3) 我用于 simplecryto 的种子是一个字节数组,反编译器比字符串更难找到它。

        我的申请最近由我公司雇用的“白帽”安全小组审核。他们标记了这个问题,并指出我应该使用 OAUTH,但他们也将其列为低风险问题,这意味着它不是很好,但还不足以阻止发布。

        请记住,“破解者”需要对设备具有物理访问权限并对其进行 root 并足够关心以找到种子。

        如果您真的关心安全性,请不要选择“让我保持登录”选项。

        【讨论】:

        • 教程链接坏了,所以我不确定它是否相同,但谷歌立即给我展示了一篇看起来非常相似的文章:androidsnippets.com/encryptdecrypt-strings,但我突然想到您可以使用存储在设备外的随机生成的种子以增加安全性。此共享密钥/种子将由每个用户随机生成,并且设备外存储将包含映射。只要您安全地与该位置交换信息,并且该存储是安全的,那不是更好吗?
        • @batbrat 如果您要拥有服务器端的持久用户状态,请推荐 oauth2 而不是自己开发的东西。在我的示例中,没有用户状态,因此我没有可调用的对象。
        • 感谢您的澄清。我现在明白了,同意了。
        • 我非常怀疑 OAuth 是否可以使其安全,最好的情况是它可以使它更难破解,最坏的情况是它会让你暴露在它自己的漏洞中。当攻击者拥有与您的应用程序和您的应用程序源代码相同的数据时,就不可能使其安全。
        • 不同的链接不起作用。改用这个。 github.com/Medisana/Android-Standalone/blob/master/app/src/main/…
        【解决方案9】:

        在不危及安全的情况下执行此操作的最安全方法是使用共享首选项仅存储最后一个登录用户的用户名。

        另外,在您的用户表中,引入一个包含布尔数字(1 或 0)的列,以表示该人是否选中了该人是否选中了“记住我”复选框。

        在启动您的应用程序时,使用 getSharedPreferences() 函数获取用户名并使用它来查询您的托管数据库以查看登录列是 1 还是 0 ,其中 1 表示该人选中了“记住我”复选框。

        【讨论】:

        • 看来您已经解决了所有的身份验证问题。除了任何人都可以在发现后将您的用户名发送到服务器(如果很难找到,那么我们也会以明文形式保存密码)
        【解决方案10】:

        至少,将其存储在SharedPreferences(私人模式)中,并且不要忘记散列密码。虽然这对恶意用户(或 root 设备)并没有真正的影响,但它确实是。

        【讨论】:

        • 任何教程请教如何散列密码?我对这个想法很感兴趣。没有人可以使用该技术检索存储的密码?
        • @androniennn 有很多关于如何散列密码的教程,所以我会让你谷歌一下。要记住的一件事是,散列只是为了阻止 casual 窥探者。当设备被植根(例如)并且恶意用户也可以访问您的二进制文件时,此技术无效。想一想,如果有人可以对您的程序进行逆向工程,那么他们可以很容易地找出如何您对密码进行哈希处理。你只需要知道所有的可能性:)
        • 如果我的设备重新启动,存储在 SharedPreferences 中会清除数据。只有我会这样吗??
        • 你为什么不使用 1-way hash,使用类似 Sha-1 的东西。那么你所要做的就是检查哈希是否匹配,而不是如果解密的明文匹配。
        • @styler1972 这与使用来自服务器的令牌相同,1-way has 可以反转(尽管这并不容易但仍然可能),1-way hashing the token is the在这里获得最佳安全性的最佳选择。即使有人花时间和精力生成与哈希令牌匹配的数据,到那时它也已经过期,如果他要找到你的密码,那么如果它是银行账户,那就和你说再见吧钱。令牌很好,哈希令牌是最好的。即使经过哈希处理,也不要保留密码。但如前所述,如果数据不敏感,则由您决定。
        猜你喜欢
        • 2021-04-25
        • 2013-10-27
        • 2014-03-08
        • 1970-01-01
        • 1970-01-01
        • 2013-12-27
        • 2012-05-10
        • 2019-09-02
        • 1970-01-01
        相关资源
        最近更新 更多