美文网首页
加密和普通的SharePreferences

加密和普通的SharePreferences

作者: 技术勤奋坚持 | 来源:发表于2018-08-18 12:31 被阅读0次

    功能简介:SharePreference工具类,提供两种类型的sharePreference,一种是系统的,另一种是加密的

    一.加密工具类

    /**
     * 加密和普通的SharedPreferences工具类
     * Created by caoshiyao on 2016/8/13.
     */
    public class PreferenceUtil {
    
        private static final String DEFAULT = "DEFAULT";
        private static final String SECURE = "SECURE_YAO";
    
        private static boolean sSecurePreferenceInited = false;
        private static SecurePreferences sSecurePreferences;
        private static SharedPreferences sSharedPreferences;
        private static final Object sLock = new Object();
    
        public static SharedPreferences getDefaultPreference(final Context context) {
            return context.getSharedPreferences(DEFAULT, Context.MODE_PRIVATE);
        }
    
        public static synchronized SharedPreferences getSecurePreference() {
            synchronized (sLock) {
                if (sSecurePreferences == null) {
                    if (sSecurePreferenceInited) {
                        return sSharedPreferences;
                    }
                    throw new IllegalStateException(
                            "initSecurePreference must be called at least once!!!");
                }
                return sSecurePreferences;
            }
        }
    
        /**
         * 初始化SecurePreferences,应该在应用启动的时候调用
         *
         * @param context
         */
        public static void initSecurePreference(final Context context) {
            synchronized (sLock) {
                sSecurePreferenceInited = true;
                if (sSecurePreferences != null) return;
                try {
                    sSecurePreferences = new SecurePreferences(context, "666yao888", SECURE);
                } catch (Exception e) { //部分机型不支持特点的加密算法,导致崩溃
                    sSharedPreferences = context.getSharedPreferences(DEFAULT, Context.MODE_PRIVATE);
                    e.printStackTrace();
                }
            }
        }
    }
    
    

    自定义的加密SecurePreferences

    public class SecurePreferences implements SharedPreferences {
    
        private static final int ORIGINAL_ITERATION_COUNT = 10000;
    
        //the backing pref file
        private SharedPreferences sharedPreferences;
        //secret keys used for enc and dec
        private AesCbcWithIntegrity.SecretKeys keys;
        //the salt used for enc and dec
        private String salt;
        private static boolean sLoggingEnabled = false;
        private static final String TAG = SecurePreferences.class.getName();
        //name of the currently loaded sharedPrefFile, can be null if default
        private String sharedPrefFilename;
    
        /**
         * User password defaults to app generated password that's stores obfucated with the other preference values. Also this uses the Default shared pref file
         *
         * @param context should be ApplicationContext not Activity
         */
        public SecurePreferences(Context context) {
            this(context, "", null);
        }
    
        /**
         * @param context        should be ApplicationContext not Activity
         * @param iterationCount The iteration count for the keys generation
         */
        public SecurePreferences(Context context, int iterationCount) {
            this(context, "", null, iterationCount);
        }
    
        /**
         * @param context            should be ApplicationContext not Activity
         * @param password           user password/code used to generate encryption key.
         * @param sharedPrefFilename name of the shared pref file. If null use the default shared prefs
         */
        public SecurePreferences(Context context, final String password, final String sharedPrefFilename) {
            this(context, password, null, sharedPrefFilename, ORIGINAL_ITERATION_COUNT);
        }
    
        /**
         * @param context        should be ApplicationContext not Activity
         * @param iterationCount The iteration count for the keys generation
         */
        public SecurePreferences(Context context, final String password, final String sharedPrefFilename, int iterationCount) {
            this(context, null, password, null, sharedPrefFilename, iterationCount);
        }
    
        /**
         * @param context            should be ApplicationContext not Activity
         * @param secretKey          that you've generated
         * @param sharedPrefFilename name of the shared pref file. If null use the default shared prefs
         */
        public SecurePreferences(Context context, final AesCbcWithIntegrity.SecretKeys secretKey, final String sharedPrefFilename) {
            this(context, secretKey, null, null, sharedPrefFilename, 0);
        }
    
        /**
         * @param context        should be ApplicationContext not Activity
         * @param iterationCount The iteration count for the keys generation
         */
        public SecurePreferences(Context context, final String password, final String salt, final String sharedPrefFilename, int iterationCount) {
            this(context, null, password, salt, sharedPrefFilename, iterationCount);
        }
    
        private SecurePreferences(Context context, final AesCbcWithIntegrity.SecretKeys secretKey, final String password, final String salt, final String sharedPrefFilename, int iterationCount) {
            if (sharedPreferences == null) {
                sharedPreferences = getSharedPreferenceFile(context, sharedPrefFilename);
            }
    
            this.salt = salt;
    
            if (secretKey != null) {
                keys = secretKey;
            } else if (TextUtils.isEmpty(password)) {
                // Initialize or create encryption key
                try {
                    final String key = generateAesKeyName(context, iterationCount);
    
                    String keyAsString = sharedPreferences.getString(key, null);
                    if (keyAsString == null) {
                        keys = AesCbcWithIntegrity.generateKey();
                        //saving new key
                        boolean committed = sharedPreferences.edit().putString(key, keys.toString()).commit();
                        if (!committed) {
                            Log.w(TAG, "Key not committed to prefs");
                        }
                    } else {
                        keys = AesCbcWithIntegrity.keys(keyAsString);
                    }
    
                    if (keys == null) {
                        throw new GeneralSecurityException("Problem generating Key");
                    }
    
                } catch (GeneralSecurityException e) {
                    if (sLoggingEnabled) {
                        Log.e(TAG, "Error init:" + e.getMessage());
                    }
                    throw new IllegalStateException(e);
                }
            } else {
                //use the password to generate the key
                try {
                    final byte[] saltBytes = getSalt(context).getBytes();
                    keys = AesCbcWithIntegrity.generateKeyFromPassword(password, saltBytes, iterationCount);
    
                    if (keys == null) {
                        throw new GeneralSecurityException("Problem generating Key From Password");
                    }
                } catch (GeneralSecurityException e) {
                    if (sLoggingEnabled) {
                        Log.e(TAG, "Error init using user password:" + e.getMessage());
                    }
                    throw new IllegalStateException(e);
                }
            }
        }
    
        /**
         * if a prefFilename is not defined the getDefaultSharedPreferences is used.
         *
         * @param context should be ApplicationContext not Activity
         * @return
         */
        private SharedPreferences getSharedPreferenceFile(Context context, String prefFilename) {
            this.sharedPrefFilename = prefFilename;
    
            if (TextUtils.isEmpty(prefFilename)) {
                return PreferenceManager
                        .getDefaultSharedPreferences(context);
            } else {
                return context.getSharedPreferences(prefFilename, Context.MODE_PRIVATE);
            }
        }
    
        /**
         * nulls in memory keys
         */
        public void destroyKeys() {
            keys = null;
        }
    
        /**
         * Uses device and application values to generate the pref key for the encryption key
         *
         * @param context        should be ApplicationContext not Activity
         * @param iterationCount The iteration count for the keys generation
         * @return String to be used as the AESkey Pref key
         * @throws GeneralSecurityException if something goes wrong in generation
         */
        private String generateAesKeyName(Context context, int iterationCount) throws GeneralSecurityException {
            final String password = context.getPackageName();
            final byte[] salt = getSalt(context).getBytes();
            AesCbcWithIntegrity.SecretKeys generatedKeyName = AesCbcWithIntegrity.generateKeyFromPassword(password, salt, iterationCount);
    
            return hashPrefKey(generatedKeyName.toString());
        }
    
        /**
         * Gets the hardware serial number of this device.
         *
         * @return serial number or Settings.Secure.ANDROID_ID if not available.
         */
        @SuppressLint("HardwareIds")
        private static String getDeviceSerialNumber(Context context) {
            // We're using the Reflection API because Build.SERIAL is only available
            // since API Level 9 (Gingerbread, Android 2.3).
            try {
                String deviceSerial = (String) Build.class.getField("SERIAL").get(
                        null);
                if (TextUtils.isEmpty(deviceSerial)) {
                    return Settings.Secure.getString(
                            context.getContentResolver(),
                            Settings.Secure.ANDROID_ID);
                } else {
                    return deviceSerial;
                }
            } catch (Exception ignored) {
                // Fall back  to Android_ID
                return Settings.Secure.getString(context.getContentResolver(),
                        Settings.Secure.ANDROID_ID);
            }
        }
    
        /**
         * Gets the salt value
         *
         * @param context used for accessing hardware serial number of this device in case salt is not set
         * @return
         */
        private String getSalt(Context context) {
            if (TextUtils.isEmpty(this.salt)) {
                return getDeviceSerialNumber(context);
            } else {
                return this.salt;
            }
        }
    
    
        /**
         * The Pref keys must be same each time so we're using a hash to obscure the stored value
         *
         * @param prefKey
         * @return SHA-256 Hash of the preference key
         */
        public static String hashPrefKey(String prefKey) {
            final MessageDigest digest;
            try {
                digest = MessageDigest.getInstance("SHA-256");
                byte[] bytes = prefKey.getBytes("UTF-8");
                digest.update(bytes, 0, bytes.length);
    
                return Base64.encodeToString(digest.digest(), AesCbcWithIntegrity.BASE64_FLAGS);
    
            } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
                if (sLoggingEnabled) {
                    Log.w(TAG, "Problem generating hash", e);
                }
            }
            return null;
        }
    
    
        private String encrypt(String cleartext) {
            if (TextUtils.isEmpty(cleartext)) {
                return cleartext;
            }
            try {
                return AesCbcWithIntegrity.encrypt(cleartext, keys).toString();
            } catch (GeneralSecurityException e) {
                if (sLoggingEnabled) {
                    Log.w(TAG, "encrypt", e);
                }
                return null;
            } catch (UnsupportedEncodingException e) {
                if (sLoggingEnabled) {
                    Log.w(TAG, "encrypt", e);
                }
            }
            return null;
        }
    
        /**
         * @param ciphertext
         * @return decrypted plain text, unless decryption fails, in which case null
         */
        private String decrypt(final String ciphertext) {
            if (TextUtils.isEmpty(ciphertext)) {
                return ciphertext;
            }
            try {
                AesCbcWithIntegrity.CipherTextIvMac cipherTextIvMac = new AesCbcWithIntegrity.CipherTextIvMac(ciphertext);
    
                return AesCbcWithIntegrity.decryptString(cipherTextIvMac, keys);
            } catch (GeneralSecurityException | UnsupportedEncodingException e) {
                if (sLoggingEnabled) {
                    Log.w(TAG, "decrypt", e);
                }
            }
            return null;
        }
    
        /**
         * @return map of with decrypted values (excluding the key if present)
         */
        @Override
        public Map<String, String> getAll() {
            //wont be null as per http://androidxref.com/5.1.0_r1/xref/frameworks/base/core/java/android/app/SharedPreferencesImpl.java
            final Map<String, ?> encryptedMap = sharedPreferences.getAll();
            final Map<String, String> decryptedMap = new HashMap<String, String>(
                    encryptedMap.size());
            for (Entry<String, ?> entry : encryptedMap.entrySet()) {
                try {
                    Object cipherText = entry.getValue();
                    //don't include the key
                    if (cipherText != null && !cipherText.equals(keys.toString())) {
                        //the prefs should all be strings
                        decryptedMap.put(entry.getKey(),
                                decrypt(cipherText.toString()));
                    }
                } catch (Exception e) {
                    if (sLoggingEnabled) {
                        Log.w(TAG, "error during getAll", e);
                    }
                    // Ignore issues that unencrypted values and use instead raw cipher text string
                    decryptedMap.put(entry.getKey(),
                            entry.getValue().toString());
                }
            }
            return decryptedMap;
        }
    
        @Override
        public String getString(String key, String defaultValue) {
            final String encryptedValue = sharedPreferences.getString(
                    SecurePreferences.hashPrefKey(key), null);
    
            String decryptedValue = decrypt(encryptedValue);
            if (encryptedValue != null && decryptedValue != null) {
                return decryptedValue;
            } else {
                return defaultValue;
            }
        }
    
        /**
         * Added to get a values as as it can be useful to store values that are
         * already encrypted and encoded
         *
         * @param key          pref key
         * @param defaultValue
         * @return Encrypted value of the key or the defaultValue if no value exists
         */
        public String getEncryptedString(String key, String defaultValue) {
            final String encryptedValue = sharedPreferences.getString(
                    SecurePreferences.hashPrefKey(key), null);
            return (encryptedValue != null) ? encryptedValue : defaultValue;
        }
    
        @Override
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        public Set<String> getStringSet(String key, Set<String> defaultValues) {
            final Set<String> encryptedSet = sharedPreferences.getStringSet(
                    SecurePreferences.hashPrefKey(key), null);
            if (encryptedSet == null) {
                return defaultValues;
            }
            final Set<String> decryptedSet = new HashSet<String>(
                    encryptedSet.size());
            for (String encryptedValue : encryptedSet) {
                decryptedSet.add(decrypt(encryptedValue));
            }
            return decryptedSet;
        }
    
        @Override
        public int getInt(String key, int defaultValue) {
            final String encryptedValue = sharedPreferences.getString(
                    SecurePreferences.hashPrefKey(key), null);
            if (encryptedValue == null) {
                return defaultValue;
            }
            try {
                return Integer.parseInt(decrypt(encryptedValue));
            } catch (NumberFormatException e) {
                throw new ClassCastException(e.getMessage());
            }
        }
    
        @Override
        public long getLong(String key, long defaultValue) {
            final String encryptedValue = sharedPreferences.getString(
                    SecurePreferences.hashPrefKey(key), null);
            if (encryptedValue == null) {
                return defaultValue;
            }
            try {
                return Long.parseLong(decrypt(encryptedValue));
            } catch (NumberFormatException e) {
                throw new ClassCastException(e.getMessage());
            }
        }
    
        @Override
        public float getFloat(String key, float defaultValue) {
            final String encryptedValue = sharedPreferences.getString(
                    SecurePreferences.hashPrefKey(key), null);
            if (encryptedValue == null) {
                return defaultValue;
            }
            try {
                return Float.parseFloat(decrypt(encryptedValue));
            } catch (NumberFormatException e) {
                throw new ClassCastException(e.getMessage());
            }
        }
    
        @Override
        public boolean getBoolean(String key, boolean defaultValue) {
            final String encryptedValue = sharedPreferences.getString(
                    SecurePreferences.hashPrefKey(key), null);
            if (encryptedValue == null) {
                return defaultValue;
            }
            try {
                return Boolean.parseBoolean(decrypt(encryptedValue));
            } catch (NumberFormatException e) {
                throw new ClassCastException(e.getMessage());
            }
        }
    
        @Override
        public boolean contains(String key) {
            return sharedPreferences.contains(SecurePreferences.hashPrefKey(key));
        }
    
    
        /**
         * Cycle through the unencrypt all the current prefs to mem cache, clear, then encypt with key generated from new password.
         * This method can be used if switching from the generated key to a key derived from user password
         * <p>
         * Note: the pref keys will remain the same as they are SHA256 hashes.
         *
         * @param newPassword
         * @param context        should be ApplicationContext not Activity
         * @param iterationCount The iteration count for the keys generation
         */
        @SuppressLint("CommitPrefEdits")
        public void handlePasswordChange(String newPassword, Context context, int iterationCount) throws GeneralSecurityException {
    
            final byte[] salt = getSalt(context).getBytes();
            AesCbcWithIntegrity.SecretKeys newKey = AesCbcWithIntegrity.generateKeyFromPassword(newPassword, salt, iterationCount);
    
            Map<String, ?> allOfThePrefs = sharedPreferences.getAll();
            Map<String, String> unencryptedPrefs = new HashMap<String, String>(allOfThePrefs.size());
            //iterate through the current prefs unencrypting each one
            for (String prefKey : allOfThePrefs.keySet()) {
                Object prefValue = allOfThePrefs.get(prefKey);
                if (prefValue instanceof String) {
                    //all the encrypted values will be Strings
                    final String prefValueString = (String) prefValue;
                    final String plainTextPrefValue = decrypt(prefValueString);
                    unencryptedPrefs.put(prefKey, plainTextPrefValue);
                }
            }
    
            //destroy and clear the current pref file
            destroyKeys();
    
            SharedPreferences.Editor editor = sharedPreferences.edit();
            editor.clear();
            editor.commit();
    
            //refresh the sharedPreferences object ref: I found it was retaining old ref/values
            sharedPreferences = null;
            sharedPreferences = getSharedPreferenceFile(context, sharedPrefFilename);
    
            //assign new key
            this.keys = newKey;
    
            SharedPreferences.Editor updatedEditor = sharedPreferences.edit();
    
            //iterate through the unencryptedPrefs encrypting each one with new key
            Iterator<String> unencryptedPrefsKeys = unencryptedPrefs.keySet().iterator();
            while (unencryptedPrefsKeys.hasNext()) {
                String prefKey = unencryptedPrefsKeys.next();
                String prefPlainText = unencryptedPrefs.get(prefKey);
                updatedEditor.putString(prefKey, encrypt(prefPlainText));
            }
            updatedEditor.commit();
        }
    
        public void handlePasswordChange(String newPassword, Context context) throws GeneralSecurityException {
            handlePasswordChange(newPassword, context, ORIGINAL_ITERATION_COUNT);
        }
    
    
        @Override
        public Editor edit() {
            return new Editor();
        }
    
        /**
         * Wrapper for Android's {@link android.content.SharedPreferences.Editor}.
         * <p>
         * Used for modifying values in a {@link SecurePreferences} object. All
         * changes you make in an editor are batched, and not copied back to the
         * original {@link SecurePreferences} until you call {@link #commit()} or
         * {@link #apply()}.
         */
        public final class Editor implements SharedPreferences.Editor {
            private SharedPreferences.Editor mEditor;
    
            /**
             * Constructor.
             */
            private Editor() {
                mEditor = sharedPreferences.edit();
            }
    
            @Override
            public SharedPreferences.Editor putString(String key, String value) {
                mEditor.putString(SecurePreferences.hashPrefKey(key),
                        encrypt(value));
                return this;
            }
    
            /**
             * This is useful for storing values that have be encrypted by something
             * else or for testing
             *
             * @param key   - encrypted as usual
             * @param value will not be encrypted
             * @return
             */
            public SharedPreferences.Editor putUnencryptedString(String key,
                                                                 String value) {
                mEditor.putString(SecurePreferences.hashPrefKey(key), value);
                return this;
            }
    
            @Override
            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            public SharedPreferences.Editor putStringSet(String key,
                                                         Set<String> values) {
                final Set<String> encryptedValues = new HashSet<String>(
                        values.size());
                for (String value : values) {
                    encryptedValues.add(encrypt(value));
                }
                mEditor.putStringSet(SecurePreferences.hashPrefKey(key),
                        encryptedValues);
                return this;
            }
    
            @Override
            public SharedPreferences.Editor putInt(String key, int value) {
                mEditor.putString(SecurePreferences.hashPrefKey(key),
                        encrypt(Integer.toString(value)));
                return this;
            }
    
            @Override
            public SharedPreferences.Editor putLong(String key, long value) {
                mEditor.putString(SecurePreferences.hashPrefKey(key),
                        encrypt(Long.toString(value)));
                return this;
            }
    
            @Override
            public SharedPreferences.Editor putFloat(String key, float value) {
                mEditor.putString(SecurePreferences.hashPrefKey(key),
                        encrypt(Float.toString(value)));
                return this;
            }
    
            @Override
            public SharedPreferences.Editor putBoolean(String key, boolean value) {
                mEditor.putString(SecurePreferences.hashPrefKey(key),
                        encrypt(Boolean.toString(value)));
                return this;
            }
    
            @Override
            public SharedPreferences.Editor remove(String key) {
                mEditor.remove(SecurePreferences.hashPrefKey(key));
                return this;
            }
    
            @Override
            public SharedPreferences.Editor clear() {
                mEditor.clear();
                return this;
            }
    
            @Override
            public boolean commit() {
                return mEditor.commit();
            }
    
            @Override
            @TargetApi(Build.VERSION_CODES.GINGERBREAD)
            public void apply() {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
                    mEditor.apply();
                } else {
                    commit();
                }
            }
        }
    
        public static boolean isLoggingEnabled() {
            return sLoggingEnabled;
        }
    
        public static void setLoggingEnabled(boolean loggingEnabled) {
            sLoggingEnabled = loggingEnabled;
        }
    
        @Override
        public void registerOnSharedPreferenceChangeListener(
                final OnSharedPreferenceChangeListener listener) {
            sharedPreferences
                    .registerOnSharedPreferenceChangeListener(listener);
        }
    
        /**
         * @param listener    OnSharedPreferenceChangeListener
         * @param decryptKeys Callbacks receive the "key" parameter decrypted
         */
        public void registerOnSharedPreferenceChangeListener(
                final OnSharedPreferenceChangeListener listener, boolean decryptKeys) {
    
            if (!decryptKeys) {
                registerOnSharedPreferenceChangeListener(listener);
            }
        }
    
        @Override
        public void unregisterOnSharedPreferenceChangeListener(
                OnSharedPreferenceChangeListener listener) {
            sharedPreferences
                    .unregisterOnSharedPreferenceChangeListener(listener);
        }
    }
    
    

    其中需要在app的build.gradle中添加AES的依赖库

    compile 'com.scottyab:aes-crypto:0.0.5'

    使用之前先在Application中初始化

    public class App extends Application {
    
        @Override
        public void onCreate() {
            super.onCreate();
            //初始化加密SP
            PreferenceUtil.initSecurePreference(getApplicationContext());
        }
    
    }
    

    二.普通的SharePreferences工具类

    /**
     * 普通的SharedPreferences工具类
     * Created by caoshiyao on 2016/8/13.
     */
    
    public class SPUtil {
    
        private static String PRI_TOKEN = "token";
    
        /**
         * 设置token
         *
         * @param context
         * @param token
         */
        public static void setToken(Context context, String token) {
            SharedPreferences sharedPreferences = context.getSharedPreferences(PRI_TOKEN, Context.MODE_PRIVATE);
            SharedPreferences.Editor edit = sharedPreferences.edit();
            edit.putString("token", token);
            edit.commit();
        }
    
        /**
         * 获取token
         *
         * @param context
         * @return
         */
        public static String getToken(Context context) {
            SharedPreferences sharedPreferences = context.getSharedPreferences(PRI_TOKEN, Context.MODE_PRIVATE);
            return sharedPreferences.getString("token", "");
        }
    
    }
    

    1.保存数据

    String token = "1234567";
    //普通SP工具类
    SPUtil.setToken(MainActivity.this, token);
    //加密SP工具类,使用加密存储
    PreferenceUtil.getSecurePreference()
                    .edit()
                    .putString("secureToken", token)
                    .commit();
    //加密SP工具类,使用非加密存储
    PreferenceUtil.getDefaultPreference(MainActivity.this)
                    .edit()
                    .putString("",token)
                    .commit();
    

    2.获取数据

    //普通SP工具类
    String tokenInfo = SPUtil.getToken(MainActivity.this);
    //加密工具类获取加密数据
    String secureToken = PreferenceUtil.getSecurePreference().getString("secureToken", "");
    //加密工具类获取非加密数据
    String noEncryptionToken = PreferenceUtil.getDefaultPreference(MainActivity.this).getString("", "");
    

    相关文章

      网友评论

          本文标题:加密和普通的SharePreferences

          本文链接:https://www.haomeiwen.com/subject/fnffiftx.html