Android Room数据库加密的示例分析

Android Room数据库加密的示例分析

这篇“AndroidRoom数据库加密的示例分析”除了程序员外大部分人都不太理解,今天小编为了让大家更加理解“AndroidRoom数据库加密的示例分析”,给大家总结了以下内容,具有一定借鉴价值,内容详细步骤清晰,细节处理妥当,希望大家通过这篇文章有所收获,下面让我们一起来看看具体内容吧。

一、需求背景

Android平台自带的SQLite有一个致命的缺陷:不支持加密。这就导致存储在SQLite中的数据可以被任何人用任何文本编辑器查看到。如果是普通的数据还好,但是当涉及到一些账号密码,或者聊天内容的时候,我们的应用就会面临严重的安全漏洞隐患。

二、加密方案

1、在数据存储之前进行加密,在加载数据之后再进行解密,这种方法大概是最容易想的到,而且也不能说这种方式不好,就是有些比较繁琐。 如果项目有特殊需求的话,可能还需要对数据库的表明,列明也进行加密。

2、对数据库整个文件进行加密,好处就是就是无需在插入之前对数据加密,也无需在查询数据之后再解密。比较出名的第三方库就是SQLCipher,它采用的方式就是对数据库文件进行加密,只需在打开数据库的时候输入密码,之后的操作更正常操作没有区别。

三、Hook Room实现方式

前面说了,加密的方式一比较繁琐的地方是需要在存储数据之前加密,在检索数据之后解密,那么是否有一种方式在Room操作数据库的过程中,自动对数据加密解密,答案是有的。

Dao编译之后的代码是这样的:

