利用SQLCipher加解密數據庫(包括加解密已有的數據庫)

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章