71

The common location where SharedPreferences are stored in Android apps is:

/data/data/<package name>/shared_prefs/<filename.xml>

User with root privileges can navigate to this location and can change its values.Need of protecting it is of much importance.

In how many ways we can encrypt whole shared_pref's xml file?

We all know that we can encrypt and save data in shared_pref's xml file, but that's not only 100% safe, so need to encrypt whole file with a key. Need help in knowing various ways to encrypt whole xml file. This is generic question, various encryption methods discussed as answers here can be helpful to all developers in securing apps.

Harsh Dattani
  • 2,109
  • 1
  • 17
  • 27

11 Answers11

95

UPDATED ANSWER:

Android has released a security library with EncryptedSharedPreferences in their Jetpack library.

Edit: With version v1.1.0 you can support Lollipop (API level 21) and above

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();
Bojan Kseneman
  • 15,488
  • 2
  • 54
  • 59
  • Thanks! explained in a great manner :) But can't we encrypt whole file? Because if we use Base64 to encrypt Key and Value pair, then it can be easily broken. Can it be made little more tough to decrypt? – Harsh Dattani May 10 '15 at 08:04
  • 1
    You cannot encrypt the whole file, if you want to do that, you would need a custom implementation of preferences and probably a good idea to make your own parser. That's what I did. I have created a new JNI based library (c++ code - not as easy to decompile) and secured the whole thing with 3KTDES encryption How SharedPreferences work is a known fact, even this fact makes them insecure, – Bojan Kseneman May 10 '15 at 08:10
  • 118
    Base64 is not encryption. Don't use it for encryption. http://stackoverflow.com/questions/4070693/what-is-the-purpose-of-base-64-encoding-and-why-it-used-in-http-basic-authentica#answer-4070709 – Klaasvaak Mar 14 '16 at 13:04
  • 23
    All of the mentioned solutions work this way: *Let's secure the door with a lock. But where to place the key? Lets just place the key next to the door.* Conclusion: All solutions do not provide any security - they all just use obscurity to hide data. Please be aware of this! – Robert Apr 14 '17 at 10:10
  • @Robert Can't you use Android KeyStore to store the key safely? – blinkmacalahan May 31 '18 at 17:09
  • @blinkmacalahan The Android KeyStore can be used, however someone with root permissions (as mentioned in the question) can AFAIK circumvent the KeyStore system pretending to be the app the key belongs to and therefore can use the stored key like the original app does -> it is just a little bit harder. – Robert May 31 '18 at 19:11
  • @BojanKseneman I wouldn't necessarily agree that simply because it is known how it works it is insecure. It's insecurity is more due to it solely being access-protected, but not encrypted. As a counter-example, it is also well-known how [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) works, yet it is considered secure. What you are referring to sounds dangerously like [Security through Obscurity](https://stackoverflow.com/questions/533965/why-is-security-through-obscurity-a-bad-idea). As Robert said, genuine security requires encryption with a user-provided key. – Michael Jess Aug 02 '18 at 12:06
  • 1
    Base64 is Encoding (not encryption) and the discussion is about encryption so doesn't look appropriate in the suggested approach. – Birender Singh Aug 20 '18 at 12:43
  • @patrickf Thanks, decided to try out your library armadillo. The Documentation and Diagrams are quite helpful. However it would be nice to have more documentation on the builder methods and which ones are symbiotic. – ClayHerendeen Dec 28 '18 at 16:28
  • @ClayHerendeen glad you like it; the Github readme should kick start you, for more varied use cases check out the unit tests or the app. Most of the options are not really needed for normal use. If you are unsure you can open an issue in Github with a specific question or if you think the readme is unclear. – Patrick Dec 28 '18 at 19:35
  • As mentioned by some comments you shouldn't use that solution. Base 64 is an algorithm for encode data not encrypt. With that solution your data will be secure as in plain text. – GMX May 22 '19 at 12:09
  • 1
    add `implementation 'androidx.security:security-crypto:1.0.0-alpha02'` to build.gradle – AtomicBoolean Oct 09 '19 at 17:07
  • 9
    How to support this api level 21+? – Sachin Tanpure Oct 24 '19 at 07:50
  • I rolled back the answer edit since it essentially rewrites the whole answer so that it makes use of a new library. If you want to add new data to the existing answer, you should mention below the existing answer. – aga Nov 11 '19 at 15:47
  • I added the magic updated text. But basically this should be the way to go in 2019 – Bojan Kseneman Nov 13 '19 at 07:56
  • `implementation "androidx.security:security-crypto:1.0.0-alpha02"` and see this nice implementation Demo https://github.com/akaita/encryptedsharedpreferences-example – EL TEGANI MOHAMED HAMAD GABIR Jan 08 '20 at 09:55
  • is there any proguard rules for using it ?https://stackoverflow.com/questions/61369378/what-is-proguard-rules-should-use-for-encryptedsharedpreferences – Ahmed Mousa Apr 22 '20 at 16:15
  • @SachinTanpure, it [supports API 21](https://developer.android.com/jetpack/androidx/releases/security#security-crypto-1.1.0-alpha01). – CoolMind Mar 19 '21 at 11:14
  • MasterKeys class become deprecated – morte Mar 20 '22 at 01:52
  • How is this supposed to be more secure than the already sandboxed app? EncryptedSharedPreferences encrypts the data with a key and then stores the key on the device. If someone has access to internal app data, whats stops them from also having access to the encryption key? Specifically, if you are worried about a rooted device, root can access the keystore of any app to get access to the app's keys. –  Jul 01 '22 at 18:24
  • 1
    There are 2 years old unresolved issues with this library. It gets worse as your app get's bigger. https://issuetracker.google.com/issues/176215143 https://issuetracker.google.com/issues/158234058 https://issuetracker.google.com/issues/164901843 – Nikola Samardzija Nov 03 '22 at 08:51
  • What is the example value of shared pref file name? – Shashi Ranjan Mar 20 '23 at 10:19
22

Google has released EncryptedSharedPreferences as part of it's androidx, I believe this should be the preferable way of encrypting the preferences.

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

Ch3D
  • 241
  • 2
  • 4
  • 1
    Do you have any example on how implement that? – GMX May 22 '19 at 12:22
  • @GMX open the link I've shared above, there is an example at the top of the page – Ch3D May 25 '19 at 15:06
  • 2
    EncryptedSharedPreferences seems to be a great option, but there is two disadvantages: 1. It is in alpha channel 2.Min sdk version for this library is 23 – Ololoking Jul 28 '19 at 12:47
  • 3
    It now supports Lollipop (21) version as well – Usman Rana Jan 15 '21 at 13:04
  • @UsmanRana, thanks, I didn't know that. [Source](https://developer.android.com/jetpack/androidx/releases/security): 'Lollipop (API Level 21+) is now supported. Please note that the AndroidKeyStore is not used for API 21 and 22'. – CoolMind Mar 18 '21 at 11:32
  • How is this supposed to be more secure than the already sandboxed app? EncryptedSharedPreferences encrypts the data with a key and then stores the key on the device. If someone has access to internal app data, whats stops them from also having access to the encryption key? –  Jul 01 '22 at 18:20
14

If you want to support Android 5.0 (API level 21) and above

Use following implementation:

implementation "androidx.security:security-crypto:1.0.0-rc04"

or get latest one from this source.

Then

First create a master key as follows:

val masterKey = MasterKey.Builder(context)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build()

After create shared preferences as follows:

    val sharedPreferences = EncryptedSharedPreferences.create(
        context,
        "secret_shared_prefs",
        masterKey,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )

Then use it as you normally would for example:

with(sharedPreferences.edit()) {
    putString(Values.SP_USER_ID, personId)
    putString(Values.SP_USER_NAME, binding.editTextTextPersonName.text.toString())
    apply()
}
F.Mysir
  • 2,838
  • 28
  • 39
13

You need to handle Verisons under API 23

fun providesSharedPreference(): SharedPreferences {
    var sharedPreferences: SharedPreferences

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        sharedPreferences = EncryptedSharedPreferences.create(
            application,
            Constant.SHARED_PREFERENCE_NAME,
            getMasterKey(),
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        )

    } else {
        sharedPreferences =
            application.getSharedPreferences(
                Constant.SHARED_PREFERENCE_NAME,
                Context.MODE_PRIVATE
            )
    }
    return sharedPreferences
}

private fun getMasterKey(): MasterKey {
    return MasterKey.Builder(application)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()
}
Mohamed AbdelraZek
  • 2,503
  • 4
  • 25
  • 36
8

You should encrypt your data and write to SharedPreferences. When you want get this data then you should decrypt from SharedPreferences. you need the following helper class for this

public class Encryption {
private final Builder mBuilder;

private Encryption(Builder builder) {
    mBuilder = builder;
}

public static Encryption getDefault(String key, String salt, byte[] iv) {
    try {
        return Builder.getDefaultBuilder(key, salt, iv).build();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        return null;
    }
}

private String encrypt(String data) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException, BadPaddingException, IllegalBlockSizeException {
    if (data == null) return null;
    SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
    byte[] dataBytes = data.getBytes(mBuilder.getCharsetName());
    Cipher cipher = Cipher.getInstance(mBuilder.getAlgorithm());
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
    return Base64.encodeToString(cipher.doFinal(dataBytes), mBuilder.getBase64Mode());
}

public String encryptOrNull(String data) {
    try {
        return encrypt(data);
    } catch (Exception e) {
        e.printStackTrace();
        return "";
    }
}

public void encryptAsync(final String data, final Callback callback) {
    if (callback == null) return;
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                String encrypt = encrypt(data);
                if (encrypt == null) {
                    callback.onError(new Exception("Encrypt return null, it normally occurs when you send a null data"));
                }
                callback.onSuccess(encrypt);
            } catch (Exception e) {
                callback.onError(e);
            }
        }
    }).start();
}

