Project: Eclipse
package com.mobilesorcery.sdk.internal;
 
import java.nio.charset.Charset; 
import java.security.GeneralSecurityException; 
import java.security.SecureRandom; 
import java.util.HashMap; 
import java.util.Map; 
 
import javax.crypto.Cipher; 
import javax.crypto.SecretKey; 
import javax.crypto.SecretKeyFactory; 
import javax.crypto.spec.PBEKeySpec; 
import javax.crypto.spec.PBEParameterSpec; 
 
import com.mobilesorcery.sdk.core.IPropertyOwner; 
import com.mobilesorcery.sdk.core.IProvider; 
import com.mobilesorcery.sdk.core.ISecurePropertyOwner; 
import com.mobilesorcery.sdk.core.PropertyUtil; 
import com.mobilesorcery.sdk.core.SecurePropertyException; 
import com.mobilesorcery.sdk.core.Util; 
 
/**
 * A utility class for encrypting/decrypting values. Encryption only occurs at 
 * save time and decryption only at load time, and the decrypted value is always 
 * stored in memory in cleartext. 
 * 
 * @author Mattias Bybro 
 * 
 */
 
public class SecureProperties implements ISecurePropertyOwner { 
 
 private final static String NULL_VALUE = "__NULL__"
 
 public final static String CIPHER = "PBEWithMD5AndDES"//$NON-NLS-1$ 
 
 public final static String KEY_FACTORY = "PBEWithMD5AndDES"//$NON-NLS-1$ 
 
 /**
  * A default suffix for properties to indicate them to be secure properties; 
  * secure properties are usually stored in the local project file. 
  */
 
 public static final String DEFAULT_SECURE_PROPERTY_SUFFIX = ".secure"
 
 private final IPropertyOwner delegate; 
 
 private final HashMap<String, String> cache = new HashMap<String, String>(); 
 
 private IProvider<PBEKeySpec, String> passwordProvider; 
 
 private PBEKeySpec password; 
 
 private boolean passwordResolved = false
 
 private final String suffix; 
 
 /**
  * Creates a new secure properties container. 
  * @param delegate 
  * @param passwordProvider A container for the encryption key; if 
  * this container returns {@code null}, no encryption will be 
  * performed 
  * @param suffix A suffix to add to property keys to indicate 
  * a secure property; if null orempty {@link #DEFAULT_SECURE_PROPERTY_SUFFIX} 
  * will be used. 
  * NOTE: We may want to refactor this. 
  */
 
