如何保护Android共享首选项?

Har*_*ani 25 security encryption android sharedpreferences

SharedPreferences存储在Android应用中的常见位置是:

/data/data/<package name>/shared_prefs/<filename.xml>
Run Code Online (Sandbox Code Playgroud)

具有root权限的用户可以导航到此位置并可以更改其值.保护它的需要非常重要.

我们可以通过多少种方式加密整个shared_pref's xml文件?

我们都知道我们可以加密并保存shared_pref's xml文件中的数据,但这不仅100%安全,因此需要用密钥加密整个文件.需要帮助了解加密整个xml文件的各种方法.这是一般性问题,这里讨论的各种加密方法可以帮助所有开发人员保护应用程序.

Boj*_*man 43

您应该注意Android的共享首选项是基于XML键值的.你无法改变这个事实(因为它会破坏它的解析器),最多你可以加密密钥和值,所以root用户可以阅读,但不会有任何想法他正在阅读.

为此,您可以使用这样的简单加密

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();
Run Code Online (Sandbox Code Playgroud)

这就是你如何使用它

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();
Run Code Online (Sandbox Code Playgroud)

你应该知道强硬,__CODE__从来没有建立安全,这只是一种坚持数据的简单方法.

您应该也知道我使用的加密并不是最安全的,但它很简单.

有几个库可以提供更好的加密,就像这些

但他们都认为文件的格式仍然是XML,而且它是基于键值的.你无法改变这一事实.见下文.

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();
Run Code Online (Sandbox Code Playgroud)

如果安全性是一个问题,除了__CODE__基于键值和XML格式的事实之外,您还需要完全避免它.

  • Base64不加密.不要用它来加密.http://stackoverflow.com/questions/4070693/what-is-the-purpose-of-base-64-encoding-and-why-it-used-in-http-basic-authentica#answer-4070709 (102认同)
  • 所有提到的解决方案都以这种方式工作:*让我们用锁来保护门.但是在哪里放钥匙?让我们把钥匙放在门旁.*结论:所有解决方案都不提供任何安全性 - 他们都只是使用默默无闻来隐藏数据.请注意这个! (8认同)
  • 如何支持此API级别21+? (2认同)

F.M*_*sir 16

如果您想支持Android 5.0(API级别21)及以上

使用以下实现:

implementation "androidx.security:security-crypto:1.0.0-rc04"
Run Code Online (Sandbox Code Playgroud)

或者从这个来源获取最新的

然后

首先创建主密钥如下:

val masterKey = MasterKey.Builder(context)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build()
Run Code Online (Sandbox Code Playgroud)

创建后shared preferences如下:

    val sharedPreferences = EncryptedSharedPreferences.create(
        context,
        "secret_shared_prefs",
        masterKey,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
Run Code Online (Sandbox Code Playgroud)

然后像平常一样使用它,例如:

with(sharedPreferences.edit()) {
    putString(Values.SP_USER_ID, personId)
    putString(Values.SP_USER_NAME, binding.editTextTextPersonName.text.toString())
    apply()
}
Run Code Online (Sandbox Code Playgroud)


小智 13

Google已将其EncryptedSharedPreferences作为androidx的一部分发布,我相信这应该是加密首选项的首选方法。

请参阅https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences

  • 现在也支持 Lollipop (21) 版本 (3认同)
  • EncryptedSharedPreferences 似乎是一个不错的选择,但有两个缺点: 1. 它处于 alpha 通道 2. 该库的最小 sdk 版本是 23 (2认同)

Zha*_*har 8

完整答案(API 级别 23+)。首先你需要来自 androidx 的加密货币。

implementation "androidx.security:security-crypto:1.0.0-alpha02"
Run Code Online (Sandbox Code Playgroud)

注意:SharedPreferences 和 EncryptedSharedPreferences 之间存在显着的性能差异。您应该注意到EncryptedSharedPreferences.create(...)不是那么快,因此您应该必须存储一个实例。

然后您必须使用它来检索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;
}
Run Code Online (Sandbox Code Playgroud)

您只需使用“标准方式”之类的偏好即可。保存它:

getEncryptedSharedPreferences().edit()
        .putString("ENCRYPTDATA", text)
        .apply()
Run Code Online (Sandbox Code Playgroud)

检索偏好值。

getEncryptedSharedPreferences().getString("ENCRYPTDATA", "defvalue")
Run Code Online (Sandbox Code Playgroud)


Moh*_*Zek 7

您需要在 API 23 下处理 Verisons

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()
}
Run Code Online (Sandbox Code Playgroud)


Bay*_*kin 5

您应该加密数据并写入SharedPreferences.如果要获取此数据,则应从SharedPreferences进行解密.你需要以下帮助类

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

}}
Run Code Online (Sandbox Code Playgroud)

然后,您可以通过加密数据来编写SharedPreferences,如下所示

 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()
Run Code Online (Sandbox Code Playgroud)

您最终可以通过以下方式读取SharedPreferences数据.这样,敏感信息将更安全,同时保持在手机的硬件级别

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


归档时间:

查看次数:

39636 次

最近记录:

5 年,11 月 前