private String decrypt(String data) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
    if (data == null) return null;
    byte[] dataBytes = Base64.decode(data, mBuilder.getBase64Mode());
    SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
    Cipher cipher = Cipher.getInstance(mBuilder.getAlgorithm());
    cipher.init(Cipher.DECRYPT_MODE, secretKey, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
    byte[] dataBytesDecrypted = (cipher.doFinal(dataBytes));
    return new String(dataBytesDecrypted);
}

public String decryptOrNull(String data) {
    try {
        return decrypt(data);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

public void decryptAsync(final String data, final Callback callback) {
    if (callback == null) return;
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                String decrypt = decrypt(data);
                if (decrypt == null) {
                    callback.onError(new Exception("Decrypt return null, it normally occurs when you send a null data"));
                }
                callback.onSuccess(decrypt);
            } catch (Exception e) {
                callback.onError(e);
            }
        }
    }).start();
}

private SecretKey getSecretKey(char[] key) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException {
    SecretKeyFactory factory = SecretKeyFactory.getInstance(mBuilder.getSecretKeyType());
    KeySpec spec = new PBEKeySpec(key, mBuilder.getSalt().getBytes(mBuilder.getCharsetName()), mBuilder.getIterationCount(), mBuilder.getKeyLength());
    SecretKey tmp = factory.generateSecret(spec);
    return new SecretKeySpec(tmp.getEncoded(), mBuilder.getKeyAlgorithm());
}

