Android Open Source - android-wallet Secure Preferences






From Project

Back to project page android-wallet.

License

The source code is released under:

GNU General Public License

If you think the Android project android-wallet listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2013, Daniel Abraham/*from w w w  . ja v a 2 s.  c  o m*/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.coinprism.utils;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.util.Base64;
import android.util.Log;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * Wrapper class for Android's {@link SharedPreferences} interface, which adds a layer of
 * encryption to the persistent storage and retrieval of sensitive key-value pairs of primitive
 * data types.
 * <p/>
 * This class provides important - but nevertheless imperfect - protection against simple attacks
 * by casual snoopers. It is crucial to remember that even encrypted data may still be susceptible
 * to attacks, especially on rooted or stolen devices!
 * <p/>
 * This class requires API level 8 (Android 2.2, a.k.a. "Froyo") or greater.
 *
 * @see <a href="http://www.codeproject.com/Articles/549119/Encryption-Wrapper-for-Android-SharedPreferences">CodeProject article</a>
 */
public class SecurePreferences implements SharedPreferences
{
    private static SharedPreferences sFile;
    private static byte[] sKey;

    /**
     * Constructor.
     *
     * @param context the caller's context
     */
    public SecurePreferences(Context context)
    {
        // Proxy design pattern
        if (SecurePreferences.sFile == null)
        {
            SecurePreferences.sFile = PreferenceManager.getDefaultSharedPreferences(context);
        }
        // Initialize encryption/decryption key
        try
        {
            final String key = SecurePreferences.generateAesKeyName(context);
            String value = SecurePreferences.sFile.getString(key, null);
            if (value == null)
            {
                value = SecurePreferences.generateAesKeyValue();
                SecurePreferences.sFile.edit().putString(key, value).commit();
            }
            SecurePreferences.sKey = SecurePreferences.decode(value);
        }
        catch (Exception e)
        {
            throw new IllegalStateException(e);
        }
    }

    private static String encode(byte[] input)
    {
        return Base64.encodeToString(input, Base64.NO_PADDING | Base64.NO_WRAP);
    }

    private static byte[] decode(String input)
    {
        return Base64.decode(input, Base64.NO_PADDING | Base64.NO_WRAP);
    }

    private static String generateAesKeyName(Context context) throws InvalidKeySpecException,
        NoSuchAlgorithmException
    {
        final char[] password = context.getPackageName().toCharArray();
        final byte[] salt = Settings.Secure.getString(context.getContentResolver(),
            Settings.Secure.ANDROID_ID).getBytes();

        // Number of PBKDF2 hardening rounds to use, larger values increase
        // computation time, you should select a value that causes
        // computation to take >100ms
        final int iterations = 1000;

        // Generate a 256-bit key
        final int keyLength = 256;

        final KeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
        return SecurePreferences.encode(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
            .generateSecret(spec).getEncoded());
    }

    private static String generateAesKeyValue() throws NoSuchAlgorithmException
    {
        // Do *not* seed secureRandom! Automatically seeded from system entropy
        final SecureRandom random = new SecureRandom();

        // Use the largest AES key length which is supported by the OS
        final KeyGenerator generator = KeyGenerator.getInstance("AES");
        try
        {
            generator.init(256, random);
        }
        catch (Exception e)
        {
            try
            {
                generator.init(192, random);
            }
            catch (Exception e1)
            {
                generator.init(128, random);
            }
        }
        return SecurePreferences.encode(generator.generateKey().getEncoded());
    }

