android 加密 SQLCipher和Conceal

        在Android中,通常使用SQLite来管理本地数据。但是如果手机被ROOT了,用户能够轻易查看到SQLite数据库中的信息。如果数据库中包含用户私密信息或者APP的关键信息,那么也就能够轻易被其他人访问。现在这是所有开发不希望看见的。

       这里讲两种数据加密方法。分别采用SQLCipher和ConCeal。并分别讲述这两种方法的优缺点。


       一. SQLCipher

       SQLCipher是在SQLite基础上进行扩展的开源数据库,它增加了数据加密功能,并且支持多种不同的平台。

首先需要下载Android项目所依赖的SQLCipher包,下载地址是:

      https://s3.amazonaws.com/sqlcipher/SQLCipher+for+Android+v2.2.2.zip

 解压压缩包,有assets和libs这两个目录,assets文件夹中有icudt461.zip,libs文件夹中有armeabi文件夹,下面有三个.so的文件。需要将这两个目录中的内容复制添加到需要加密数据库的Android项目的assets和libs文件夹中。



         到此准备工作完毕,,我们需要建立一个SQLCipherDatabaseHelper继承自SQLiteOpenHelper,这里使用的不是Android API中的SQLiteOpenHelper,而是net.sqlcipher.database包下的SQLiteOpenHelper。代码如下:

import android.content.Context;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteDatabase.CursorFactory;
import net.sqlcipher.database.SQLiteOpenHelper;

public class MyDatabaseHelper extends SQLiteOpenHelper{

	 public static final String CREATE_TABLE = "create table Book(name text, pages integer)";  
	  
	    public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version) {  
	        super(context, name, factory, version);  
	    }  
	  
	    @Override  
	    public void onCreate(SQLiteDatabase db) {  
	        db.execSQL(CREATE_TABLE);  
	    }  
	  
	    @Override  
	    public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) {  
	  
	    }

}
      我们引入的是net.sqlcipher.database.SQLiteOpenHelper在这段代码中,建立了一个书籍的表,分别记录书名name和页数pages。

     除了引入的包不一样,其他的用法和SQLite都是完全相同。 添加和查询数据的代码和SQLite是相同的。

public class MainActivity extends Activity {  

      

    private SQLiteDatabase db;  

  

    @Override  

    protected void onCreate(Bundle savedInstanceState) {  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.activity_main);  

        SQLiteDatabase.loadLibs(this);  

        MyDatabaseHelper dbHelper = new MyDatabaseHelper(this, "demo.db", null, 1);  

        db = dbHelper.getWritableDatabase("secret_key");  

        Button addData = (Button) findViewById(R.id.add_data);  

        Button queryData = (Button) findViewById(R.id.query_data);  

        addData.setOnClickListener(new OnClickListener() {  

            @Override  

            public void onClick(View v) {  

                ContentValues values = new ContentValues();  

                values.put("name", "达芬奇密码");  

                values.put("pages", 566);  

                db.insert("Book", null, values);  

            }  

        });  

        queryData.setOnClickListener(new OnClickListener() {  

            @Override  

            public void onClick(View v) {  

                Cursor cursor = db.query("Book", null, null, null, null, null, null);  

                if (cursor != null) {  

                    while (cursor.moveToNext()) {  

                        String name = cursor.getString(cursor.getColumnIndex("name"));  

                        int pages = cursor.getInt(cursor.getColumnIndex("pages"));  

                        Log.d("TAG", "book name is " + name);  

                        Log.d("TAG", "book pages is " + pages);  

                    }  

                }  

                cursor.close();  

            }  

        });  

    }  

}  

     在OnCreate()中,调用了SQLiteDatabase的loadLibs()静态方法将SQLCipher所以来的.so加载进来,注意引入的是net.sqlcipher.database下的SQLiteDatabase。然后我们创建了MyDatabaseHelper的实例,并调用getWritableDatabase()方法去获取SQLiteDatabase对象。这里在调用getWritableDatabase()方法的时候传入了一个字符串参数,它就是SQLCipher所依赖的key,在对数据库进行加解密的时候SQLCipher都将使用这里指定的key。

     在添加数据按钮的点击事件里面,我们通过ContentValues构建了一条数据,然后调用SQLiteDatabase的insert()方法将这条数据插入到Book表中。

     在查询数据按钮的点击事件里面,我们调用SQLiteDatabase的query()方法来查询Book表中的数据,查询到的结果会存放在Cursor对象中,注意这里使用的是net.sqlcipher包下的Cursor。然后对Cursor对象进行遍历,并将查询到的结果打印出来。

     现在运行一下程序,先点击添加数据按钮,再点击查询数据按钮,刚刚添加的那条数据就应该在控制台里打印出来了。

     SQLCipher提供的API与Android原生的API操作起来是几乎一模一样的,因为SQLCipher对Android SDK中所有与数据库相关的API做了一份镜像。这样开发者可以向操作普通数据库文件一样来操作SQLCipher,而所有的加密解密操作,对开发人员而言都是黑盒的,SQLCipher在背后帮我们做好了。

      二。ConCeal

     ConCeal是Facebook推出的用于对数据进行快速加密和认证的开发包,Facebook用它来加密手机、平板电脑SD卡中的数据和图片。能够高效快速地在磁盘上加密大文件,对于低版本的android系统、低内存、较慢的处理器,都有很好的效率。ConCeal的加密算法基于OpenSSL,经过编译之后,apk包大小只增加0.1MB左右,能够兼容众多Android版本,减少后期维护。

      项目主页是:http://www.open-open.com/lib/view/home/1394779937806

     Android的jar包下载地址:http://facebook.github.io/conceal/documentation/

     ConCeal的加密代码判断是:

