Wednesday 6 November 2013

Secure your content provider with SQLCipher


SQLCipher provides encryption of SQLite database files. It encrypts database using AES-256 in CBC mode. SQLCipher supports many platform including android. Here is a basic tutorial for setting up and using SQLCipher in android application.

In this tutorial I'll try to explain how can we secure our Contentprovider with SQLCipher. We need to change few things in our MyDatabase class in order to use SQLCipher. We have to import:

   import net.sqlcipher.database.SQLiteDatabase;
   import net.sqlcipher.database.SQLiteOpenHelper;

instead of

  import android.database.sqlite.SQLiteDatabase;
  import android.database.sqlite.SQLiteOpenHelper;


Method signatures of both libraries  are same so no modification is required in function calls.
Now lets see the MyContentProvider class. Here again we need use SQLCipher libs instead of android's SQLite libs. Import

 import net.sqlcipher.database.SQLiteDatabase;
 import net.sqlcipher.database.SQLiteQueryBuilder;

Instead of

 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteQueryBuilder;


There is a small difference between SQLite and SQLCipher's db.getReadableDatabase and db.getWriteableDatabase functions though.
android's SQLite  functions receives no argument while SQLCipher's functions take (String) password as an argument. This password is used to encrypt when writing and decrypt when reading from database. To put things together, MyContentProvider class will be look like this when using SQLCipher:

package sohail.aziz.mycontentprovider;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;

import net.sqlcipher.database.SQLiteDatabase;

import net.sqlcipher.database.SQLiteQueryBuilder;
import android.net.Uri;
import android.util.Log;


public class MyContentProvider extends ContentProvider {

        private MyDatabase mydb;

        private static String dbPassword = "sohail"; 

        @Override
        public boolean onCreate() {
               
                Context ctx = getContext();
                mydb = new MyDatabase(ctx);
                return (mydb == null) ? false : true;
        }

        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
              
                 SQLiteDatabase db = mydb.getWritableDatabase(dbPassword);
                 int token = MyContentDescriptor.URI_MATCHER.match(uri);
                 int count=0;
                 
                 switch(token){
                 case MyContentDescriptor.Categories.CAT_PATH_TOKEN:
                        count= db.delete(MyContentDescriptor.Categories.TABLE_NAME, selection, selectionArgs);
                         break;
                 case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN:
                         count= db.delete(MyContentDescriptor.Transactions.TABLE_NAME, selection, selectionArgs);
                         break;
                 }
                 
