50

I am developing an Android app where the user needs to sign in to perform operations. But mostly on an android handset, people use "Keep me signed in", In that case, I'll have to maintain the value of Username and Password within my app. Should I use SharedPreferences, or SQLite Database or is there something else which I can use.
And how can I make it secure?

Rohit Singh
  • 16,950
  • 7
  • 90
  • 88
Saurabh Agrawal
  • 1,355
  • 3
  • 17
  • 33

10 Answers10

36

Yes, this is tricky on Android. You don't want to store the plaintext password in the preferences, because anyone with a rooted device will basically be displaying their password to the world. On the flip side, you can't use an encrypted password, because you'd have to store your encryption/decryption key somewhere on the device, again susceptible to the root attack.

One solution I used a while back is to have the server generate a "ticket" which it passes back to the device, which is good for a certain period of time. This ticket is used by the device for all communication, using SSL of course so people can't steal your ticket. This way, the user authenticates their password on the server once, the server sends back an expiring ticket, and the password is never stored anywhere on the device.

Several three-legged authentication mechanisms, like OpenID, Facebook, even Google APIs, use this mechanism. The downsides are that every once in a while when the ticket expires, the user needs to re-login.

Ultimately, it depends on how secure you want your application to be. If this is simply to distinguish users, and no super-secret information is being stored like bank accounts or blood types, then perhaps saving the PWD in plaintext on the device is just fine :)

Good luck, whatever method you decide is best for your particular situation!

Edit: I should note that this technique transfers the responsibility of security to the server - you'll want to use salted hashes for password comparison on the server, an idea you'll see in some of the other comments for this question. This prevents the plaintext password from appearing anywhere except the EditText View on the device, the SSL communication to the server, and the server's RAM while it salts and hashes the password. It's never stored on disk, which is a Good Thing™.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
Mike
  • 4,542
  • 1
  • 26
  • 29
  • 5
    I agree on everything you said technically speaking. But I feel the more public your blood type is, the more likely you can be saved in case of emergency ;) – Mick F Jan 30 '14 at 17:47
  • 4
    It doesn't matter if the data is not super-secret, users can use the same user/password in many places and an attacker can get those and try on different services. – Fernando Gallego Oct 12 '16 at 11:30
  • 1
    `I should note that this technique transfers the responsibility of security to the server` really the server already *has* this responsibility because it needs to handle the logins anyway wether you use this technique or not. – RobCo Feb 11 '20 at 14:51
  • As of 2022, [EncryptedSharedPreferences](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences) seems like a good choice. – Simon Forsberg Dec 06 '22 at 13:10
27

As others have said there is no secure way to store a password in Android which protects the data fully. Hashing/encrypting the password is a great idea but all it will do is slow down the "cracker".

With that said, this is what I did:

1) I used this simplecryto.java class which takes a seed and a text and encrypts it. 2) I used SharedPreferences in private mode which protects the saved file in non-rooted devices. 3) The seed I used for simplecryto is an array of bytes which is a little bit harder to find by decompilers than a String.

My application was recently reviewed by a "white hat" security group hired by my company. They flagged this issue, and indicated I should be using OAUTH but they also listed it as a LOW risk issue, which means it's not great, but not bad enough to prevent release.

Remember that the "cracker" would need to have physical access to the device AND root it AND care enough to find the seed.

If you really care about security, don't have a "keep me logged in" option.

MH.
  • 45,303
  • 10
  • 103
  • 116
knaak
  • 1,293
  • 1
  • 12
  • 18
  • The tutorial link is broken, so I'm not sure if it is the same, but a Google immediately gave showed me an article that seemed pretty similar: http://www.androidsnippets.com/encryptdecrypt-strings, but it occurs to me that you could use a randomly generated seed that is stored off the device for added security. This shared secret/seed would be randomly generated per user and the off-device storage would contain the mapping. So long as you securely exchange information with that location securely, and that storage is secure, wouldn't that work out better? – batbrat Mar 11 '14 at 09:29
  • 1
    @batbrat If you are going to have a server side persistent user state, recommend oauth2 rather than something homegrown. In my example, there is no user state and so I had nothing available to call against. – knaak Mar 11 '14 at 19:17
  • Thank you for clarifying. I understand better now, and agree. – batbrat Mar 11 '14 at 19:35
  • I very much doubt that OAuth can make it secure, at best it can make it harder to crack, at worst it can leave you exposed by its own vulnerabilities. When an attacker has the same data as your application and your application's source code, it is impossible to make it secure. – Velizar Hristov Jul 16 '16 at 14:31
  • The different links doesn't work. Use this instead. https://github.com/Medisana/Android-Standalone/blob/master/app/src/main/java/com/example/miguel/myapplication/service/SimpleCrypto.java – Jesper Sep 14 '16 at 08:53
7

At the very least, store it in SharedPreferences (private mode) and don't forget to hash the password. Although this won't really make a difference with a malicious user (or rooted device), it's something.

Marvin Pinto
  • 30,138
  • 7
  • 37
  • 54
  • 1
    Any tutorial please of how hashing a password? I'm interested in that idea. And no one can retrieve the stored password with that technic? – androniennn Feb 10 '12 at 19:10
  • 3
    @androniennn There are a _ton_ of tutorials on how to hash passwords so I'll let you Google that. One thing to keep in mind is that hashing is only meant to thwart the _casual_ snooper. This technique **has no effect** when the device is rooted (for example), and the malicious user has access to your binary as well. Think about it, if someone can reverse engineer your program, then they can easily figure out _how_ you hashed the password. You just need to be aware of the possibilities is all :) – Marvin Pinto Feb 10 '12 at 19:14
  • storing in SharedPreferences is clearing data if my device re-booted. Is that happening only to me ?? – Gnanam R May 08 '13 at 10:03
  • 1
    Why dont you 1-way hash, using something like Sha-1.. then all you have to do is check to see that the hashes match, not if the decrypted plaintext matches. – Tyler Aug 29 '13 at 14:31
  • @styler1972 that would be the same as using a token from the server, 1-way has can be reversed (even though it is not easy it is still possible), 1 way hashing the token is the best option for best security here. even if someone was to put the time and effort in generating a data that matches the hashed token by then it would have already been expired, where if he was to find your password then, if it is a bank account, say good bye to your money. tokens are good, hashed tokens are best. never keep passwords even if hashed. But as mentioned if the data is not sensitive then it is your call. – bakriawad Aug 02 '18 at 21:32