 public SecureProperties(IPropertyOwner delegate, 
   IProvider<PBEKeySpec, String> passwordProvider, 
   String suffix) { 
  this.delegate = delegate; 
  this.passwordProvider = passwordProvider; 
  this.suffix = Util.isEmpty(suffix) ? DEFAULT_SECURE_PROPERTY_SUFFIX : suffix; 
 
 
 private Cipher createCipher(int mode, byte[] salt) throws GeneralSecurityException { 
  SecretKeyFactory keyFactory; 
  keyFactory = SecretKeyFactory.getInstance(KEY_FACTORY); 
 
  SecretKey key = keyFactory.generateSecret(password); 
 
  PBEParameterSpec entropy = new PBEParameterSpec(salt, 8); 
 
  Cipher cipher = Cipher.getInstance(CIPHER); 
  cipher.init(mode, key, entropy); 
  return cipher; 
 
 
 public static String generateRandomKey() throws GeneralSecurityException { 
  return generateRandomKey(512); 
 
 
 public static String generateRandomKey(int bits) throws GeneralSecurityException { 
  SecureRandom rnd = new SecureRandom(); 
  byte[] result = new byte[bits / 8]; 
  rnd.nextBytes(result); 
  return Util.toBase16(result); 
 
 
 private static byte[] generateSalt() { 
  byte[] salt = new byte[8]; 
  SecureRandom random = new SecureRandom(); 
  random.nextBytes(salt); 
  return salt; 
 
 
 public String encrypt(String value) throws GeneralSecurityException { 
  resolvePassword(); 
 
  if (Util.isEmpty(value) || password == null) { 
   return value; 
  
 
  byte[] salt = generateSalt(); 
  Cipher c = createCipher(Cipher.ENCRYPT_MODE, salt); 
  byte[] result = c.doFinal(value.getBytes(Charset.forName("UTF8"))); 
  return PropertyUtil.fromStrings(new String[] { Util.toBase16(salt), Util.toBase16(result) }); 
 
 
 public String decrypt(String value) throws GeneralSecurityException { 
  resolvePassword(); 
 
  if (Util.isEmpty(value) || password == null) { 
   return value; 
  
 
  String[] saltAndPepper = PropertyUtil.toStrings(value); 
  if (saltAndPepper.length != 2) { 
   throw new GeneralSecurityException("Invalid encrypted data (salt is missing)"); 
  
  byte[] salt = Util.fromBase16(saltAndPepper[0]); 
  byte[] toDecrypt = Util.fromBase16(saltAndPepper[1]); 
  Cipher c = createCipher(Cipher.DECRYPT_MODE, salt); 
  byte[] result = c.doFinal(toDecrypt); 
  return new String(result, Charset.forName("UTF8")); 
 
 
 @Override 
 public boolean setSecureProperty(String key, String value) 
   throws SecurePropertyException { 
  try { 
   String encrypted = encrypt(value); 
   cache.put(key, value == null ? NULL_VALUE : value); 
   return delegate.setProperty(key + suffix, encrypted); 
  catch (GeneralSecurityException e) { 
   throw new SecurePropertyException("Unable to encrypt", e); 
  
 
 
 @Override 
 public String getSecureProperty(String key) throws SecurePropertyException { 
  String cached = cache.get(key); 
  if (cached != null) { 
   return cached == NULL_VALUE ? null : cached; 
  
 
  try { 
   String encrypted = delegate.getProperty(key + suffix); 
   String decrypted = encrypted == null ? NULL_VALUE : decrypt(encrypted); 
   cache.put(key, decrypted); 
   return decrypted; 
  catch (GeneralSecurityException e) { 
   throw new SecurePropertyException("Unable to decrypt", e); 
  
 
 
 @Override 
 public void resetMasterPassword(IProvider<PBEKeySpec, String> newPasswordProvider) throws GeneralSecurityException, SecurePropertyException { 
  // Make sure we have the old password! 
  resolvePassword(); 
  Map<String, String> delegateProperties = delegate.getProperties(); 
  Map<String, String> decryptedProperties = new HashMap<String, String>(); 
  for (String key : delegateProperties.keySet()) { 
   // Bah, overly complicated with all these suffices, will do for now. 
   String secureKey = getSecurePropertyKey(key); 
   if (secureKey != null) { 
    String decrypted = decryptOrClear(delegateProperties.get(key)); 
    decryptedProperties.put(secureKey, decrypted); 
   
  
  // Ok, no exception! 
  this.passwordProvider = newPasswordProvider; 
  this.passwordResolved = false
  resolvePassword(); 
  this.cache.clear(); 
  for (String key : decryptedProperties.keySet()) { 
   setSecureProperty(key, decryptedProperties.get(key)); 
  
 
 
 private String decryptOrClear(String value) { 
  try { 
   return decrypt(value); 
  catch (GeneralSecurityException e) { 
   return null
  
 
 
 private void resolvePassword() throws GeneralSecurityException { 
  if (!passwordResolved) { 
   // MUST be lazily evaluated, see bug report MOSYNCTWOSIX-115. 
   password = passwordProvider.get(null); 
   passwordResolved = true
  
 
 
 private String getSecurePropertyKey(String key) { 
  if (key.endsWith(suffix)) { 
   return key.substring(0, key.length() - suffix.length()); 
  
  return null
 
 
}