private char[] hashTheKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException {
    MessageDigest messageDigest = MessageDigest.getInstance(mBuilder.getDigestAlgorithm());
    messageDigest.update(key.getBytes(mBuilder.getCharsetName()));
    return Base64.encodeToString(messageDigest.digest(), Base64.NO_PADDING).toCharArray();
}

public interface Callback {
    void onSuccess(String result);
    void onError(Exception exception);
}

private static class Builder {

    private byte[] mIv;
    private int mKeyLength;
    private int mBase64Mode;
    private int mIterationCount;
    private String mSalt;
    private String mKey;
    private String mAlgorithm;
    private String mKeyAlgorithm;
    private String mCharsetName;
    private String mSecretKeyType;
    private String mDigestAlgorithm;
    private String mSecureRandomAlgorithm;
    private SecureRandom mSecureRandom;
    private IvParameterSpec mIvParameterSpec;

    public static Builder getDefaultBuilder(String key, String salt, byte[] iv) {
        return new Builder()
                .setIv(iv)
                .setKey(key)
                .setSalt(salt)
                .setKeyLength(128)
                .setKeyAlgorithm("AES")
                .setCharsetName("UTF8")
                .setIterationCount(1)
                .setDigestAlgorithm("SHA1")
                .setBase64Mode(Base64.DEFAULT)
                .setAlgorithm("AES/CBC/PKCS5Padding")
                .setSecureRandomAlgorithm("SHA1PRNG")
                .setSecretKeyType("PBKDF2WithHmacSHA1");
    }