@OverridepubliclongsaveCache(finalCacheTestcache){__db.assertNotSuspendingTransaction();__db.beginTransaction();try{//核心代码,绑定数据long_result=__insertionAdapterOfCacheTest.insertAndReturnId(cache);__db.setTransactionSuccessful();return_result;}finally{__db.endTransaction();}}

__insertionAdapterOfCacheTest 是在CacheDaoTest_Impl 的构造方法里面创建的一个匿名内部类,这个匿名内部类实现了bind 方法

publicCacheDaoTest_Impl(RoomDatabase__db){this.__db=__db;this.__insertionAdapterOfCacheTest=newEntityInsertionAdapter<CacheTest>(__db){@OverridepublicStringcreateQuery(){return"INSERTORREPLACEINTO`table_cache`(`key`,`name`)VALUES(?,?)";}@Overridepublicvoidbind(SupportSQLiteStatementstmt,CacheTestvalue){if(value.getKey()==null){stmt.bindNull(1);}else{stmt.bindString(1,value.getKey());}if(value.getName()==null){stmt.bindNull(2);}else{stmt.bindString(2,value.getName());}}};}

关于SQLiteStatement 不清楚的同学可以百度一下,简单说他就代表一句sql语句,bind 方法就是绑定sql语句所需要的参数,现在的问题是我们可否自定义一个SupportSQLiteStatement ,然后在bind的时候加密参数呢。

我们看一下SupportSQLiteStatement 的创建过程。

publicSupportSQLiteStatementacquire(){assertNotMainThread();returngetStmt(mLock.compareAndSet(false,true));}privateSupportSQLiteStatementgetStmt(booleancanUseCached){finalSupportSQLiteStatementstmt;//代码有删减stmt=createNewStatement();returnstmt;}kotlinprivateSupportSQLiteStatementcreateNewStatement(){Stringquery=createQuery();returnmDatabase.compileStatement(query);}

可以看到SupportSQLiteStatement 最终来自RoomDataBase的compileStatement 方法,这就给我们hook 提供了接口,我们只要自定义一个SupportSQLiteStatement 类来代理原来的SupportSQLiteStatement 就可以了。

encoder 就是用来加密数据的。

加密数据之后剩余的就是解密数据了,解密数据我们需要在哪里Hook呢?

我们知道数据库检索返回的数据一般都是通过Cursor 传递给用户,这里我们就可以通过代理数据库返回的这个Cursor 进而实现解密数据。

@Database(entities=[CacheTest::class],version=3)abstractclassTestDb:RoomDatabase(){abstractfuntestDao():CacheDaoTestcompanionobject{valMIGRATION_2_1:Migration=object:Migration(2,1){overridefunmigrate(database:SupportSQLiteDatabase){}}valMIGRATION_2_3:Migration=object:Migration(2,3){overridefunmigrate(database:SupportSQLiteDatabase){}}valMIGRATION_3_4:Migration=object:Migration(3,4){overridefunmigrate(database:SupportSQLiteDatabase){}}valMIGRATION_2_4:Migration=object:Migration(2,4){overridefunmigrate(database:SupportSQLiteDatabase){}}}privatevalencoder:IEncode=TestEncoder()overridefunquery(query:SupportSQLiteQuery):Cursor{varcusrosr=super.query(query)println("开始查询1")returnDencodeCursor(cusrosr,encoder)}overridefunquery(query:String,args:Array<outAny>?):Cursor{varcusrosr=super.query(query,args)println("开始查询2")returnDencodeCursor(cusrosr,encoder)}overridefunquery(query:SupportSQLiteQuery,signal:CancellationSignal?):Cursor{println("开始查询3")returnDencodeCursor(super.query(query,signal),encoder)}}

我们这里重写了RoomDatabase 的是query 方法,代理了原先的Cursor 。

classDencodeCursor(valdelete:Cursor,valencoder:IEncode):Cursor{//代码有删减overridefungetString(columnIndex:Int):String{returnencoder.decodeString(delete.getString(columnIndex))}}

如上,最终加密解密的都被hook在了Room框架中间。但是这种有两个个缺陷

加密解密的过程中不可以改变数据的类型,也就是整型在加密之后还必须是整型,整型在解密之后也必须是整型。同时有些字段可能不需要加密也不需要解密,例如自增长的整型的primary key。其实这种方式也比较好解决,可以规定key 为整数型,其余的数据一律是字符串。这样所有的树数字类型的数据都不需要参与加密解密的过程。

sql 与的参数必须是动态绑定的,而不是在sql语句中静态指定。

@Query("select*fromtable_cachewhere`key`=:primaryKey")fungetCache(primaryKey:String):LiveData<CacheTest>

@Query("select*fromtable_cachewhere`key`='123'")fungetCache():LiveData<CacheTest>

四、SQLCipher方式

SQLCipher 仿照官方的架构自己重写了一套代码,官方提供的各种数据库相关的类在SQLCipher 里面也是存在的而且名字都一样除了包名不同。

SQLCipher 与Room的结合方式同上面的情形是类似,也是通过代理的方式实现。由于Room需要的类跟SQLCipher 提供的类包名不一致,所以这里需要对SQLCipher 提供的类进行一下代理然后传递给Room架构使用就可以了。

funinit(context:Context){valmDataBase1=Room.databaseBuilder(context.applicationContext,TestDb::class.java,"user_login_info_db").openHelperFactory(SafeHelperFactory("".toByteArray())).build()}

这里主要需要自定义一个SupportSQLiteOpenHelper.Factory也就是SafeHelperFactory 这个SafeHelperFactory 完全是仿照Room架构默认的Factory 也就是FrameworkSQLiteOpenHelperFactory 实现。主要是用户创建一个用于打开数据库的SQLiteOpenHelper,主要的区别是自定义的Facttory 需要一个用于加密与解密的密码。
我们首先需要定义一个自己的OpenHelperFactory

publicclassSafeHelperFactoryimplementsSupportSQLiteOpenHelper.Factory{publicstaticfinalStringPOST_KEY_SQL_MIGRATE="PRAGMAcipher_migrate;";publicstaticfinalStringPOST_KEY_SQL_V3="PRAGMAcipher_compatibility=3;";finalprivatebyte[]passphrase;finalprivateOptionsoptions;publicSafeHelperFactory(byte[]passphrase,Optionsoptions){this.passphrase=passphrase;this.options=options;}/***{@inheritDoc}*/@OverridepublicSupportSQLiteOpenHelpercreate(SupportSQLiteOpenHelper.Configurationconfiguration){return(create(configuration.context,configuration.name,configuration.callback));}publicSupportSQLiteOpenHelpercreate(Contextcontext,Stringname,SupportSQLiteOpenHelper.Callbackcallback){//创建一个Helperreturn(newHelper(context,name,callback,passphrase,options));}privatevoidclearPassphrase(char[]passphrase){for(inti=0;i<passphrase.length;i++){passphrase[i]=(byte)0;}}

SafeHelperFactory 的create创建了一个Helper,这个Helper实现了Room框架的SupportSQLiteOpenHelper ,实际这个Helper 是个代理类被代理的类为OpenHelper ,OpenHelper 用于操作SQLCipher 提供的数据库类。

classHelperimplementsSupportSQLiteOpenHelper{privatefinalOpenHelperdelegate;privatefinalbyte[]passphrase;privatefinalbooleanclearPassphrase;Helper(Contextcontext,Stringname,Callbackcallback,byte[]passphrase,SafeHelperFactory.Optionsoptions){SQLiteDatabase.loadLibs(context);clearPassphrase=options.clearPassphrase;delegate=createDelegate(context,name,callback,options);this.passphrase=passphrase;}privateOpenHelpercreateDelegate(Contextcontext,Stringname,finalCallbackcallback,SafeHelperFactory.Optionsoptions){finalDatabase[]dbRef=newDatabase[1];return(newOpenHelper(context,name,dbRef,callback,options));}/***{@inheritDoc}*/@OverridesynchronizedpublicStringgetDatabaseName(){returndelegate.getDatabaseName();}/***{@inheritDoc}*/@Override@RequiresApi(api=Build.VERSION_CODES.JELLY_BEAN)synchronizedpublicvoidsetWriteAheadLoggingEnabled(booleanenabled){delegate.setWriteAheadLoggingEnabled(enabled);}@OverridesynchronizedpublicSupportSQLiteDatabasegetWritableDatabase(){SupportSQLiteDatabaseresult;try{result=delegate.getWritableSupportDatabase(passphrase);}catch(SQLiteExceptione){if(passphrase!=null){booleanisCleared=true;for(byteb:passphrase){isCleared=isCleared&&(b==(byte)0);}if(isCleared){thrownewIllegalStateException("Thepassphraseappearstobecleared.Thishappensby"+"defaultthefirsttimeyouusethefactorytoopenadatabase,sowecanremovethe"+"cleartextpassphrasefrommemory.Ifyouclosethedatabaseyourself,pleaseusea"+"freshSafeHelperFactorytoreopenit.Ifsomethingelse(e.g.,Room)closedthe"+"database,andyoucannotcontrolthat,useSafeHelperFactory.Optionstooptoutof"+"theautomaticpasswordclearingstep.SeetheprojectREADMEformoreinformation.");}}throwe;}if(clearPassphrase&&passphrase!=null){for(inti=0;i<passphrase.length;i++){passphrase[i]=(byte)0;}}return(result);}/***{@inheritDoc}**NOTE:thisimplementationdelegatestogetWritableDatabase(),toensure*thatweonlyneedthepassphraseonce*/@OverridepublicSupportSQLiteDatabasegetReadableDatabase(){return(getWritableDatabase());}/***{@inheritDoc}*/@Overridesynchronizedpublicvoidclose(){delegate.close();}staticclassOpenHelperextendsSQLiteOpenHelper{privatefinalDatabase[]dbRef;privatevolatileCallbackcallback;privatevolatilebooleanmigrated;}

真正操作数据库的类OpenHelper,OpenHelper 继承的SQLiteOpenHelper 是net.sqlcipher.database 包下的

staticclassOpenHelperextendsSQLiteOpenHelper{privatefinalDatabase[]dbRef;privatevolatileCallbackcallback;privatevolatilebooleanmigrated;OpenHelper(Contextcontext,Stringname,finalDatabase[]dbRef,finalCallbackcallback,finalSafeHelperFactory.Optionsoptions){super(context,name,null,callback.version,newSQLiteDatabaseHook(){@OverridepublicvoidpreKey(SQLiteDatabasedatabase){if(options!=null&&options.preKeySql!=null){database.rawExecSQL(options.preKeySql);}}@OverridepublicvoidpostKey(SQLiteDatabasedatabase){if(options!=null&&options.postKeySql!=null){database.rawExecSQL(options.postKeySql);}}},newDatabaseErrorHandler(){@OverridepublicvoidonCorruption(SQLiteDatabasedbObj){Databasedb=dbRef[0];if(db!=null){callback.onCorruption(db);}}});this.dbRef=dbRef;this.callback=callback;}synchronizedSupportSQLiteDatabasegetWritableSupportDatabase(byte[]passphrase){migrated=false;SQLiteDatabasedb=super.getWritableDatabase(passphrase);if(migrated){close();returngetWritableSupportDatabase(passphrase);}returngetWrappedDb(db);}synchronizedDatabasegetWrappedDb(SQLiteDatabasedb){DatabasewrappedDb=dbRef[0];if(wrappedDb==null){wrappedDb=newDatabase(db);dbRef[0]=wrappedDb;}return(dbRef[0]);}/***{@inheritDoc}*/@OverridepublicvoidonCreate(SQLiteDatabasesqLiteDatabase){callback.onCreate(getWrappedDb(sqLiteDatabase));}/***{@inheritDoc}*/@OverridepublicvoidonUpgrade(SQLiteDatabasesqLiteDatabase,intoldVersion,intnewVersion){migrated=true;callback.onUpgrade(getWrappedDb(sqLiteDatabase),oldVersion,newVersion);}/***{@inheritDoc}*/@OverridepublicvoidonConfigure(SQLiteDatabasedb){callback.onConfigure(getWrappedDb(db));}/***{@inheritDoc}*/@OverridepublicvoidonDowngrade(SQLiteDatabasedb,intoldVersion,intnewVersion){migrated=true;callback.onDowngrade(getWrappedDb(db),oldVersion,newVersion);}/***{@inheritDoc}*/@OverridepublicvoidonOpen(SQLiteDatabasedb){if(!migrated){//fromGoogle:"ifwe'vemigrated,we'llre-openthedbsoweshouldnotcallthecallback."callback.onOpen(getWrappedDb(db));}}/***{@inheritDoc}*/@Overridepublicsynchronizedvoidclose(){super.close();dbRef[0]=null;}}

这里的OpenHelper 完全是仿照Room 框架下的OpenHelper 实现的。

Android是什么

Android是一种基于Linux内核的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由美国Google公司和开放手机联盟领导及开发。

感谢你的阅读,希望你对“AndroidRoom数据库加密的示例分析”这一关键问题有了一定的理解,具体使用情况还需要大家自己动手实验使用过才能领会,快去试试吧,如果想阅读更多相关知识点的文章,欢迎关注恰卡编程网行业资讯频道!

发布于 2022-01-17 22:04:18
收藏
分享
海报
0 条评论
53
上一篇:Docker镜像分层及dockerfile编写技巧是什么 下一篇:spring Bean创建方法是什么
目录

    推荐阅读

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码