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官网: https://www.zetetic.net/sqlcipher/sqlcipher-for-android/
sqlcipher/sqlcipher-android-tests: https://github.com/sqlcipher/sqlcipher-android-tests。