经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
Android?Room数据库加密详解
来源:jb51  时间:2022/1/17 14:21:55  对本文有异议

本文实例为大家分享了Android Room之数据库加密的具体实现,供大家参考,具体内容如下

一、需求背景

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

二、加密方案

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

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

三、Hook Room实现方式

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

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

  1. @Override
  2. public long saveCache(final CacheTest cache) {
  3. ? __db.assertNotSuspendingTransaction();
  4. ? __db.beginTransaction();
  5. ? try {
  6. ? //核心代码,绑定数据
  7. ? ? long _result = __insertionAdapterOfCacheTest.insertAndReturnId(cache);
  8. ? ? __db.setTransactionSuccessful();
  9. ? ? return _result;
  10. ? } finally {
  11. ? ? __db.endTransaction();
  12. ? }
  13. }

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

  1. public CacheDaoTest_Impl(RoomDatabase __db) {
  2. ? this.__db = __db;
  3. ? this.__insertionAdapterOfCacheTest = new EntityInsertionAdapter<CacheTest>(__db) {
  4. ? ? @Override
  5. ? ? public String createQuery() {
  6. ? ? ? return "INSERT OR REPLACE INTO `table_cache` (`key`,`name`) VALUES (?,?)";
  7. ? ? }
  8.  
  9. ? ? @Override
  10. ? ? public void bind(SupportSQLiteStatement stmt, CacheTest value) {
  11. ? ? ? if (value.getKey() == null) {
  12. ? ? ? ? stmt.bindNull(1);
  13. ? ? ? } else {
  14. ? ? ? ? stmt.bindString(1, value.getKey());
  15. ? ? ? }
  16. ? ? ? if (value.getName() == null) {
  17. ? ? ? ? stmt.bindNull(2);
  18. ? ? ? } else {
  19. ? ? ? ? stmt.bindString(2, value.getName());
  20. ? ? ? }
  21. ? ? }
  22. ? };
  23. }

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

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

  1. public SupportSQLiteStatement acquire() {
  2. ? ? ?assertNotMainThread();
  3. ? ? ?return getStmt(mLock.compareAndSet(false, true));
  4. ?}
  5. ?
  6. ?private SupportSQLiteStatement getStmt(boolean canUseCached) {
  7. ? ? ?final SupportSQLiteStatement stmt;
  8. ? ? ?//代码有删减
  9. ? ? ? ? stmt = createNewStatement();
  10. ? ? ?return stmt;
  11. ?}
  12.  
  13. kotlin
  14. ?private SupportSQLiteStatement createNewStatement() {
  15. ? ? ?String query = createQuery();
  16. ? ? ?return mDatabase.compileStatement(query);
  17. ?}

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

encoder 就是用来加密数据的。

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

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

  1. @Database(entities = [CacheTest::class], version = 3)
  2. abstract class TestDb : RoomDatabase() {
  3. ? ? abstract fun testDao(): CacheDaoTest
  4.  
  5. ? ? companion object {
  6. ? ? ? ? val MIGRATION_2_1: Migration = object : Migration(2, 1) {
  7. ? ? ? ? ? ? override fun migrate(database: SupportSQLiteDatabase) {
  8. ? ? ? ? ? ? }
  9. ? ? ? ? }
  10.  
  11. ? ? ? ? val MIGRATION_2_3: Migration = object : Migration(2, 3) {
  12. ? ? ? ? ? ? override fun migrate(database: SupportSQLiteDatabase) {
  13. ? ? ? ? ? ? }
  14. ? ? ? ? }
  15. ? ? ? ? val MIGRATION_3_4: Migration = object : Migration(3,4) {
  16. ? ? ? ? ? ? override fun migrate(database: SupportSQLiteDatabase) {
  17. ? ? ? ? ? ? }
  18. ? ? ? ? }
  19. ? ? ? ? val MIGRATION_2_4: Migration = object : Migration(2, 4) {
  20. ? ? ? ? ? ? override fun migrate(database: SupportSQLiteDatabase) {
  21. ? ? ? ? ? ? }
  22. ? ? ? ? }
  23.  
  24. ? ? }
  25.  
  26. ? ? private val encoder: IEncode = TestEncoder()
  27. ? ? override fun query(query: SupportSQLiteQuery): Cursor {
  28. ? ? ? ? var cusrosr = super.query(query)
  29. ? ? ? ? println("开始查询1")
  30. ? ? ? ? return DencodeCursor(cusrosr, encoder)
  31. ? ? }
  32.  
  33. ? ? override fun query(query: String, args: Array<out Any>?): Cursor {
  34. ? ? ? ? var cusrosr = super.query(query, args)
  35. ? ? ? ? println("开始查询2")
  36. ? ? ? ? return DencodeCursor(cusrosr, encoder)
  37. ? ? }
  38.  
  39. ? ? override fun query(query: SupportSQLiteQuery, signal: CancellationSignal?): Cursor {
  40. ? ? ? ? println("开始查询3")
  41. ? ? ? ? return DencodeCursor(super.query(query, signal), encoder)
  42. ? ? }
  43. }

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

  1. class DencodeCursor(val delete: Cursor, val encoder: IEncode) : Cursor {
  2. //代码有删减
  3. ? ? override fun getString(columnIndex: Int): String {
  4. ? ? ? ? return encoder.decodeString(delete.getString(columnIndex))
  5. ? ? }
  6. }

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

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

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

  1. @Query("select * from table_cache where `key`=:primaryKey")
  2. fun getCache(primaryKey: String): LiveData<CacheTest>
  1. @Query("select * from table_cache where `key`= '123' ")
  2. fun getCache(): LiveData<CacheTest>

