文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android 使用sqlcipher加密和解密数据库(包括加密和解密已有的数据库,还有如何查看数据库教程)

2023-10-22 12:10

关注

前言

我们知道Android系统有一个内嵌的SQLite数据库,并且提供了一整套的API用于对数据库进行增删改查操作,SQLite是一个轻量级的、跨平台的、开源的嵌入式数据库引擎,也是一个关系型的的使用SQL语句的数据库引擎,读写效率高、资源消耗总量少、延迟时间少,使其成为移动平台数据库的最佳解决方案(如Android、iOS)
但是Android上自带的SQLite数据库是没有实现加密的,我们可以通过Android Studio直接导出应用创建的数据库文件,然后通过如SQLiteStudio 这种可视化工具打开数据库文件进行查看数据库的表结构,以及数据。

不过,使用SQLite来存储数据却存在着一个问题,正常情况下我们不能看到Android手机数据库文件,但是Android手机Root过,而Root过的手机都可以进入到/data/data//databases目录下(或者直接使用Android Studio自带的模拟器也能进入到/data/data//databases目录下),在这里就可以查看到数据库中存储的所有数据。如果是一般的数据还好,但是当涉及到一些账号密码,或者聊天内容的时候,我们的程序就会面临严重的安全漏洞隐患。那么今天,就让我们一起研究一下如何借助sqlcipher来解决这个安全性问题。

sqlcipher是一个在SQLite基础之上进行扩展的开源数据库,它主要是在SQLite的基础之上增加了数据加密功能,如果我们在项目中使用它来存储数据的话,就可以大大提高程序的安全性。sqlcipher支持很多种不同的平台,这里我们要学习的自然是Android中sqlcipher的用法了。

注意:实际上是对原有的db文件数据拷贝了一份到新的db文件并进行了加密,并不是对db文件的字段进行加密。

1.首先创建一个数据库(这里以Room数据库为例,详见Room数据库的使用与升级(详细介绍了增删改查,关于查询,各种查询方式都有介绍)_ErwinNakajima的博客-CSDN博客)。

2.引入sqlcipher依赖,在app的build.gradle中添加依赖,并同步项目。

dependencies {implementation "net.zetetic:android-database-sqlcipher:4.4.3@aar"}

3.把第一步中的Room数据库文件AppRoomDataBase改写一下,并把version在原基础之上加1。

