How to Secure Android Shared Preferences?
Solution 1
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();
Solution 2
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
Solution 3
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()
}
Solution 4
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",""));
Solution 5
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()
}
Harsh Dattani
What do I do: By starting of day a student, converts into a learner by end of afternoon and later in evening a work ninja! Spending at least a hour on Stack Overflow. Articles written by Me: Android Reverse Engineering Tutorial 5 Parts article series on Android Reverse Engineering Follow me: \ Facebook: /harshdattani G+: +harshdattani Twitter: /dattaniharsh
Updated on July 05, 2022Comments
-
Harsh Dattani almost 2 years
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 wholexml
file. This is generic question, various encryption methods discussed as answers here can be helpful to all developers in securing apps.