如何保护 Android 共享首选项?

Posted

技术标签:

【中文标题】如何保护 Android 共享首选项?【英文标题】:How to Secure Android Shared Preferences? 【发布时间】:2015-07-20 20:08:46 【问题描述】:

SharedPreferencesandroid 应用中的常见存储位置是:

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

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

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

我们都知道我们可以在shared_pref's xml 文件中加密和保存数据,但这不仅是 100% 安全的,因此需要使用密钥对整个文件进行加密。在了解加密整个 xml 文件的各种方法方面需要帮助。这是一个通用问题,此处讨论的各种加密方法作为答案可以帮助所有开发人员保护应用程序。

【问题讨论】:

【参考方案1】:

更新答案:

Android 已在其 Jetpack 库中发布了带有 EncryptedSharedPreferences 的安全库。

编辑:使用 v1.1.0 版本,您可以支持Lollipop (API level 21) 及更高版本

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

【讨论】:

你不能加密整个文件,如果你想这样做,你需要一个自定义的首选项实现,并且可能是一个好主意来制作你自己的解析器。这就是我所做的。我创建了一个新的基于 JNI 的库(c++ 代码 - 不那么容易反编译)并使用 3KTDES 加密来保护整个东西 SharedPreferences 的工作原理是一个众所周知的事实,即使这个事实使它们不安全, Base64 不是加密的。不要将其用于加密。 ***.com/questions/4070693/… 所有提到的解决方案都是这样工作的:让我们用锁来保护门。但是把钥匙放在哪里呢?让我们把钥匙放在门旁边。 结论:所有解决方案都没有提供任何安全性——它们都只是使用模糊来隐藏数据。请注意这一点! Base64 是编码(不是加密),讨论是关于加密的,所以在建议的方法中看起来不合适。 如何支持这个api level 21+?【参考方案2】:

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

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

【讨论】:

你有任何关于如何实现它的例子吗? @GMX 打开我上面分享的链接,页面顶部有一个例子 EncryptedSharedPreferences 似乎是一个不错的选择,但有两个缺点:1.它在 alpha 通道中 2.这个库的最小 sdk 版本是 23 现在也支持 Lollipop (21) 版本 @UsmanRana,谢谢,我不知道。 Source: '现在支持棒棒糖(API 级别 21+)。请注意,AndroidKeyStore 不适用于 API 21 和 22'。【参考方案3】:

您需要在 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()

【讨论】:

这对我有用。前面的示例使用了已弃用的 api 旧 SDKS 的回退是一个不错的选择【参考方案4】:

您应该加密您的数据并写入 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;
    


然后您可以通过如下加密数据来写入 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()

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

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

【讨论】:

并不比保存为纯文本更好,看看这个答案:***.com/a/26077852/4438470【参考方案5】:

如果您想支持 Android 5.0(API 级别 21)及更高版本

使用以下实现:

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

或从this source.获取最新消息

那么

首先创建一个主密钥如下:

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

创建shared preferences后如下:

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

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

with(sharedPreferences.edit()) 
    putString(Values.SP_USER_ID, personId)
    putString(Values.SP_USER_NAME, binding.editTextTextPersonName.text.toString())
    apply()

【讨论】:

【参考方案6】:

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

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

Care :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;

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

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

检索偏好值。

getEncryptedSharedPreferences().getString("ENCRYPTDATA", "defvalue")

【讨论】:

在发布模式下我是这个错误'引起:java.lang.RuntimeException: Field keySize_ for'。你以前见过这个错误吗? 我从来没有经历过。也许 key 的大小有限制?什么是完整的堆栈跟踪? 我在这里发布了一个关于堆栈跟踪的问题:***.com/questions/62135766/… 我们可以将现有的 sharedPreferences 迁移到加密的 sharepreferences 吗? 如果你的应用没有发布,是的。然后,这取决于您是否要保留实际值。否则,您可以“手动”执行此操作。检查旧的偏好值是否存在,如果存在,则使用 getEncryptedSharedPreferences().edit().putXX 迁移到安全值。然后将旧首选项值存储为已迁移(使用特殊标志或字符串)。当您检索首选项时,请检查旧值是否已标记。【参考方案7】:

Base64 不是加密!不要使用它!是的,“root”用户可以访问该数据。您可以做的一件事是使用 AES 加密该数据或使用单个 NoSQL 数据库文件并加密该文件。当应用程序打开时,您解密数据库并使用它来存储信息或独立加密所有文件。

看这里:https://code.tutsplus.com/tutorials/storing-data-securely-on-android--cms-30558

【讨论】:

【参考方案8】:

Kotlin 使用 anrdoidx 的 security-crypto 库(最低 API 23)的双重用途加密和未加密共享首选项示例。

EncryptedSharedPreferences 的体面介绍:garageprojects.tech/

我使用 Dagger2 在需要的地方将其作为@Singleton 注入。

使用 Dagger 模块中的 @Name 注释来区分 SharedPreferences 实例,您可以有 2 个单独的 .xml 文件(1 个加密,1 个未加密)来读取/写入/写入。

对 Dagger2 的体面介绍:codinginflow.com 以下代码的 Dagger 模块和字段注入示例:***.com

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


using Dagger2 to inject as a @Singleton 的替代选项可以是:

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)
    

然后从任何地方拨打电话AppPrefs.prefsAppPrefs.encryptedPrefs

【讨论】:

【参考方案9】:
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

【讨论】:

如何将加密字节转换为可以保存在首选项 xml 文件中的内容? 在保存到首选项之前,您应该加密数据然后保存。 我有一种感觉,在这个例子中,1) 密钥在内存中的存储时间超过了必要的时间,2) 密钥实际上已被记录。因此,密钥通常存储在数组中,一旦它们被使用就会被清除(例如,用空字节填充)。数组也使“意外”记录键变得困难,因为Array#toString() 只返回数组类型和哈希。【参考方案10】:

对于 API 23 以下,请查看 Datum 库。它对数据进行异步加密:

StringDatum stringDatum = provider.new StringDatum("string_key", "default");
stringDatum.setValue("new value");
stringDatum.getValue(value->
    //got value
);

【讨论】:

以上是关于如何保护 Android 共享首选项?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Android Studio 查看共享首选项文件?

如何使用android中的共享首选项将数据保存在editText中[重复]

iOS 等效于 Android 共享首选项

来自共享首选项的android数组

如何将 HashMap 保存到共享首选项?

Android 共享首选项分配不会在模拟器会话之间持续存在