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