    private Encryption build() throws NoSuchAlgorithmException {
        setSecureRandom(SecureRandom.getInstance(getSecureRandomAlgorithm()));
        setIvParameterSpec(new IvParameterSpec(getIv()));
        return new Encryption(this);
    }

    private String getCharsetName() {
        return mCharsetName;
    }

    private Builder setCharsetName(String charsetName) {
        mCharsetName = charsetName;
        return this;
    }

    private String getAlgorithm() {
        return mAlgorithm;
    }

    private Builder setAlgorithm(String algorithm) {
        mAlgorithm = algorithm;
        return this;
    }

    private String getKeyAlgorithm() {
        return mKeyAlgorithm;
    }

    private Builder setKeyAlgorithm(String keyAlgorithm) {
        mKeyAlgorithm = keyAlgorithm;
        return this;
    }

    private int getBase64Mode() {
        return mBase64Mode;
    }

    private Builder setBase64Mode(int base64Mode) {
        mBase64Mode = base64Mode;
        return this;
    }

    private String getSecretKeyType() {
        return mSecretKeyType;
    }

    private Builder setSecretKeyType(String secretKeyType) {
        mSecretKeyType = secretKeyType;
        return this;
    }

    private String getSalt() {
        return mSalt;
    }

    private Builder setSalt(String salt) {
        mSalt = salt;
        return this;
    }

    private String getKey() {
        return mKey;
    }

    private Builder setKey(String key) {
        mKey = key;
        return this;
    }

    private int getKeyLength() {
        return mKeyLength;
    }

    public Builder setKeyLength(int keyLength) {
        mKeyLength = keyLength;
        return this;
    }

    private int getIterationCount() {
        return mIterationCount;
    }

    public Builder setIterationCount(int iterationCount) {
        mIterationCount = iterationCount;
        return this;
    }

    private String getSecureRandomAlgorithm() {
        return mSecureRandomAlgorithm;
    }

    public Builder setSecureRandomAlgorithm(String secureRandomAlgorithm) {
        mSecureRandomAlgorithm = secureRandomAlgorithm;
        return this;
    }

    private byte[] getIv() {
        return mIv;
    }

    public Builder setIv(byte[] iv) {
        mIv = iv;
        return this;
    }

    private SecureRandom getSecureRandom() {
        return mSecureRandom;
    }

    public Builder setSecureRandom(SecureRandom secureRandom) {
        mSecureRandom = secureRandom;
        return this;
    }

    private IvParameterSpec getIvParameterSpec() {
        return mIvParameterSpec;
    }