                getContext().getContentResolver().notifyChange(uri, null);
                return count;
               
        }

        @Override
        public String getType(Uri uri) {
                // returning self defined mime types
                // to be used by other applications if any
                final int match = MyContentDescriptor.URI_MATCHER.match(uri);
                switch (match) {

                case MyContentDescriptor.Categories.CAT_PATH_TOKEN:
                case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN:
                        return MyContentDescriptor.CONTENT_TYPE_DIR;

                case MyContentDescriptor.Categories.CAT_PATH_FOR_ID_TOKEN:
                case MyContentDescriptor.Transactions.TRAN_PATH_FOR_ID_TOKEN:
                        return MyContentDescriptor.CONTENT_ITEM_TYPE;

                }

                return null;
        }

        @Override
        public Uri insert(Uri uri, ContentValues values) {
               
                Log.d("sohail", "inside insert");
              SQLiteDatabase db =mydb.getWritableDatabase(dbPassword);

                int token = MyContentDescriptor.URI_MATCHER.match(uri);
                switch (token) {
                case MyContentDescriptor.Categories.CAT_PATH_TOKEN: // uri is of
                                                                                                                        // categories table
                        Log.d("sohail", "matched uri is CAT_PATH_TOKEN:" + uri.toString());
                        long id = db.insert(MyContentDescriptor.Categories.TABLE_NAME,
                                        null, values);
                        // notifying change to content observers
                        getContext().getContentResolver().notifyChange(uri, null);
                        return MyContentDescriptor.Categories.CONTENT_URI.buildUpon()
                                        .appendPath(String.valueOf(id)).build();

                case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN: // uri is of
                                                                                                                                // transaction
                                                                                                                                // table
                        Log.d("sohail", "matched uri is TRAN_PATH_TOKEN:" + uri.toString());
                        long idd = db.insert(MyContentDescriptor.Transactions.TABLE_NAME,
                                        null, values);
                        getContext().getContentResolver().notifyChange(uri, null);
                        return MyContentDescriptor.Transactions.CONTENT_URI.buildUpon()
                                        .appendPath(String.valueOf(idd)).build();

                default:
                        throw new UnsupportedOperationException("URI: " + uri
                                        + " not supported.");
                }

        }

        @Override
        public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
               
                Log.d("sohail", "query called");
               SQLiteDatabase db = mydb.getReadableDatabase(dbPassword);
                SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
                Cursor c;
                int token = MyContentDescriptor.URI_MATCHER.match(uri);

                switch (token) {

                case MyContentDescriptor.Categories.CAT_PATH_TOKEN:
                        Log.d("sohail", "matched uri is CAT_PATH_TOKEN:" + uri.toString());
                        queryBuilder.setTables(MyContentDescriptor.Categories.TABLE_NAME);
                        c = queryBuilder.query(db, projection, selection, selectionArgs,
                                        null, null, sortOrder);
                        return c;

                case MyContentDescriptor.Categories.CAT_PATH_FOR_ID_TOKEN:
                        Log.d("sohail", "matched uri is CAT_PATH_TOKEN:" + uri.toString());
                        queryBuilder.setTables(MyContentDescriptor.Categories.TABLE_NAME);
                        queryBuilder.appendWhere(MyContentDescriptor.Categories.Cols.cat_id
                                        + "=" + uri.getLastPathSegment());
                        c = queryBuilder.query(db, projection, selection, selectionArgs,
                                        null, null, sortOrder);
                        return c;

                case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN:
                        Log.d("sohail", "matched uri is TRAN_PATH_TOKEN:" + uri.toString());
                        queryBuilder.setTables(MyContentDescriptor.Transactions.TABLE_NAME);
                        c = queryBuilder.query(db, projection, selection, selectionArgs,
                                        null, null, sortOrder);
                        return c;
                case MyContentDescriptor.Transactions.TRAN_PATH_FOR_ID_TOKEN:
                        Log.d("sohail", "matched uri is TRAN_PATH_TOKEN:" + uri.toString());
                        queryBuilder.setTables(MyContentDescriptor.Transactions.TABLE_NAME);
                        queryBuilder
                                        .appendWhere(MyContentDescriptor.Transactions.Cols.tran_id
                                                        + "=" + uri.getLastPathSegment());
                        c = queryBuilder.query(db, projection, selection, selectionArgs,
                                        null, null, sortOrder);
                        return c;

                default:
                        Log.d("sohail", "no URI MATCHED");
                        return null;
                }

        }

        @Override
        public int update(Uri uri, ContentValues values, String selection,
                        String[] selectionArgs) {
              

        SQLiteDatabase db = mydb.getWritableDatabase(dbPassword);
                 int token = MyContentDescriptor.URI_MATCHER.match(uri);
                 int count=0;
                 
                 switch(token){
                 case MyContentDescriptor.Categories.CAT_PATH_TOKEN:
                        count= db.update(MyContentDescriptor.Categories.TABLE_NAME,values, selection, selectionArgs);
                         break;
                 case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN:
                         count= db.update(MyContentDescriptor.Transactions.TABLE_NAME,values,selection, selectionArgs);
                         break;
                 }
                 
                getContext().getContentResolver().notifyChange(uri, null);
                return count;
               
        }

}


Please note that we use

mydb.getWritableDatabase(dbPassword);    and
mydb.getReadableDatabase(dbPassword);
 
With this slight change the database file is encrypted using password dbPassword. Please do configure the libs and assests as explained on SQLCipher site before running this code.



Monday 28 October 2013

Custom ContentProvider Permissions and Use from other Apps


A custom content provider was discussed and implemented here earlier. Today I'll discuss how a custom content provider can be used by other applications and how content provider permissions can be defined which other applications need to have in order to use it.

Content Provider Permissions:

Lets start with the provider permissions. Below is the modified Manifest.xml file of the MyContentProvider:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="sohail.aziz.mycontentprovider"
    android:versionCode="1"
    android:versionName="1.0" >

   <uses-sdk android:minSdkVersion="9" />
    
   <permission
        android:name="sohail.aziz.READ"
        android:protectionLevel="signature" />
   <permission
        android:name="sohail.aziz.WRITE"
        android:protectionLevel="signature" />

   <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >

       
        <provider android:name=".MyContentProvider"
          android:authorities="sohail.aziz.mycontentprovider"

          android:enabled="true"
          android:exported="true"
          android:readPermission="sohail.aziz.READ"
          android:writePermission="sohail.aziz.WRITE" />
    </application>

