1、介绍
SQLCipher是一个在SQLite基础之上进行扩展的开源数据库,它主要是在SQLite的基础之上增加了数据加密功能,如果我们在项目中使用它来存储数据的话,就可以大大提高程序的安全性。SQLCipher支持很多种不同的平台,这里仅介绍Android中SQLCipher的用法。SQLCipher官网参见
https://www.zetetic.net/sqlcipher/
。
网上的很多资料,大都说的是用sqlcipher加密,并通过密码来打开数据库及后续的增删改查操作等。但这些例子都是新建带密码的数据库,而非对已有的数据库进行加密和解密。对于已有的未加密的数据库,显然有极大的不便。另外一些资料,是通过sqlcipher的命令模式直接改密码,本人未做尝试,暂不做评论。基于此,才有了如下的项目。
2、利用AndroidStudio新建项目,并以gradle的方式将SQLCipher导入到我们的项目
在app级别的build.gradle中添加如下代码:
dependencies {
compile 'net.zetetic:android-database-sqlcipher:3.5.4@aar'
}
然后,编译项目即可。
查询最新版本的SQLCipher,可参见如下网址
https://www.zetetic.net/sqlcipher/sqlcipher-for-android/。
3、项目只有MainActivity.java和activity_main.xml两个文件:
- 布局文件activity_main.xml代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" tools:context="com.wjk.sqlciphertest.MainActivity"> <Button android:id="@+id/bt_encry" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="加密" /> <Button android:id="@+id/bt_decry" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:text="解密" /> </LinearLayout>
布局文件中包括加密和解密两个按钮。
- 接下来在MainActivity.java中编写加密和解密方法。主要用到SQLiteDatabase.rawExecSQL()和sqlcipher_export()两个方法。先上代码:
package com.***.sqlciphertest; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import net.sqlcipher.database.SQLiteDatabase; import java.io.File; public class MainActivity extends AppCompatActivity { private final String SDcardPath = "/mnt/sdcard/"; private Button mEncryptButton; private Button mDecryptButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SQLiteDatabase.loadLibs(this);//引用SQLiteDatabase的方法之前必须先添加这句代码 mEncryptButton = (Button) findViewById(R.id.bt_encry); mDecryptButton = (Button) findViewById(R.id.bt_decry); mEncryptButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { encrypt("encryptedtest.db","test.db","1234"); } }); mDecryptButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { decrypt("encryptedtest.db","decryptedtest.db","1234"); } }); } /** * 加密数据库 * @param encryptedName 加密后的数据库名称 * @param decryptedName 要加密的数据库名称 * @param key 密码 */ private void encrypt(String encryptedName,String decryptedName,String key) { try { File databaseFile = getDatabasePath(SDcardPath + decryptedName); SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(databaseFile, "", null);//打开要加密的数据库 /*String passwordString = "1234"; //只能对已加密的数据库修改密码,且无法直接修改为“”或null的密码 database.changePassword(passwordString.toCharArray());*/ File encrypteddatabaseFile = getDatabasePath(SDcardPath + encryptedName);//新建加密后的数据库文件 //deleteDatabase(SDcardPath + encryptedName); //连接到加密后的数据库,并设置密码 database.rawExecSQL(String.format("ATTACH DATABASE '%s' as "+ encryptedName.split("\\.")[0] +" KEY '"+ key +"';", encrypteddatabaseFile.getAbsolutePath())); //输出要加密的数据库表和数据到加密后的数据库文件中 database.rawExecSQL("SELECT sqlcipher_export('"+ encryptedName.split("\\.")[0] +"');"); //断开同加密后的数据库的连接 database.rawExecSQL("DETACH DATABASE "+ encryptedName.split("\\.")[0] +";"); //打开加密后的数据库,测试数据库是否加密成功 SQLiteDatabase encrypteddatabase = SQLiteDatabase.openOrCreateDatabase(encrypteddatabaseFile, key, null); //encrypteddatabase.setVersion(database.getVersion()); encrypteddatabase.close();//关闭数据库 database.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 解密数据库 * @param encryptedName 要解密的数据库名称 * @param decryptedName 解密后的数据库名称 * @param key 密码 */ private void decrypt(String encryptedName,String decryptedName,String key) { try { File databaseFile = getDatabasePath(SDcardPath + encryptedName); SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(databaseFile, key, null); File decrypteddatabaseFile = getDatabasePath(SDcardPath + decryptedName); //deleteDatabase(SDcardPath + decryptedName); //连接到解密后的数据库,并设置密码为空 database.rawExecSQL(String.format("ATTACH DATABASE '%s' as "+ decryptedName.split("\\.")[0] +" KEY '';", decrypteddatabaseFile.getAbsolutePath())); database.rawExecSQL("SELECT sqlcipher_export('"+ decryptedName.split("\\.")[0] +"');"); database.rawExecSQL("DETACH DATABASE "+ decryptedName.split("\\.")[0] +";"); SQLiteDatabase decrypteddatabase = SQLiteDatabase.openOrCreateDatabase(decrypteddatabaseFile, "", null); //decrypteddatabase.setVersion(database.getVersion()); decrypteddatabase.close(); database.close(); } catch (Exception e) { e.printStackTrace(); } } }
代码中已经做了详尽的注释,而且代码也很简单。主要是参考sqlcipher/sqlcipher-android-tests,网址见
https://github.com/sqlcipher/sqlcipher-android-tests
。
如果数据库是没有密码的,加密后,再打开数据库,则会提示file is encrypted or is not a database。再解密后,即可正常打开数据库。
不论是新建的数据库,还是已有的加密或没加密过的数据库,并且对更新数据库的数据都会带来极大的方便。
4、参考文献
sqlcipher/sqlcipher-android-tests:
https://github.com/sqlcipher/sqlcipher-android-tests。