/* * Sonet - Android Social Networking Widget * Copyright (C) 2009 Bryan Emmanuel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Bryan Emmanuel piusvelte@gmail.com */ package com.piusvelte.sonet; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.preference.PreferenceManager; import android.text.TextUtils; import android.util.Base64; import android.util.Log; import com.piusvelte.sonet.provider.Accounts; import com.piusvelte.sonet.provider.Entity; import com.piusvelte.sonet.provider.Statuses; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; public class SonetCrypto { private static SonetCrypto instance; private SecretKey mSecretKey = null; protected static final String ALIAS = "Sonet"; protected static final String PASSWORD = "password"; protected static final String KEYSTORE = "sonet.bks"; private static final String TAG = "SonetCrypto"; private SonetCrypto(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String password = prefs.getString(PASSWORD, null); if (password != null) { try { KeyStore ks = KeyStore.getInstance("BKS"); FileInputStream fis = context.openFileInput(KEYSTORE); ks.load(fis, password.toCharArray()); fis.close(); mSecretKey = (SecretKey) ks.getKey(ALIAS, password.toCharArray()); } catch (NoSuchAlgorithmException e) { Log.e(TAG, e.toString()); } catch (CertificateException e) { Log.e(TAG, e.toString()); } catch (FileNotFoundException e) { Log.e(TAG, e.toString()); } catch (IOException e) { Log.e(TAG, e.toString()); } catch (KeyStoreException e) { Log.e(TAG, e.toString()); } catch (UnrecoverableKeyException e) { Log.e(TAG, e.toString()); } } else { try { // generate aes key KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(256); mSecretKey = kgen.generateKey(); // generate password SecureRandom sr = new SecureRandom(); password = new BigInteger(256, sr).toString(8); // create a keystore KeyStore ks = KeyStore.getInstance("BKS"); ks.load(null, password.toCharArray()); ks.setKeyEntry(ALIAS, mSecretKey, password.toCharArray(), null); FileOutputStream fos = context.openFileOutput(KEYSTORE, Context.MODE_PRIVATE); ks.store(fos, password.toCharArray()); fos.close(); // store the password SharedPreferences.Editor editor = prefs.edit(); editor.putString(PASSWORD, password); editor.apply(); // encrypt the oauth data Cursor accounts = context.getContentResolver() .query(Accounts.getContentUri(context), new String[] { Accounts._ID, Accounts.TOKEN, Accounts.SECRET, Accounts.SID }, Accounts.SERVICE + "!=?", new String[] { Integer.toString(Sonet.SMS) }, null); if (accounts.moveToFirst()) { while (!accounts.isAfterLast()) { // encryption will occur in the provider, pass unencrypted data back in ContentValues values = new ContentValues(); values.put(Accounts.TOKEN, RemoveUnderscore(accounts.getString(1))); values.put(Accounts.SECRET, RemoveUnderscore(accounts.getString(2))); values.put(Accounts.SID, RemoveUnderscore(accounts.getString(3))); context.getContentResolver().update(Accounts.getContentUri(context), values, Accounts._ID + "=?", new String[] { Long.toString(accounts.getLong(0)) }); accounts.moveToNext(); } } accounts.close(); // encrypt the SIDs and ESIDs everywhere Cursor entities = context.getContentResolver() .query(Entity.getContentUri(context), new String[] { Entity._ID, Entity.ESID }, null, null, null); if (entities.moveToFirst()) { while (!entities.isAfterLast()) { ContentValues values = new ContentValues(); values.put(Entity.ESID, RemoveUnderscore(entities.getString(1))); context.getContentResolver().update(Entity.getContentUri(context), values, Entity._ID + "=?", new String[] { Long.toString(entities.getLong(0)) }); entities.moveToNext(); } } entities.close(); Cursor statuses = context.getContentResolver() .query(Statuses.getContentUri(context), new String[] { Statuses._ID, Statuses.SID }, null, null, null); if (statuses.moveToFirst()) { while (!statuses.isAfterLast()) { ContentValues values = new ContentValues(); values.put(Statuses.SID, RemoveUnderscore(statuses.getString(1))); context.getContentResolver().update(Statuses.getContentUri(context), values, Statuses._ID + "=?", new String[] { Long.toString(statuses.getLong(0)) }); statuses.moveToNext(); } } statuses.close(); } catch (NoSuchAlgorithmException e) { Log.e(TAG, e.toString()); } catch (KeyStoreException e) { Log.e(TAG, e.toString()); } catch (CertificateException e) { Log.e(TAG, e.toString()); } catch (IOException e) { Log.e(TAG, e.toString()); } } } public static synchronized SonetCrypto getInstance(Context context) { if (instance == null) { instance = new SonetCrypto(context); } return instance; } public String Decrypt(String data) { if (mSecretKey != null && !TextUtils.isEmpty(data)) { try { Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, mSecretKey); return new String(cipher.doFinal(Base64.decode(data, Base64.DEFAULT)), "UTF-8"); } catch (NoSuchAlgorithmException e) { Log.e(TAG, e.toString()); } catch (NoSuchPaddingException e) { Log.e(TAG, e.toString()); } catch (InvalidKeyException e) { Log.e(TAG, e.toString()); } catch (IllegalBlockSizeException e) { Log.e(TAG, e.toString()); } catch (BadPaddingException e) { Log.e(TAG, e.toString()); } catch (UnsupportedEncodingException e) { Log.e(TAG, e.toString()); } } return data; } public String Encrypt(String data) { if ((mSecretKey != null) && (data != null)) { try { Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, mSecretKey); return Base64.encodeToString(cipher.doFinal(data.getBytes("UTF-8")), Base64.DEFAULT); } catch (NoSuchAlgorithmException e) { Log.e(TAG, e.toString()); } catch (NoSuchPaddingException e) { Log.e(TAG, e.toString()); } catch (InvalidKeyException e) { Log.e(TAG, e.toString()); } catch (IllegalBlockSizeException e) { Log.e(TAG, e.toString()); } catch (BadPaddingException e) { Log.e(TAG, e.toString()); } catch (UnsupportedEncodingException e) { Log.e(TAG, e.toString()); } } return null; } protected static String RemoveUnderscore(String sid) { return (sid != null) && (sid.length() != 0) && sid.substring(0, 1).equals("_") ? sid.substring(1) : sid; } }