    private static String encrypt(String cleartext)
    {
        if (cleartext == null || cleartext.length() == 0)
        {
            return cleartext;
        }
        try
        {
            final Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(SecurePreferences.sKey, "AES"));
            return SecurePreferences.encode(cipher.doFinal(cleartext.getBytes("UTF-8")));
        }
        catch (Exception e)
        {
            Log.w(SecurePreferences.class.getName(), "encrypt", e);
            return null;
        }
    }

    private static String decrypt(String ciphertext)
    {
        if (ciphertext == null || ciphertext.length() == 0)
        {
            return ciphertext;
        }
        try
        {
            final Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(SecurePreferences.sKey, "AES"));
            return new String(cipher.doFinal(SecurePreferences.decode(ciphertext)), "UTF-8");
        }
        catch (Exception e)
        {
            Log.w(SecurePreferences.class.getName(), "decrypt", e);
            return null;
        }
    }

    @Override
    public Map<String, String> getAll()
    {
        final Map<String, ?> encryptedMap = SecurePreferences.sFile.getAll();
        final Map<String, String> decryptedMap = new HashMap<String, String>(encryptedMap.size());
        for (Entry<String, ?> entry : encryptedMap.entrySet())
        {
            try
            {
                decryptedMap.put(SecurePreferences.decrypt(entry.getKey()),
                    SecurePreferences.decrypt(entry.getValue().toString()));
            }
            catch (Exception e)
            {
                // Ignore unencrypted key/value pairs
            }
        }
        return decryptedMap;
    }

    @Override
    public String getString(String key, String defaultValue)
    {
        final String encryptedValue =
            SecurePreferences.sFile.getString(SecurePreferences.encrypt(key), null);
        return (encryptedValue != null) ? SecurePreferences.decrypt(encryptedValue) : defaultValue;
    }

    @Override
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public Set<String> getStringSet(String key, Set<String> defaultValues)
    {
        final Set<String> encryptedSet =
            SecurePreferences.sFile.getStringSet(SecurePreferences.encrypt(key), null);
        if (encryptedSet == null)
        {
            return defaultValues;
        }
        final Set<String> decryptedSet = new HashSet<String>(encryptedSet.size());
        for (String encryptedValue : encryptedSet)
        {
            decryptedSet.add(SecurePreferences.decrypt(encryptedValue));
        }
        return decryptedSet;
    }

    @Override
    public int getInt(String key, int defaultValue)
    {
        final String encryptedValue =
            SecurePreferences.sFile.getString(SecurePreferences.encrypt(key), null);
        if (encryptedValue == null)
        {
            return defaultValue;
        }
        try
        {
            return Integer.parseInt(SecurePreferences.decrypt(encryptedValue));
        }
        catch (NumberFormatException e)
        {
            throw new ClassCastException(e.getMessage());
        }
    }

    @Override
    public long getLong(String key, long defaultValue)
    {
        final String encryptedValue =
            SecurePreferences.sFile.getString(SecurePreferences.encrypt(key), null);
        if (encryptedValue == null)
        {
            return defaultValue;
        }
        try
        {
            return Long.parseLong(SecurePreferences.decrypt(encryptedValue));
        }
        catch (NumberFormatException e)
        {
            throw new ClassCastException(e.getMessage());
        }
    }

    @Override
    public float getFloat(String key, float defaultValue)
    {
        final String encryptedValue =
            SecurePreferences.sFile.getString(SecurePreferences.encrypt(key), null);
        if (encryptedValue == null)
        {
            return defaultValue;
        }
        try
        {
            return Float.parseFloat(SecurePreferences.decrypt(encryptedValue));
        }
        catch (NumberFormatException e)
        {
            throw new ClassCastException(e.getMessage());
        }
    }

    @Override
    public boolean getBoolean(String key, boolean defaultValue)
    {
        final String encryptedValue =
            SecurePreferences.sFile.getString(SecurePreferences.encrypt(key), null);
        if (encryptedValue == null)
        {
            return defaultValue;
        }
        try
        {
            return Boolean.parseBoolean(SecurePreferences.decrypt(encryptedValue));
        }
        catch (NumberFormatException e)
        {
            throw new ClassCastException(e.getMessage());
        }
    }

    @Override
    public boolean contains(String key)
    {
        return SecurePreferences.sFile.contains(SecurePreferences.encrypt(key));
    }

    @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 static class Editor implements SharedPreferences.Editor
    {
        private SharedPreferences.Editor mEditor;

        /**
         * Constructor.
         */
        private Editor()
        {
            mEditor = SecurePreferences.sFile.edit();
        }

        @Override
        public SharedPreferences.Editor putString(String key, String value)
        {
            mEditor.putString(SecurePreferences.encrypt(key), SecurePreferences.encrypt(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(SecurePreferences.encrypt(value));
            }
            mEditor.putStringSet(SecurePreferences.encrypt(key), encryptedValues);
            return this;
        }

        @Override
        public SharedPreferences.Editor putInt(String key, int value)
        {
            mEditor.putString(SecurePreferences.encrypt(key),
                SecurePreferences.encrypt(Integer.toString(value)));
            return this;
        }

        @Override
        public SharedPreferences.Editor putLong(String key, long value)
        {
            mEditor.putString(SecurePreferences.encrypt(key),
                SecurePreferences.encrypt(Long.toString(value)));
            return this;
        }

        @Override
        public SharedPreferences.Editor putFloat(String key, float value)
        {
            mEditor.putString(SecurePreferences.encrypt(key),
                SecurePreferences.encrypt(Float.toString(value)));
            return this;
        }

        @Override
        public SharedPreferences.Editor putBoolean(String key, boolean value)
        {
            mEditor.putString(SecurePreferences.encrypt(key),
                SecurePreferences.encrypt(Boolean.toString(value)));
            return this;
        }

        @Override
        public SharedPreferences.Editor remove(String key)
        {
            mEditor.remove(SecurePreferences.encrypt(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()
        {
            mEditor.apply();
        }
    }

    @Override
    public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener)
    {
        SecurePreferences.sFile.registerOnSharedPreferenceChangeListener(listener);
    }

    @Override
    public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener)
    {
        SecurePreferences.sFile.unregisterOnSharedPreferenceChangeListener(listener);
    }
}




Java Source Code List

com.coinprism.model.APIClient.java
com.coinprism.model.APIException.java
com.coinprism.model.AddressBalance.java
com.coinprism.model.AssetBalance.java
com.coinprism.model.AssetDefinition.java
com.coinprism.model.BalanceLoader.java
com.coinprism.model.CoinprismWalletApplication.java
com.coinprism.model.DownloadImageTask.java
com.coinprism.model.QRCodeEncoder.java
com.coinprism.model.SingleAssetTransaction.java
com.coinprism.model.TransactionsLoader.java
com.coinprism.model.WalletConfiguration.java
com.coinprism.model.WalletState.java
com.coinprism.utils.Formatting.java
com.coinprism.utils.SecurePreferences.java
com.coinprism.utils.StretchingImageView.java
com.coinprism.wallet.ApplicationTest.java
com.coinprism.wallet.ProgressDialog.java
com.coinprism.wallet.QRCodeDialog.java
com.coinprism.wallet.UserPreferences.java
com.coinprism.wallet.WalletOverview.java
com.coinprism.wallet.adapter.AssetBalanceAdapter.java
com.coinprism.wallet.adapter.AssetSelectorAdapter.java
com.coinprism.wallet.adapter.TabsPagerAdapter.java
com.coinprism.wallet.adapter.TransactionAdapter.java
com.coinprism.wallet.fragment.BalanceTab.java
com.coinprism.wallet.fragment.SendTab.java
com.coinprism.wallet.fragment.TransactionsTab.java