package org.andengine.util.preferences; import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.andengine.util.exception.MethodNotYetImplementedException; import org.andengine.util.preferences.exception.SecureSharedPreferencesException; import android.annotation.TargetApi; import android.content.SharedPreferences; import android.os.Build; import android.util.Base64; /** * (c) 2013 Nicolas Gramlich * * @author Nicolas Gramlich * @since 20:09:38 - 13.04.2013 */ public class SecureSharedPreferences implements SharedPreferences { // =========================================================== // Constants // =========================================================== protected static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding"; protected static final String KEY_HASH_TRANSFORMATION = "SHA-256"; protected static final String CHARSET = "UTF-8"; // =========================================================== // Fields // =========================================================== protected final SharedPreferences mDelegate; protected final boolean mEncryptKeys; protected final boolean mEncryptValues; protected final Cipher mEncryptCipher; protected final Cipher mDecryptCipher; protected final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(true); // =========================================================== // Constructors // =========================================================== public SecureSharedPreferences(final SharedPreferences pDelegate, final String pSecureKey) throws SecureSharedPreferencesException { this(pDelegate, pSecureKey, true, true); } public SecureSharedPreferences(final SharedPreferences pDelegate, final String pSecureKey, final boolean pEncryptKeys, final boolean pEncryptValues) throws SecureSharedPreferencesException { this.mDelegate = pDelegate; this.mEncryptKeys = pEncryptKeys; this.mEncryptValues = pEncryptValues; try { this.mEncryptCipher = Cipher.getInstance(SecureSharedPreferences.CIPHER_TRANSFORMATION); this.mDecryptCipher = Cipher.getInstance(SecureSharedPreferences.CIPHER_TRANSFORMATION); final IvParameterSpec ivSpec = this.getIvParameterSpec(); final SecretKeySpec secretKey = this.getSecretKeySpec(pSecureKey); this.mEncryptCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); this.mDecryptCipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec); } catch (final GeneralSecurityException e) { throw new SecureSharedPreferencesException(e); } catch (final UnsupportedEncodingException e) { throw new SecureSharedPreferencesException(e); } } // =========================================================== // Getter & Setter // =========================================================== public Lock getReadLock() { return this.mReadWriteLock.readLock(); } public Lock getWriteLock() { return this.mReadWriteLock.writeLock(); } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public Editor edit() { return new Editor(); } @Override public Map<String, ?> getAll() { throw new MethodNotYetImplementedException(); } @Override public boolean getBoolean(final String pKey, final boolean pDefaultValue) { final String encryptedKey = this.encryptKey(pKey); final String value = this.mDelegate.getString(encryptedKey, null); if (value == null) { return pDefaultValue; } else { final String decryptedValue = this.decryptValue(value); return Boolean.parseBoolean(decryptedValue); } } @Override public int getInt(final String pKey, final int pDefaultValue) { final String encryptedKey = this.encryptKey(pKey); final String value = this.mDelegate.getString(encryptedKey, null); if (value == null) { return pDefaultValue; } else { final String decryptedValue = this.decryptValue(value); return Integer.parseInt(decryptedValue); } } @Override public long getLong(final String pKey, final long pDefaultValue) { final String encryptedKey = this.encryptKey(pKey); final String value = this.mDelegate.getString(encryptedKey, null); if (value == null) { return pDefaultValue; } else { final String decryptedValue = this.decryptValue(value); return Long.parseLong(decryptedValue); } } @Override public float getFloat(final String pKey, final float pDefaultValue) { final String encryptedKey = this.encryptKey(pKey); final String value = this.mDelegate.getString(encryptedKey, null); if (value == null) { return pDefaultValue; } else { final String decryptedValue = this.decryptValue(value); return Float.parseFloat(decryptedValue); } } @Override public String getString(final String pKey, final String pDefaultValue) { final String encryptedKey = this.encryptKey(pKey); final String value = this.mDelegate.getString(encryptedKey, null); if (value == null) { return pDefaultValue; } else { final String decryptedValue = this.decryptValue(value); return decryptedValue; } } @Override public boolean contains(final String pKey) { return this.mDelegate.contains(pKey); } @Override public void registerOnSharedPreferenceChangeListener(final OnSharedPreferenceChangeListener pOnSharedPreferenceChangeListener) { this.mDelegate.registerOnSharedPreferenceChangeListener(pOnSharedPreferenceChangeListener); } @Override public void unregisterOnSharedPreferenceChangeListener(final OnSharedPreferenceChangeListener pOnSharedPreferenceChangeListener) { this.mDelegate.unregisterOnSharedPreferenceChangeListener(pOnSharedPreferenceChangeListener); } @Override public Set<String> getStringSet(final String pKey, final Set<String> pStringSet) { throw new MethodNotYetImplementedException(); } // =========================================================== // Methods // =========================================================== protected String encryptKey(final String pKey) { if (this.mEncryptKeys) { return this.encrypt(pKey); } else { return pKey; } } protected String encryptValue(final String pValue) { if (this.mEncryptValues) { return this.encrypt(pValue); } else { return pValue; } } protected String decryptKey(final String pKey) { if (this.mEncryptKeys) { return this.decrypt(pKey); } else { return pKey; } } protected String decryptValue(final String pValue) { if (this.mEncryptValues) { return this.decrypt(pValue); } else { return pValue; } } protected IvParameterSpec getIvParameterSpec() { final int blockSize = this.mEncryptCipher.getBlockSize(); final byte[] iv = new byte[blockSize]; System.arraycopy("abcdefghijklmnopqrstuvwxzyABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".getBytes(), 0, iv, 0, blockSize); return new IvParameterSpec(iv); } protected SecretKeySpec getSecretKeySpec(final String pKey) throws UnsupportedEncodingException, NoSuchAlgorithmException { final byte[] keyBytes = this.createKeyBytes(pKey); return new SecretKeySpec(keyBytes, SecureSharedPreferences.CIPHER_TRANSFORMATION); } protected byte[] createKeyBytes(final String pKey) throws UnsupportedEncodingException, NoSuchAlgorithmException { final MessageDigest md = MessageDigest.getInstance(SecureSharedPreferences.KEY_HASH_TRANSFORMATION); md.reset(); final byte[] keyBytes = md.digest(pKey.getBytes(SecureSharedPreferences.CHARSET)); return keyBytes; } protected String encrypt(final String pPlainText) throws SecureSharedPreferencesException { byte[] secureValue; try { secureValue = SecureSharedPreferences.crypt(this.mEncryptCipher, pPlainText.getBytes(SecureSharedPreferences.CHARSET)); } catch (final UnsupportedEncodingException e) { throw new SecureSharedPreferencesException(e); } final String secureValueEncoded = Base64.encodeToString(secureValue, Base64.NO_WRAP); return secureValueEncoded; } protected String decrypt(final String pCipherText) { final byte[] securedValue = Base64.decode(pCipherText, Base64.NO_WRAP); final byte[] pValue = SecureSharedPreferences.crypt(this.mDecryptCipher, securedValue); try { return new String(pValue, SecureSharedPreferences.CHARSET); } catch (final UnsupportedEncodingException e) { throw new SecureSharedPreferencesException(e); } } protected static byte[] crypt(final Cipher pCipher, final byte[] pBytes) throws SecureSharedPreferencesException { try { return pCipher.doFinal(pBytes); } catch (final Exception e) { throw new SecureSharedPreferencesException(e); } } // =========================================================== // Inner and Anonymous Classes // =========================================================== public class Editor implements SharedPreferences.Editor { // =========================================================== // Constants // =========================================================== // =========================================================== // Fields // =========================================================== protected final SharedPreferences.Editor mDelegate; // =========================================================== // Constructors // =========================================================== public Editor() { this.mDelegate = SecureSharedPreferences.this.mDelegate.edit(); } // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public Editor putBoolean(final String pKey, final boolean pValue) { final String encryptedKey = SecureSharedPreferences.this.encryptKey(pKey); final String encryptedValue = SecureSharedPreferences.this.encryptValue(Boolean.toString(pValue)); this.mDelegate.putString(encryptedKey, encryptedValue); return this; } @Override public Editor putInt(final String pKey, final int pValue) { final String encryptedKey = SecureSharedPreferences.this.encrypt(pKey); final String encryptValue = SecureSharedPreferences.this.encryptValue(Integer.toString(pValue)); this.mDelegate.putString(encryptedKey, encryptValue); return this; } @Override public Editor putLong(final String pKey, final long pValue) { final String encryptedKey = SecureSharedPreferences.this.encrypt(pKey); final String encryptValue = SecureSharedPreferences.this.encryptValue(Long.toString(pValue)); this.mDelegate.putString(encryptedKey, encryptValue); return this; } @Override public Editor putFloat(final String pKey, final float pValue) { final String encryptedKey = SecureSharedPreferences.this.encrypt(pKey); final String encryptedVaue = SecureSharedPreferences.this.encryptValue(Float.toString(pValue)); this.mDelegate.putString(encryptedKey, encryptedVaue); return this; } @Override public Editor putString(final String pKey, final String pValue) { final String encryptedKey = SecureSharedPreferences.this.encrypt(pKey); final String encryptValue = SecureSharedPreferences.this.encryptValue(pValue); this.mDelegate.putString(encryptedKey, encryptValue); return this; } @Override public Editor putStringSet(final String pKey, final Set<String> pStringSet) { throw new MethodNotYetImplementedException(); } @Override public Editor remove(final String pKey) { final String encryptedKey = SecureSharedPreferences.this.encrypt(pKey); this.mDelegate.remove(encryptedKey); return this; } @Override public Editor clear() { this.mDelegate.clear(); return this; } @Override public boolean commit() { return this.mDelegate.commit(); } @TargetApi(Build.VERSION_CODES.GINGERBREAD) @Override public void apply() { this.mDelegate.apply(); } // =========================================================== // Methods // =========================================================== // =========================================================== // Inner and Anonymous Classes // =========================================================== } }