    public Builder setIvParameterSpec(IvParameterSpec ivParameterSpec) {
        mIvParameterSpec = ivParameterSpec;
        return this;
    }

    private String getDigestAlgorithm() {
        return mDigestAlgorithm;
    }

    public Builder setDigestAlgorithm(String digestAlgorithm) {
        mDigestAlgorithm = digestAlgorithm;
        return this;
    }

}}

then you can write in SharedPreferences by encrypting your data as follows

 Encryption encryption = Encryption.getDefault("Key", "Salt", new byte[16]);
 SharedPreferences preferences =    PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
 SharedPreferences.Editor editor = preferences.edit();
 editor.putString("token", encryption.encryptOrNull(userModel.getToken()));
 editor.apply()

you can finally read from SharedPreferences data in the following way. This way, sensitive information will be safer while kept on the hardware level in the phone

final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
Encryption encryption = Encryption.getDefault("Key", "Salt", new byte[16]);
String token  = encryption.decryptOrNull(preferences.getString("token",""));
7

Complete answer (api level 23+). First you need crypto from androidx.

implementation "androidx.security:security-crypto:1.0.0-alpha02"

Care : there is a significant performance difference between SharedPreferences and EncryptedSharedPreferences. You should notice that EncryptedSharedPreferences.create(...) is not so fast so you should have to store one instance at all.

Then you have to use this to retrieve EncryptedSharedPreferences.

public SharedPreferences getEncryptedSharedPreferences(){
   String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
   SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
       "secret_shared_prefs_file",
       masterKeyAlias,
       context,
       EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
       EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
   );
    return sharedPreferences;
}

The you just have to use preference like the "standard way". To save it :

getEncryptedSharedPreferences().edit()
        .putString("ENCRYPTDATA", text)
        .apply()

To retrieve preference value.

getEncryptedSharedPreferences().getString("ENCRYPTDATA", "defvalue")
Zhar
  • 3,330
  • 2
  • 24
  • 25
  • In release mode I am this error 'Caused by: java.lang.RuntimeException: Field keySize_ for'. Have you seen this error before? – CanCoder Jun 01 '20 at 15:33
  • I never experience that. Perhaps there is a limit size for key ? What is the full stack trace ? – Zhar Jun 02 '20 at 16:31
  • I posted a question with the stacktrace here: https://stackoverflow.com/questions/62135766/secured-android-sharedpreferences-error-caused-by-java-lang-runtimeexception – CanCoder Jun 02 '20 at 19:00
  • Can we migrate existing sharedPreferences to encrypted sharepreferences? – Usman Rana Jan 18 '21 at 06:04
  • If your app is not released, yes. Then it depends if you want to keep actuals values or not. Otherwise you can do it "manually". Check if the old preference value exists, if yes migrate to secured value with getEncryptedSharedPreferences().edit().putXX. Then store old preference value as migrated (with a special flag or a string). When you retrieve your preference check if the old value is flagged or not. – Zhar Jan 19 '21 at 08:47
  • Where do you store the key? otherwise it can be taken when rooted – sandulasanth-7 Dec 06 '22 at 03:52
3

Base64 is NOT encryption! Don't use it! Yes 'root' users can access that data. One thing you can do is use AES to encrypt either that data or use a single NoSQL database file and encrypt that file. When the app opens, you decrypt the database and use that to store info or encrypt all files independently.

Look here: https://code.tutsplus.com/tutorials/storing-data-securely-on-android--cms-30558

Digital Human
  • 1,599
  • 1
  • 16
  • 26
2

Kotlin example for dual purpose encrypted & unencrypted shared preferences using anrdoidx's security-crypto library (min API 23).


I use Dagger2 to inject this as a @Singleton where needed.

Use the @Name annotation in your Dagger Modules to differentiate between the SharedPreferences instances and you can have 2 separate .xml files (1 encrypted, 1 unencrypted) to read/write to/from.