</manifest> 


We have defined two custom permissions sohail.aziz.READ and sohail.aziz.WRITE and used them as content provider's readPermission and writePermission. By defining these permissions we made it mandatory for other apps to have these permission in order to READ (Query) and WRITE (insert, update,delete) to this content provider.

Accessing Content Provider from other Apps:

In order to access this custom content provider from other apps, we need to have following things:

  •     Name of the needed permissions if any.
  •     Content URI of all the tables we need to interact.
  •     Exact fields/Cols name of the particular table.

Lets see how can we perform CRUD operations on the MyContentProvider defined here. Content URI for the categories table defined in MyContentDescriptor will be:

Uri  categoriesUri=Uri.parse("content://sohail.aziz.mycontentprovider/categories");

Lets put some content values in mycontentprovider from another app, say sample.apk.

 
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


Uri  categoriesURI=Uri.parse("content://sohail.aziz.mycontentprovider/categories"); 

 ContentValues vals=new ContentValues();

    /* name and status are the field name of categories table in MyContentProvider*/

            vals.put("name","sohail");
            vals.put("status",false);
            getContentResolver().insert(categoriesURI,vals);

   }
}


Same way we can call the other CRUD operations. As discussed at the start will be required to have  sohail.aziz.READ and/or sohail.aziz.WRITE permissions.

Friday 29 March 2013

BiMap: A two-way Map in #android

A simple hashMap is used to keep key,value pairs. It is useful in situation where we need to lookup values by keys. e.g
        
HashMap<String, String> simplemap=new HashMap<String, String>();
        simplemap.put("name", "sohail");
        
        simplemap.get("name");


Above is the simple example of hashmap, where we put a key,value pair and then lookup using key. However, there are situations where we need reverse lookup i.e lookup by value. An example of such case can be a Map of the indexes of two lists, and we need to lookup one's index by other and vice versa. A map which can be looked-up by keys as well as values, is called BiMap provided by Guava.
We can use HashBiMap to create a bimap and then use its map.inverse function to get inverse map.
 
       
HashBiMap<String, String> map = HashBiMap.create();

        map.put("name", "Sohail");
        map.put("country", "Pakistan");

        Log.d("tag", "name is " + map.get("name"));
       
       
        BiMap<String, String>invmap= map.inverse();
       
        Log.d("tag", "Pakistan is a " + invmap.get("Pakistan"));


  

Wednesday 20 March 2013

AntiSpyware Fraud on #Android's Google Play


States have been making money for decades on the name of  "Security". After the revolution of mobile phones many companies are doing the same in the world of  mobile applications. After Realizing the fact that many people, who owns smart phones, are conscious about their privacy, and majority of those do not really know about the security and privacy, are easy prey of such companies.

One such example of such apps is MPL AntiSpy by Mobile Privacy Labs.  This is $10 paid app! By placing such high price they try to give user an impression that this is REAL anti spyware. Not only this, an other app  AntiSpy TESTFILE by sample company is published and been declared a metric for AntiSpyware apps. This is what is written in the description of MPL AntiSpy

To verify MPL AntiSpy works correctly on your phone, please download AntiSpy TESTFILE for free. MPL AntiSpy will detect the TESTFILE as spyware if it works.
(Download here: http://play.google.com/store/apps/details?id=com.ANTISPY.TESTFILE)

To test MPL AntiSpy "Email Alert" feature
1. Enable Email Alert. Click "Menu"-> Type valid email address -> Exit from AntiSpy.
2. Install AntiSpy TESTFILE.
3. Check Toast note saying that "TESTFILE is SPYWARE app."
4. Check your email for Email Alert about the SPYWARE app was found.
I was getting curious to know what is ANTISPY TESTFILE is. So I de-compiled it by standard apk decompilation tools (apktool, dex2jar, jd). What I found is: this ANTISPY TESTFILE is not any way similar to the behavior and capabilities of a real spyware. It CANNOT read Contacts, SMS, Calllogs, browsing history, location etc (as shown in the screen shot of MPL AntiSpy).

Its well known fact that in android environment, Every application needs access permissions to access phone's Contacts, SMS, Calllogs etc. However ANTISPY TESTFILE doesn't request any such permissions nor do anything like real spyware.

So whats the purpose of all this? The idea is to fool "security conscious" innocent users and give impression that MPL AntiSpy is only real anti-spyware because it detects ANTISPY TESTFILE while others do not. Mobile Privacy Labs is making money by fraud with innocent android users. Lets see whether Google will protect its users from such "legal" fraud. Below are the screen shots of ANTISPY TESTFILE source code.















Tuesday 22 January 2013

Db4o Concurrent Access

Db4o has access limitations, which means you cannot use the ObjectContainer to query/store objects in different process than the one it was opened in. For example if you open db

ObjectContainer db= Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(),
DB_PATH);