6

You could use EncryptedSharedPreferences from the Jetpack security library. It works great for key-value type settings.

It wraps SharedPreferences, providing secure encryption/decryption while maintaining the same API as SharedPreferences.

As in their example:

  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();
Climax
  • 663
  • 6
  • 17
  • is there any possibility of values getting leaked, one who gets hold of device or rooted device? – Girish Aug 04 '20 at 16:53
  • Highly unlikely @Girish. If it is backed with hardware crypto devices it is truly difficult if not improbable. – Climax Aug 08 '20 at 16:38
  • Furthermore, you could use key attestation to determine an extra level of certainty that the device is still trusted (at least by Google). – Climax Aug 08 '20 at 16:41
  • Could be please give an example of a key attestation. – Girish Aug 10 '20 at 05:02
  • @Girish I cannot state it better than the docs: https://developer.android.com/training/articles/security-key-attestation – Climax Aug 11 '20 at 21:05
  • **WARNING** Absolutely avoid using it if you develop a system app (_sharedUserId="android.uid.system"_), here's why: https://stackoverflow.com/questions/66988384/does-android-keystore-and-clear-data-action-of-the-settings-app-are-related – Max_Payne Jan 16 '23 at 12:06
2

I wanted to save the password in the SharedPreferences , so I implemented it privately first like the code below

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

}

and to save the password, I used an algorithm to encrypt and decrypt

encrypt algorithm

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

decrypt algorithm

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

NOTE:

In this simple algorithm, I split the password from the middle into two pieces, turned it upside down, and put it back together. It was just an idea and you can use your own algorithms to change how to save the password.

FULL CODE

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();
  }
}
abolfazl bazghandi
  • 935
  • 15
  • 29
  • 1
    Your pseudo algorithm is not secure as the algorithm requires it to be secret otherwise it won't work and this does not increase security as NO secret algorithm is safer than an open source one. In any case, that type of operation on a string is predictable. – Nicola Revelant Sep 22 '21 at 12:48
  • It doesn't take a genius to figure out that "ssapdrow" is really "password". Please don't call your pseudo-algorithm "encryption"/"decryption". It is highly misleading. – Simon Forsberg Dec 06 '22 at 13:20
  • You are absolutely right, of course, if the user uses a simple password, it is easily recognized, but any developer can create his own algorithm to change the stored string or add some value to it. I just wanted to say a somewhat creative way. @SimonForsberg – abolfazl bazghandi Dec 07 '22 at 07:44
  • @abolfazlbazghandi When it comes to encryption, I would strongly recommend that developers do not try to "roll their own" but instead use one of the standards, such as AES256. – Simon Forsberg Dec 07 '22 at 15:46
1

The safest way to do this without jeopardizing security is to use the shared preferences to store ONLY the username of the last person to login in.

Also, in your table of users, introduce a column that holds numeric boolean (1 or 0) to represent whether the person checked the person checked the "remember me" checkbox or not.

When launching your app get the username using the getSharedPreferences() function and use it to query your hosted database to see whether the signedin column is either 1 or 0 , where 1 indicates the person checked the "remember me" checkbox.

biegleux
  • 13,179
  • 11
  • 45
  • 52
  • 1
    Well it seems you have solved every authentication problem. Except that ANYONE can send your username to the server once discovered (and if it is difficult to find out then we will also save the password in clear text) – Nicola Revelant Sep 22 '21 at 12:53
1

Using NDK for encryption and decryption along with defining the String Key variable there instead of saving it in the shared preferences or defining it ins the string xml would help to prevent secret key stealing against most of the script kiddies. The resulted cipher text would be then stored in the shared preferences. This link may help about the sample code

Irfan Ul Haq
  • 1,065
  • 1
  • 11
  • 19
1

Google offers the mechanism of the AccountManager. This is the standard mechanism to use for creating accounts. The login data is then stored where Android finds it suitable, e.g. if the device is offering a secured zones, it will be used. Of course rooted devices are still an issue, but at least this is using the standard mechanism and not something self baked which is also not benefiting from Android system updates. This also has the advantage that the account is listed in the Android settings, another positive features is the "sync" feature which enables an account to sync data between the app and the backend system, so you get more than just the login.

Apart from this using a username and password is not the best option anymore. All better apps are using OAuth nowadays. Here the noteworthy difference is that the password is just transmitted once during the login in exchange of an access token. The access token has usually an expiration date and can also be revoked on the server. This mitigates the risk that the password is intercepted and it is not stored on the device. Your backend should support this.

k_o_
  • 5,143
  • 1
  • 34
  • 43
0
 //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();
            }
  • 2
    Welcome to StackOverflow! Can you also provide an explanation to help the PO to understand your answer? – FrankS101 Sep 01 '16 at 11:30
  • 13
    Base64 is not encryption, it is encoding algorithm, which is very easy to recognize and decode. – Miha_x64 Oct 27 '16 at 12:56
0
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);
        }
MEGHA DOBARIYA
  • 1,622
  • 9
  • 7