Add to dependenies in build.gradle:

implementation "androidx.security:security-crypto:1.0.0-beta01"


import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences

class Prefs(prefsName: String, context: Context) {

    private lateinit var ANDX_SECURITY_KEY_KEYSET: String
    private lateinit var ANDX_SECURITY_VALUE_KEYSET: String
    private lateinit var cntext: Context
    private lateinit var prefName: String

    private lateinit var prefs: SharedPreferences

    constructor(
        prefsName: String,
        context: Context,
        masterKeyAlias: String,
        prefKeyEncryptionScheme: EncryptedSharedPreferences.PrefKeyEncryptionScheme,
        prefValueEncryptionScheme: EncryptedSharedPreferences.PrefValueEncryptionScheme
    ): this(prefsName, context) {
        ANDX_SECURITY_KEY_KEYSET = "__androidx_security_crypto_encrypted_prefs_key_keyset__"
        ANDX_SECURITY_VALUE_KEYSET =    "__androidx_security_crypto_encrypted_prefs_value_keyset__"
        cntext = context
        prefName = prefsName
        prefs =
            EncryptedSharedPreferences.create(
                prefsName,
                masterKeyAlias,
                context,
                prefKeyEncryptionScheme,
                prefValueEncryptionScheme
            )
    }

    init {
        if (!::ANDX_SECURITY_KEY_KEYSET.isInitialized) {
            prefs =
                context.getSharedPreferences(
                    prefsName,
                    Context.MODE_PRIVATE
                )
        }
    }

    companion object {
        const val INVALID_BOOLEAN: Boolean = false
        const val INVALID_FLOAT: Float = -11111111111F
        const val INVALID_INT: Int = -1111111111
        const val INVALID_LONG: Long = -11111111111L
        const val INVALID_STRING: String = "INVALID_STRING"
        val INVALID_STRING_SET: Set<String> = setOf(INVALID_STRING)
    }

    /**
     * OnChangeListener
     * */
    fun registerOnSharedPreferenceChangeListener(
        listener: SharedPreferences.OnSharedPreferenceChangeListener) =
        prefs.registerOnSharedPreferenceChangeListener(listener)

    fun unregisterOnSharedPreferenceChangeListener(
        listener: SharedPreferences.OnSharedPreferenceChangeListener) =
        prefs.unregisterOnSharedPreferenceChangeListener(listener)

    /**
     * Read Shared Prefs
     * */
    fun contains(key: String): Boolean =
        prefs.contains(key)

    fun getAll(): Map<String, *> =
        prefs.all

    // Returns null if the Boolean value is not in
    //  Shared Preferences
    fun read(key: String): Boolean? =
        if (contains(key)) {
            read(key, INVALID_BOOLEAN)
        } else {
            null
        }

    // Boolean
    fun read(key: String, returnIfInvalid: Boolean): Boolean =
        prefs.getBoolean(key, returnIfInvalid)

    // Float
    fun read(key: String, returnIfInvalid: Float): Float =
        prefs.getFloat(key, returnIfInvalid)

    // Int
    fun read(key: String, returnIfInvalid: Int): Int =
        prefs.getInt(key, returnIfInvalid)

    // Long
    fun read(key: String, returnIfInvalid: Long): Long =
        prefs.getLong(key, returnIfInvalid)

    // Set<String>
    fun read(key: String, returnIfInvalid: Set<String>): Set<String>? =
        prefs.getStringSet(key, returnIfInvalid)

    // String
    fun read(key: String, returnIfInvalid: String): String? =
        prefs.getString(key, returnIfInvalid)