You cannot use this ObjectContainer, db for database operations in other processes. If you try to open db again in some other process you might get
DatabaseFileLockedException. So db4o don't allow concurrent access this way. However, in real world applications, we need to perform database operations in many different threads, AsyncTask and services. To perform such concurrent operations in isolation, db4o provides many mechanisms, one of which is, opening different db sessions in different processes. Once you have opened the database, You can use it to open session in different process like:

ObjectContainer db_session= db.ext.openSession();

now we can execute all db operations with this ObjectContainer e.g

  db_session.store(someObj);
  db_session.commit();

However, keep in mind, that you need to explicitly close the session by

  db_session.Close();
 
Below is a helper class, can be used to open/close db.

//////////////////////////////////////////////////////////////////////////////////////
public class dbHelper {

private static ObjectContainer database;
private static final String DATABASE_NAME = "My_database.db4o";
private static final int DATABASE_MODE = 0;
 private static Context ctx;

public dbHelper(Context context) {

   ctx=context;
   database=null;

}

private OpenDatabse(){

try {
if (database == null) {
database = Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(),
db4oDBFullPath());
}
} catch (Exception ie) {
Log.e(DbHelper.class.getName(), ie.toString());
}


}

private String db4oDBFullPath() {
return ctx.getDir("data", DATABASE_MODE) + "/" + DATABASE_NAME;
}

public void close() {
if (this.database != null) {
this.database.close();
}
}

public ObjectContainer getDatabaseSession() {

         return database.ext().openSession();
}

}


/////////////////////////////////////////////////////////////////////////////////

Using this helper class we can pen the db somewhere at application start,


dbHelper helper= new dbHelper(context);
helper.OpenDatabse();

and close the db some where at application finish, by

helper.CloseDb();

Now in any process where you want to access db, use getSession method

ObjectContainer db= helper.getDatabaseSession();

db.store(someObj);
db.commit();
db.Close();

 That's it about concurrent access, hope it will be helpful.

Tuesday 8 January 2013

Using Database for Object (db4o) in android part-2

In previous post we discussed db4o benefits over relational databases and saw how we can setup and use db4o in android applications.

Today we will go little deeper in understanding the advance functionality of  db4o and its querying mechanism. QBE is easy but We need more sophisticated  mechanisms of querying in actual android applications, and that's Native Queries.

Native queries provide us absolute power of conditional querying. We can query an object by any of its fields and using all conditional operators and we can combine our quries using logical operators AND, OR, NOT. Lets start with an example. Lets say we have a  Contact class

Class Contact{
    Public String name;
    Public String number;
    Public int age;
    Public boolean member;
}

Querying based on conditions is very easy in db4o than in relational databases. We have a predicate function   in which we can write any condition which should be true. Lets see :

Find a contact where number= 1234


ObjectSet<Contact> result = db.query(new Predicate<Contact>() {
public boolean match(Contact conObj) {
return (conObj.number.equal("1234"));
}
});

find a contact where age=20


ObjectSet<Contact> result = db.query(new Predicate<Contact>() {
public boolean match(Contact conObj) {
return (conObj.age==20);
}
});

to get all objects of type Contact

ObjectSet<Contact> result = db.query(new Predicate<Contact>() {
public boolean match(Contact conObj) {
return true;
}
});



Note that we are using db.query instead of db.QBE. db.query return us one or more matching objects in result which Contact type object set. We can iterate result to get objetcs. We can write any expression in match function which should be true when querying objects.

To retrieve found objects, check the result returned by db.query

if(result.hasNext()) // if result is not empty
   Contact obj= result.Next();


and if you want to return all objects returned by query

ArrayList<Contact> list=new ArrayList<Contact>();

while(result.hasNext()){
  list.add(result.Next());
}


that's it about native queries. There are some other interesting things like indexing for  fast querying and dealing with objects containing other objects. You are encouraged to read about them in db4o reference document and tutorial.