package com.phone.library_common.roomimport androidx.room.Databaseimport androidx.room.Roomimport androidx.room.RoomDatabaseimport com.phone.library_common.BaseApplicationimport com.phone.library_common.BuildConfigimport com.phone.library_common.JavaGetDataimport com.phone.library_common.manager.LogManagerimport com.phone.library_common.manager.SharedPreferencesManagerimport net.sqlcipher.database.SQLiteDatabaseimport net.sqlcipher.database.SQLiteDatabaseHookimport net.sqlcipher.database.SupportFactory@Database(entities = [Book::class], version = 3)abstract class AppRoomDataBase : RoomDatabase() {    //创建DAO的抽象类    abstract fun bookDao(): BookDao    companion object {        private val TAG = AppRoomDataBase::class.java.simpleName        val DATABASE_ENCRYPT_KEY =            JavaGetData.nativeDatabaseEncryptKey(BaseApplication.get(), BuildConfig.IS_RELEASE)        val passphrase = SQLiteDatabase.getBytes(DATABASE_ENCRYPT_KEY.toCharArray())        val factory = SupportFactory(passphrase, object : SQLiteDatabaseHook {            override fun preKey(database: SQLiteDatabase?) {            }            override fun postKey(database: SQLiteDatabase?) {//                database?.execSQL("PRAGMA cipher_page_size = 1024")//                database?.execSQL("PRAGMA kdf_iter = 64000")//                database?.execSQL("PRAGMA cipher_hmac_algorithm = HMAC_SHA1")//                database?.execSQL("PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1")            }        }, true)        const val DATABASE_NAME = "simple_app.db"        const val DATABASE_ENCRYPT_NAME = "simple_encrypt_app.db"        const val DATABASE_DECRYPT_NAME = "simple_decrypt_app.db"        @Volatile        private var databaseInstance: AppRoomDataBase? = null        @Synchronized        @JvmStatic        fun get(): AppRoomDataBase {            if (databaseInstance == null) {//                databaseInstance = Room.databaseBuilder(//                    BaseApplication.get(),//                    AppRoomDataBase::class.java,//                    DATABASE_NAME//                )//                    .allowMainThreadQueries()//允许在主线程操作数据库,一般不推荐;设置这个后主线程调用增删改查不会报错,否则会报错                    .openHelperFactory(factory)//                    .build()                databaseInstance = Room.databaseBuilder(                    BaseApplication.get(),                    AppRoomDataBase::class.java,                    DATABASE_ENCRYPT_NAME                )                    .allowMainThreadQueries()//允许在主线程操作数据库,一般不推荐;设置这个后主线程调用增删改查不会报错,否则会报错                    .openHelperFactory(factory)                    .build()                val dataEncryptTimes = SharedPreferencesManager.get("dataEncryptTimes", "0")                if ("0".equals(dataEncryptTimes)) {                    encrypt(                        DATABASE_ENCRYPT_NAME,                        DATABASE_NAME,                        DATABASE_ENCRYPT_KEY                    )                    SharedPreferencesManager.put("dataEncryptTimes", "1")                }            }            return databaseInstance!!        }                @JvmStatic        fun encrypt(encryptedName: String, name: String, key: String) {            try {                val databaseFile = BaseApplication.get().getDatabasePath(name)                LogManager.i(TAG, "databaseFile*****${databaseFile.absolutePath}")                val database: SQLiteDatabase =                    SQLiteDatabase.openOrCreateDatabase(databaseFile, "", null) //打开要加密的数据库                                val encrypteddatabaseFile =                    BaseApplication.get().getDatabasePath(encryptedName) //新建加密后的数据库文件                LogManager.i(TAG, "encrypteddatabaseFile*****${encrypteddatabaseFile.absolutePath}")                //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] + ";")                //打开加密后的数据库,测试数据库是否加密成功                val encrypteddatabase: SQLiteDatabase =                    SQLiteDatabase.openOrCreateDatabase(encrypteddatabaseFile, key, null)                //encrypteddatabase.setVersion(database.getVersion());                encrypteddatabase.close() //关闭数据库                database.close()            } catch (e: Exception) {                e.printStackTrace()            }        }                @JvmStatic        fun decrypt(decryptedName: String, name: String, key: String) {            try {                val databaseFile = BaseApplication.get().getDatabasePath(name)                val database: SQLiteDatabase =                    SQLiteDatabase.openOrCreateDatabase(databaseFile, key, null)                val decryptedDatabaseFile = BaseApplication.get().getDatabasePath(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] + ";")                val decrypteddatabase: SQLiteDatabase =                    SQLiteDatabase.openOrCreateDatabase(decryptedDatabaseFile, "", null)                //decrypteddatabase.setVersion(database.getVersion());                decrypteddatabase.close()                database.close()            } catch (e: Exception) {                e.printStackTrace()            }        }    }}

4.加密数据库方法(实际上是把原有simple_app.db文件数据拷贝了一份到新的simple_encrypt_app.db文件,并给新的simple_encrypt_app.db文件设置了密码)。

                @JvmStatic        fun encrypt(encryptedName: String, name: String, key: String) {            try {                val databaseFile = BaseApplication.get().getDatabasePath(name)                LogManager.i(TAG, "databaseFile*****${databaseFile.absolutePath}")                val database: SQLiteDatabase =                    SQLiteDatabase.openOrCreateDatabase(databaseFile, "", null) //打开要加密的数据库                                val encrypteddatabaseFile =                    BaseApplication.get().getDatabasePath(encryptedName) //新建加密后的数据库文件                LogManager.i(TAG, "encrypteddatabaseFile*****${encrypteddatabaseFile.absolutePath}")                //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] + ";")                //打开加密后的数据库,测试数据库是否加密成功                val encrypteddatabase: SQLiteDatabase =                    SQLiteDatabase.openOrCreateDatabase(encrypteddatabaseFile, key, null)                //encrypteddatabase.setVersion(database.getVersion());                encrypteddatabase.close() //关闭数据库                database.close()            } catch (e: Exception) {                e.printStackTrace()            }        }

5.解密数据库方法(实际上是把原有simple_encrypt_app.db文件数据拷贝了一份到新的simple_decrypt_app.db文件,并给新的simple_decrypt_app.db文件取消了密码)。

        @JvmStatic        fun decrypt(decryptedName: String, name: String, key: String) {            try {                val databaseFile = BaseApplication.get().getDatabasePath(name)                val database: SQLiteDatabase =                    SQLiteDatabase.openOrCreateDatabase(databaseFile, key, null)                val decryptedDatabaseFile = BaseApplication.get().getDatabasePath(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] + ";")                val decrypteddatabase: SQLiteDatabase =                    SQLiteDatabase.openOrCreateDatabase(decryptedDatabaseFile, "", null)                //decrypteddatabase.setVersion(database.getVersion());                decrypteddatabase.close()                database.close()            } catch (e: Exception) {                e.printStackTrace()            }        }

6.新安装的App(覆盖安装)在初始化数据库的时候,会进行数据库加密操作(只加密一次),然后会生成一份新的simple_decrypt_app.db文件。

        @Synchronized        @JvmStatic        fun get(): AppRoomDataBase {            if (databaseInstance == null) {//                databaseInstance = Room.databaseBuilder(//                    BaseApplication.get(),//                    AppRoomDataBase::class.java,//                    DATABASE_NAME//                )//                    .allowMainThreadQueries()//允许在主线程操作数据库,一般不推荐;设置这个后主线程调用增删改查不会报错,否则会报错                    .openHelperFactory(factory)//                    .build()                databaseInstance = Room.databaseBuilder(                    BaseApplication.get(),                    AppRoomDataBase::class.java,                    DATABASE_ENCRYPT_NAME                )                    .allowMainThreadQueries()//允许在主线程操作数据库,一般不推荐;设置这个后主线程调用增删改查不会报错,否则会报错                    .openHelperFactory(factory)                    .build()                val dataEncryptTimes = SharedPreferencesManager.get("dataEncryptTimes", "0")                if ("0".equals(dataEncryptTimes)) {                    encrypt(                        DATABASE_ENCRYPT_NAME,                        DATABASE_NAME,                        DATABASE_ENCRYPT_KEY                    )                    SharedPreferencesManager.put("dataEncryptTimes", "1")                }            }            return databaseInstance!!        }

初始化数据库的方法(注意:加密或解密数据库时候,不要执行增删改查操作,不然有可能造成App崩溃)。

val appRoomDataBase = AppRoomDataBase.get()

7.查看加密过的数据库文件(先运行项目看一下加密前的数据库文件simple_app.db,然后改成加密方式,运行项目,会生成加密后的数据库文件simple_encrypt_app.db)。

Android查看加密前的数据库文件和加密后的数据库文件视频

如对此有疑问,请联系qq1164688204。

推荐Android开源项目

项目功能介绍:原本是RxJava2 和Retrofit2 项目,现已更新使用Kotlin+RxJava2+Retrofit2+MVP架构+组件化和
Kotlin+Retrofit2+协程+MVVM架构+组件化, 添加自动管理token 功能,添加RxJava2 生命周期管理,集成极光推送、阿里云Oss对象存储和高德地图定位功能。

项目地址:https://gitee.com/urasaki/RxJava2AndRetrofit2

来源地址:https://blog.csdn.net/NakajimaFN/article/details/130912193

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