    /**
     * Modify Shared Prefs
     * */
    fun clear() {
        if (::ANDX_SECURITY_KEY_KEYSET.isInitialized) {
            val clearTextPrefs = cntext.getSharedPreferences(prefName, Context.MODE_PRIVATE)
            val keyKeyset = clearTextPrefs.getString(ANDX_SECURITY_KEY_KEYSET, INVALID_STRING)
            val valueKeyset = clearTextPrefs.getString(ANDX_SECURITY_VALUE_KEYSET, INVALID_STRING)
            if (keyKeyset != null && keyKeyset != INVALID_STRING
                && valueKeyset != null && valueKeyset != INVALID_STRING) {
                if (!clearTextPrefs.edit().clear().commit()) {
                    clearTextPrefs.edit().clear().apply()
                }
                if (!clearTextPrefs.edit().putString(ANDX_SECURITY_KEY_KEYSET, keyKeyset).commit()) {
                    clearTextPrefs.edit().putString(ANDX_SECURITY_KEY_KEYSET, keyKeyset).apply()
                }
                if (!clearTextPrefs.edit().putString(ANDX_SECURITY_VALUE_KEYSET, valueKeyset).commit()) {
                    clearTextPrefs.edit().putString(ANDX_SECURITY_VALUE_KEYSET, valueKeyset).apply()
                }
            }
        } else {
            if (!prefs.edit().clear().commit()) {
                prefs.edit().clear().apply()
            }
        }
    }

    fun remove(key: String) {
        if (!prefs.edit().remove(key).commit()) {
            prefs.edit().remove(key).apply()
        }
    }

    // Boolean
    fun write(key: String, value: Boolean) {
        if (!prefs.edit().putBoolean(key, value).commit()) {
            prefs.edit().putBoolean(key, value).apply()
        }
    }

    // Float
    fun write(key: String, value: Float) {
        if (!prefs.edit().putFloat(key, value).commit()) {
            prefs.edit().putFloat(key, value).apply()
        }
    }

    // Int
    fun write(key: String, value: Int) {
        if (!prefs.edit().putInt(key, value).commit()) {
            prefs.edit().putInt(key, value).apply()
        }
    }

    // Long
    fun write(key: String, value: Long) {
        if (!prefs.edit().putLong(key, value).commit()) {
            prefs.edit().putLong(key, value).apply()
        }
    }

    // Set<String>
    fun write(key: String, value: Set<String>) {
        if (!prefs.edit().putStringSet(key, value).commit()) {
            prefs.edit().putStringSet(key, value).apply()
        }
    }

    // String
    fun write(key: String, value: String) {
        if (!prefs.edit().putString(key, value).commit()) {
            prefs.edit().putString(key, value).apply()
        }
    }
}

An alternative option to using Dagger2 to inject as a @Singleton could be to:

AppPrefs.kt

object AppPrefs {
    lateinit var encryptedPrefs: Prefs
    lateinit var prefs: Prefs

    // Add your key strings here...

    fun initEncryptedPrefs(context: Context) {
        encryptedPrefs =
            Prefs(
                "ENCRYPTED_PREFS",
                context,
                MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
                EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
            )
    }

    fun initPrefs(context: Context) {
        prefs = Prefs("PREFS", context)
    }
}

Application.kt

class Application: Application() {
    override fun onCreate() {
        super.onCreate()
        AppPrefs.initEncryptedPrefs(this.applicationContext)
        AppPrefs.initPrefs(this.applicationContext)
    }
}

Then just call from where ever AppPrefs.prefs or AppPrefs.encryptedPrefs

05nelsonm
  • 311
  • 3
  • 5
1
public class NodeCrypto {

        private String iv = "fedcba9876543210";//Dummy iv (CHANGE IT!)
        private IvParameterSpec ivspec;
        private SecretKeySpec keyspec;
        private Cipher cipher;

        private String SecretKey = "0123456789abcdef";//Dummy secretKey (CHANGE IT!)