//使用秘钥链和原生库的默认实现,来创建一个新的加密对象
Crypto crypto = new Crypto(
  new SharedPrefsBackedKeyChain(context),
  new SystemNativeCryptoLibrary());
 
//检查加密功能是否可用
//如果Android没有正确载入库,则此步骤可能失败
if (!crypto.isAvailable()) {
  return;
}
 
OutputStream fileStream = new BufferedOutputStream(
  new FileOutputStream(file));
 
//创建输出流,当数据写入流的时候进行加密,并将加密后的数据输出到文件
OutputStream outputStream = crypto.getCipherOutputStream(
  fileStream,
  entity);
 
//将纯文本写入其中
outputStream.write(plainText);
outputStream.close();
      ConCeal的解密代码段是:

// 打开用ConCeal加密的文件
FileInputStream fileStream = new FileInputStream(file);

// 创建一个输入流,当数据读入的时候用ConCeal进行解密
InputStream inputStream = crypto.getCipherInputStream(
  fileStream,entity);

// 变为字节
int read;
byte[] buffer = new byte[1024];
// 读入整个输入流,在输入流结束时进行验证,如果不一直验证到流结束,将有安全漏洞
while ((read = inputStream.read(buffer)) != -1) {
  out.write(buffer, 0, read);
}

inputStream.close();
      创建新的KeyChain。程序中有默认的KeyChain,但是也可以继承KeyChain以实现一个自己的KeyChain。

public class CustomKeyChain implements KeyChain {
  ...
}
      下面讲述ConCeal的具体实现

      1. 从http://facebook.github.io/conceal/documentation/中下载工程所需要的两个Jar包和so文件,分别点击ConCeal Jar、Android Jar与Native Binaries下载即可。将libconceal.jar与conceal_android.jar放入工程的libs文件夹下。下载的libs.rar解压后,把armeabi文件夹下的libconceal.so文件翻入自己工程libs文件夹下的armeabi文件夹下即可。





      2. 加密

	private void encryption(){
		// Creates a new Crypto object with default implementations of 
		// a key chain as well as native library.
		Crypto crypto = new Crypto(
		  new SharedPrefsBackedKeyChain(context),
		  new SystemNativeCryptoLibrary());

		// Check for whether the crypto functionality is available
		// This might fail if Android does not load libaries correctly.
		if (!crypto.isAvailable()) {
			Toast.makeText(context, "ENCRYPTION FAIL!", Toast.LENGTH_SHORT).show();
			return;
		}

		try {
			//加密后的文件路径
			File file = new File(GPUtils.getSharePicPath() + "/text.txt");
			
			OutputStream fileStream = new BufferedOutputStream(new FileOutputStream(file));
			
			com.facebook.crypto.Entity entity = new com.facebook.crypto.Entity("text");
			
			// Creates an output stream which encrypts the data as
			// it is written to it and writes it out to the file.
			OutputStream outputStream;
			outputStream = crypto.getCipherOutputStream(fileStream, entity);
			//需要加密的text
			String plainString = "TEST!!!测试!!!";
			//将String变为byt[]类型
			byte[] plainText = plainString.getBytes();
			
			// Write plaintext to it.
			outputStream.write(plainText);
			outputStream.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (CryptoInitializationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (KeyChainException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
          Entity应该是加密所使用秘钥。但是具体没有研究。

      3. 解密

	private void decryption(){
		// Creates a new Crypto object with default implementations of 
		// a key chain as well as native library.
		Crypto crypto = new Crypto(
		  new SharedPrefsBackedKeyChain(context),
		  new SystemNativeCryptoLibrary());

		try {
			//需要解密的文件路径
			File file = new File(GPUtils.getSharePicPath() + "/text.txt");
			
			// Get the file to which ciphertext has been written.
			FileInputStream fileStream = new FileInputStream(file);

			com.facebook.crypto.Entity entity = new com.facebook.crypto.Entity("text");
			
			// Creates an input stream which decrypts the data as
			// it is read from it.
			InputStream inputStream;
			inputStream = crypto.getCipherInputStream(fileStream,entity);

			// Read into a byte array.
			int read;
			byte[] buffer = new byte[1024];
			//解密后的文本
			String plainString = new String();
			
			// You must read the entire stream to completion.
			// The verification is done at the end of the stream.
			// Thus not reading till the end of the stream will cause
			// a security bug. 
			StringBuilder stringBuilder = new StringBuilder();
		    while((read = inputStream.read(buffer)) > 0){
		        stringBuilder.append(new String(buffer, 0, read));
		    }
			    
			plainString = stringBuilder.toString();
			Toast.makeText(context, plainString, Toast.LENGTH_SHORT).show();
			inputStream.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (CryptoInitializationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (KeyChainException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
       在解密时,new Entity时使用的String必须与解密时相同,否则不能正确解密。

       4. 扩展

       目前只是实现了加密String类型。其余类型没有实现。猜测JSON类型的应该也是可以的。这样的话,对于结构化的数据也能较好得加解密了。

     三。总结

       总结一下就是,SQLite对程序猿而言,只需要选择秘钥,其余查询、插入等数据库操作方法与SQLite几乎一模一样,以为它是SQLite的镜像,容易上手,加密结构化数据很便利。但是导入的jar包较大,会增加4MB左右apk大小。Conceal上手相对于SQLite而言,稍微慢一点点,加密大型的结构化的数据较为麻烦。但是体量小,对apk包大小影响很小,只增加0.1MB左右。如果进行快速小型的加密时,使用ConCeal较为方便。

你可能感兴趣的:(android,加密,SQLCipher,ConCeal)