四、SQLCipher方式

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

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

  1. fun init(context: Context) {
  2. ? val ?mDataBase1 = Room.databaseBuilder(
  3. ? ? ? ? context.applicationContext,
  4. ? ? ? ? TestDb::class.java,
  5. ? ? ? ? "user_login_info_db"
  6. ? ? ).openHelperFactory(SafeHelperFactory("".toByteArray()))
  7. ? ? ? .build()
  8. }

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

  1. public class SafeHelperFactory implements SupportSQLiteOpenHelper.Factory {
  2. ? public static final String POST_KEY_SQL_MIGRATE = "PRAGMA cipher_migrate;";
  3. ? public static final String POST_KEY_SQL_V3 = "PRAGMA cipher_compatibility = 3;";
  4.  
  5. ? final private byte[] passphrase;
  6. ? final private Options options;
  7.  
  8. ?
  9. ? public SafeHelperFactory(byte[] passphrase, Options options) {
  10. ? ? this.passphrase = passphrase;
  11. ? ? this.options = options;
  12. ? }
  13.  
  14. ? /**
  15. ? ?* {@inheritDoc}
  16. ? ?*/
  17. ? @Override
  18. ? public SupportSQLiteOpenHelper create(
  19. ? ? SupportSQLiteOpenHelper.Configuration configuration) {
  20. ? ? return(create(configuration.context, configuration.name,
  21. ? ? ? configuration.callback));
  22. ? }
  23.  
  24. ? public SupportSQLiteOpenHelper create(Context context, String name,
  25. ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? SupportSQLiteOpenHelper.Callback callback) {
  26. ? ? ?//创建一个Helper
  27. ? ? return(new Helper(context, name, callback, passphrase, options));
  28. ? }
  29.  
  30. ? private void clearPassphrase(char[] passphrase) {
  31. ? ? for (int i = 0; i < passphrase.length; i++) {
  32. ? ? ? passphrase[i] = (byte) 0;
  33. ? ? }
  34. ? }

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

  1. class Helper implements SupportSQLiteOpenHelper {
  2. ? private final OpenHelper delegate;
  3. ? private final byte[] passphrase;
  4. ? private final boolean clearPassphrase;
  5.  
  6. ? Helper(Context context, String name, Callback callback, byte[] passphrase,
  7. ? ? ? ? ?SafeHelperFactory.Options options) {
  8. ? ? SQLiteDatabase.loadLibs(context);
  9. ? ? clearPassphrase=options.clearPassphrase;
  10. ? ? delegate=createDelegate(context, name, callback, options);
  11. ? ? this.passphrase=passphrase;
  12. ? }
  13.  
  14. ? private OpenHelper createDelegate(Context context, String name,
  15. ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? final Callback callback, SafeHelperFactory.Options options) {
  16. ? ? final Database[] dbRef = new Database[1];
  17.  
  18. ? ? return(new OpenHelper(context, name, dbRef, callback, options));
  19. ? }
  20.  
  21. ? /**
  22. ? ?* {@inheritDoc}
  23. ? ?*/
  24. ? @Override
  25. ? synchronized public String getDatabaseName() {
  26. ? ? return delegate.getDatabaseName();
  27. ? }
  28.  
  29. ? /**
  30. ? ?* {@inheritDoc}
  31. ? ?*/
  32. ? @Override
  33. ? @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
  34. ? synchronized public void setWriteAheadLoggingEnabled(boolean enabled) {
  35. ? ? delegate.setWriteAheadLoggingEnabled(enabled);
  36. ? }
  37.  
  38.  
  39. ? @Override
  40. ? synchronized public SupportSQLiteDatabase getWritableDatabase() {
  41. ? ? SupportSQLiteDatabase result;
  42.  
  43. ? ? try {
  44. ? ? ? result = delegate.getWritableSupportDatabase(passphrase);
  45. ? ? }
  46. ? ? catch (SQLiteException e) {
  47. ? ? ? if (passphrase != null) {
  48. ? ? ? ? boolean isCleared = true;
  49.  
  50. ? ? ? ? for (byte b : passphrase) {
  51. ? ? ? ? ? isCleared = isCleared && (b == (byte) 0);
  52. ? ? ? ? }
  53.  
  54. ? ? ? ? if (isCleared) {
  55. ? ? ? ? ? throw new IllegalStateException("The passphrase appears to be cleared. This happens by" +
  56. ? ? ? ? ? ? ? "default the first time you use the factory to open a database, so we can remove the" +
  57. ? ? ? ? ? ? ? "cleartext passphrase from memory. If you close the database yourself, please use a" +
  58. ? ? ? ? ? ? ? "fresh SafeHelperFactory to reopen it. If something else (e.g., Room) closed the" +
  59. ? ? ? ? ? ? ? "database, and you cannot control that, use SafeHelperFactory.Options to opt out of" +
  60. ? ? ? ? ? ? ? "the automatic password clearing step. See the project README for more information.");
  61. ? ? ? ? }
  62. ? ? ? }
  63.  
  64. ? ? ? throw e;
  65. ? ? }
  66.  
  67. ? ? if (clearPassphrase && passphrase != null) {
  68. ? ? ? for (int i = 0; i < passphrase.length; i++) {
  69. ? ? ? ? passphrase[i] = (byte) 0;
  70. ? ? ? }
  71. ? ? }
  72.  
  73. ? ? return(result);
  74. ? }
  75.  
  76. ? /**
  77. ? ?* {@inheritDoc}
  78. ? ?*
  79. ? ?* NOTE: this implementation delegates to getWritableDatabase(), to ensure
  80. ? ?* that we only need the passphrase once
  81. ? ?*/
  82. ? @Override
  83. ? public SupportSQLiteDatabase getReadableDatabase() {
  84. ? ? return(getWritableDatabase());
  85. ? }
  86.  
  87. ? /**
  88. ? ?* {@inheritDoc}
  89. ? ?*/
  90. ? @Override
  91. ? synchronized public void close() {
  92. ? ? delegate.close();
  93. ? }
  94.  
  95. ? static class OpenHelper extends SQLiteOpenHelper {
  96. ? ? private final Database[] dbRef;
  97. ? ? private volatile Callback callback;
  98. ? ? private volatile boolean migrated;
  99. }

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

  1. static class OpenHelper extends SQLiteOpenHelper {
  2. ? ? private final Database[] dbRef;
  3. ? ? private volatile Callback callback;
  4. ? ? private volatile boolean migrated;
  5. ?OpenHelper(Context context, String name, final Database[] dbRef, final Callback callback,
  6. ? ? ? ? ? ? ? ?final SafeHelperFactory.Options options) {
  7. ? ? ? super(context, name, null, callback.version, new SQLiteDatabaseHook() {
  8. ? ? ? ? @Override
  9. ? ? ? ? public void preKey(SQLiteDatabase database) {
  10. ? ? ? ? ? if (options!=null && options.preKeySql!=null) {
  11. ? ? ? ? ? ? database.rawExecSQL(options.preKeySql);
  12. ? ? ? ? ? }
  13. ? ? ? ? }
  14.  
  15. ? ? ? ? @Override
  16. ? ? ? ? public void postKey(SQLiteDatabase database) {
  17. ? ? ? ? ? if (options!=null && options.postKeySql!=null) {
  18. ? ? ? ? ? ? database.rawExecSQL(options.postKeySql);
  19. ? ? ? ? ? }
  20. ? ? ? ? }
  21. ? ? ? }, new DatabaseErrorHandler() {
  22. ? ? ? ? @Override
  23. ? ? ? ? public void onCorruption(SQLiteDatabase dbObj) {
  24. ? ? ? ? ? Database db = dbRef[0];
  25.  
  26. ? ? ? ? ? if (db != null) {
  27. ? ? ? ? ? ? callback.onCorruption(db);
  28. ? ? ? ? ? }
  29. ? ? ? ? }
  30. ? ? ? });
  31.  
  32. ? ? ? this.dbRef = dbRef;
  33. ? ? ? this.callback=callback;
  34. ? ? }
  35.  
  36. ? ? synchronized SupportSQLiteDatabase getWritableSupportDatabase(byte[] passphrase) {
  37. ? ? ? migrated = false;
  38.  
  39. ? ? ? SQLiteDatabase db=super.getWritableDatabase(passphrase);
  40.  
  41. ? ? ? if (migrated) {
  42. ? ? ? ? close();
  43. ? ? ? ? return getWritableSupportDatabase(passphrase);
  44. ? ? ? }
  45.  
  46. ? ? ? return getWrappedDb(db);
  47. ? ? }
  48.  
  49. ? ? synchronized Database getWrappedDb(SQLiteDatabase db) {
  50. ? ? ? Database wrappedDb = dbRef[0];
  51.  
  52. ? ? ? if (wrappedDb == null) {
  53. ? ? ? ? wrappedDb = new Database(db);
  54. ? ? ? ? dbRef[0] = wrappedDb;
  55. ? ? ? }
  56.  
  57. ? ? ? return(dbRef[0]);
  58. ? ? }
  59.  
  60. ? ? /**
  61. ? ? ?* {@inheritDoc}
  62. ? ? ?*/
  63. ? ? @Override
  64. ? ? public void onCreate(SQLiteDatabase sqLiteDatabase) {
  65. ? ? ? callback.onCreate(getWrappedDb(sqLiteDatabase));
  66. ? ? }
  67.  
  68. ? ? /**
  69. ? ? ?* {@inheritDoc}
  70. ? ? ?*/
  71. ? ? @Override
  72. ? ? public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
  73. ? ? ? migrated = true;
  74. ? ? ? callback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
  75. ? ? }
  76.  
  77. ? ? /**
  78. ? ? ?* {@inheritDoc}
  79. ? ? ?*/
  80. ? ? @Override
  81. ? ? public void onConfigure(SQLiteDatabase db) {
  82. ? ? ? callback.onConfigure(getWrappedDb(db));
  83. ? ? }
  84.  
  85. ? ? /**
  86. ? ? ?* {@inheritDoc}
  87. ? ? ?*/
  88. ? ? @Override
  89. ? ? public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  90. ? ? ? migrated = true;
  91. ? ? ? callback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
  92. ? ? }
  93.  
  94. ? ? /**
  95. ? ? ?* {@inheritDoc}
  96. ? ? ?*/
  97. ? ? @Override
  98. ? ? public void onOpen(SQLiteDatabase db) {
  99. ? ? ? if (!migrated) {
  100. ? ? ? ? // from Google: "if we've migrated, we'll re-open the db so we ?should not call the callback."
  101. ? ? ? ? callback.onOpen(getWrappedDb(db));
  102. ? ? ? }
  103. ? ? }
  104.  
  105. ? ? /**
  106. ? ? ?* {@inheritDoc}
  107. ? ? ?*/
  108. ? ? @Override
  109. ? ? public synchronized void close() {
  110. ? ? ? super.close();
  111. ? ? ? dbRef[0] = null;
  112. ? ? }
  113. ? }

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持w3xue。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号