        public void doKey(String key)
        {
                ivspec = new IvParameterSpec(iv.getBytes());

                key = padRight(key,16);

                Log.d("hi",key);

                keyspec = new SecretKeySpec(key.getBytes(), "AES");

                try {
                        cipher = Cipher.getInstance("AES/CBC/NoPadding");
                } catch (NoSuchAlgorithmException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                } catch (NoSuchPaddingException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }

        public byte[] encrypt(String text,String key) throws Exception
        {
                if(text == null || text.length() == 0)
                        throw new Exception("Empty string");

                doKey(key);

                byte[] encrypted = null;

                try {
                        cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);

                        encrypted = cipher.doFinal(padString(text).getBytes());
                } catch (Exception e)
                {                       
                        throw new Exception("[encrypt] " + e.getMessage());
                }

                return encrypted;
        }

        public byte[] decrypt(String code,String key) throws Exception
        {
                if(code == null || code.length() == 0)
                        throw new Exception("Empty string");

                byte[] decrypted = null;

                doKey(key);

                try {
                        cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);

                        decrypted = cipher.doFinal(hexToBytes(code));
                } catch (Exception e)
                {
                        throw new Exception("[decrypt] " + e.getMessage());
                }
                return decrypted;
        }



        public static String bytesToHex(byte[] data)
        {
                if (data==null)
                {
                        return null;
                }

                int len = data.length;
                String str = "";
                for (int i=0; i<len; i++) {
                        if ((data[i]&0xFF)<16)
                                str = str + "0" + java.lang.Integer.toHexString(data[i]&0xFF);
                        else
                                str = str + java.lang.Integer.toHexString(data[i]&0xFF);
                }
                return str;
        }


        public static byte[] hexToBytes(String str) {
                if (str==null) {
                        return null;
                } else if (str.length() < 2) {
                        return null;
                } else {
                        int len = str.length() / 2;
                        byte[] buffer = new byte[len];
                        for (int i=0; i<len; i++) {
                                buffer[i] = (byte) Integer.parseInt(str.substring(i*2,i*2+2),16);
                        }
                        return buffer;
                }
        }



        private static String padString(String source)
        {
          char paddingChar = ' ';
          int size = 16;
          int x = source.length() % size;
          int padLength = size - x;

          for (int i = 0; i < padLength; i++)
          {
                  source += paddingChar;
          }

          return source;
        }

        public static String padRight(String s, int n) {
            return String.format("%1$-" + n + "s", s);  
          }
}

-----------------------------------------------
from your activity or class call encrypt or decrypt method before saving or   retriving from SharedPreference
Mohammad nabil
  • 1,010
  • 2
  • 12
  • 23
  • how would you convert the encrypted byte to something that can be saved in a preferences xml file? – behelit Jun 09 '16 at 05:17
  • before saving into preference you should encrypt data and then save. – Mohammad nabil Jun 10 '16 at 06:04
  • I have a feeling that in this example, 1) keys are stored longer than necessary in memory, and 2) the key is actually logged. Typically keys are therefore stored in arrays which are cleared (e.g. filled with null bytes) once they have been consumed. Arrays also make it difficult to "accidentally" log keys, because ``Array#toString()`` just returns the array type and hash. – Michael Jess Aug 02 '18 at 12:11
0

For below API 23 check out the Datum library. It ciphers data asynchronously:

StringDatum stringDatum = provider.new StringDatum("string_key", "default");
stringDatum.setValue("new value");
stringDatum.getValue(value->{
    //got value
});
navid
  • 1,022
  • 9
  • 20
0

If you are worried about root, there is essentially nothing you can do to prevent it from reading internal app data, encrypted or otherwise.

Specifically, encrypting shared preferences using EncryptedSharedPreferences (or by a similar means) won't do anything because it stores the encryption key on the device. Root can then read the encryption key and de-encrypt the data. (Root can access the KeyStore of any app).

Even if you do manage to safely encrypt the data with a key that root does not have access to, when you decrypt it, the data will be in the process's memory where root can then read it.

If you're worried about an attacker who has root, its already game over.