我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android Room数据库加密详解

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

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android Room数据库加密详解

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

一、需求背景

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

二、加密方案

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

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

三、Hook Room实现方式

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

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

@Override
public long saveCache(final CacheTest cache) {
  __db.assertNotSuspendingTransaction();
  __db.beginTransaction();
  try {
  //核心代码,绑定数据
    long _result = __insertionAdapterOfCacheTest.insertAndReturnId(cache);
    __db.setTransactionSuccessful();
    return _result;
  } finally {
    __db.endTransaction();
  }
}

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

public CacheDaoTest_Impl(RoomDatabase __db) {
  this.__db = __db;
  this.__insertionAdapterOfCacheTest = new EntityInsertionAdapter<CacheTest>(__db) {
    @Override
    public String createQuery() {
      return "INSERT OR REPLACE INTO `table_cache` (`key`,`name`) VALUES (?,?)";
    }

    @Override
    public void bind(SupportSQLiteStatement stmt, CacheTest value) {
      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 的创建过程。

public SupportSQLiteStatement acquire() {
     assertNotMainThread();
     return getStmt(mLock.compareAndSet(false, true));
 }
 
 private SupportSQLiteStatement getStmt(boolean canUseCached) {
     final SupportSQLiteStatement stmt;
     //代码有删减
        stmt = createNewStatement();
     return stmt;
 }

kotlin
 private SupportSQLiteStatement createNewStatement() {
     String query = createQuery();
     return mDatabase.compileStatement(query);
 }

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

encoder 就是用来加密数据的。

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

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

@Database(entities = [CacheTest::class], version = 3)
abstract class TestDb : RoomDatabase() {
    abstract fun testDao(): CacheDaoTest

    companion object {
        val MIGRATION_2_1: Migration = object : Migration(2, 1) {
            override fun migrate(database: SupportSQLiteDatabase) {
            }
        }

        val MIGRATION_2_3: Migration = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
            }
        }
        val MIGRATION_3_4: Migration = object : Migration(3,4) {
            override fun migrate(database: SupportSQLiteDatabase) {
            }
        }
        val MIGRATION_2_4: Migration = object : Migration(2, 4) {
            override fun migrate(database: SupportSQLiteDatabase) {
            }
        }

    }

    private val encoder: IEncode = TestEncoder()
    override fun query(query: SupportSQLiteQuery): Cursor {
        var cusrosr = super.query(query)
        println("开始查询1")
        return DencodeCursor(cusrosr, encoder)
    }

    override fun query(query: String, args: Array<out Any>?): Cursor {
        var cusrosr = super.query(query, args)
        println("开始查询2")
        return DencodeCursor(cusrosr, encoder)
    }

    override fun query(query: SupportSQLiteQuery, signal: CancellationSignal?): Cursor {
        println("开始查询3")
        return DencodeCursor(super.query(query, signal), encoder)
    }
}

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

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

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

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

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

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

四、SQLCipher方式

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

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

fun init(context: Context) {
  val  mDataBase1 = 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

public class SafeHelperFactory implements SupportSQLiteOpenHelper.Factory {
  public static final String POST_KEY_SQL_MIGRATE = "PRAGMA cipher_migrate;";
  public static final String POST_KEY_SQL_V3 = "PRAGMA cipher_compatibility = 3;";

  final private byte[] passphrase;
  final private Options options;

 
  public SafeHelperFactory(byte[] passphrase, Options options) {
    this.passphrase = passphrase;
    this.options = options;
  }

  
  @Override
  public SupportSQLiteOpenHelper create(
    SupportSQLiteOpenHelper.Configuration configuration) {
    return(create(configuration.context, configuration.name,
      configuration.callback));
  }

  public SupportSQLiteOpenHelper create(Context context, String name,
                                        SupportSQLiteOpenHelper.Callback callback) {
     //创建一个Helper
    return(new Helper(context, name, callback, passphrase, options));
  }

  private void clearPassphrase(char[] passphrase) {
    for (int i = 0; i < passphrase.length; i++) {
      passphrase[i] = (byte) 0;
    }
  }

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

class Helper implements SupportSQLiteOpenHelper {
  private final OpenHelper delegate;
  private final byte[] passphrase;
  private final boolean clearPassphrase;

  Helper(Context context, String name, Callback callback, byte[] passphrase,
         SafeHelperFactory.Options options) {
    SQLiteDatabase.loadLibs(context);
    clearPassphrase=options.clearPassphrase;
    delegate=createDelegate(context, name, callback, options);
    this.passphrase=passphrase;
  }

  private OpenHelper createDelegate(Context context, String name,
                                    final Callback callback, SafeHelperFactory.Options options) {
    final Database[] dbRef = new Database[1];

    return(new OpenHelper(context, name, dbRef, callback, options));
  }

  
  @Override
  synchronized public String getDatabaseName() {
    return delegate.getDatabaseName();
  }

  
  @Override
  @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
  synchronized public void setWriteAheadLoggingEnabled(boolean enabled) {
    delegate.setWriteAheadLoggingEnabled(enabled);
  }


  @Override
  synchronized public SupportSQLiteDatabase getWritableDatabase() {
    SupportSQLiteDatabase result;

    try {
      result = delegate.getWritableSupportDatabase(passphrase);
    }
    catch (SQLiteException e) {
      if (passphrase != null) {
        boolean isCleared = true;

        for (byte b : passphrase) {
          isCleared = isCleared && (b == (byte) 0);
        }

        if (isCleared) {
          throw new IllegalStateException("The passphrase appears to be cleared. This happens by" +
              "default the first time you use the factory to open a database, so we can remove the" +
              "cleartext passphrase from memory. If you close the database yourself, please use a" +
              "fresh SafeHelperFactory to reopen it. If something else (e.g., Room) closed the" +
              "database, and you cannot control that, use SafeHelperFactory.Options to opt out of" +
              "the automatic password clearing step. See the project README for more information.");
        }
      }

      throw e;
    }

    if (clearPassphrase && passphrase != null) {
      for (int i = 0; i < passphrase.length; i++) {
        passphrase[i] = (byte) 0;
      }
    }

    return(result);
  }

  
  @Override
  public SupportSQLiteDatabase getReadableDatabase() {
    return(getWritableDatabase());
  }

  
  @Override
  synchronized public void close() {
    delegate.close();
  }

  static class OpenHelper extends SQLiteOpenHelper {
    private final Database[] dbRef;
    private volatile Callback callback;
    private volatile boolean migrated;
}

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

static class OpenHelper extends SQLiteOpenHelper {
    private final Database[] dbRef;
    private volatile Callback callback;
    private volatile boolean migrated;
 OpenHelper(Context context, String name, final Database[] dbRef, final Callback callback,
               final SafeHelperFactory.Options options) {
      super(context, name, null, callback.version, new SQLiteDatabaseHook() {
        @Override
        public void preKey(SQLiteDatabase database) {
          if (options!=null && options.preKeySql!=null) {
            database.rawExecSQL(options.preKeySql);
          }
        }

        @Override
        public void postKey(SQLiteDatabase database) {
          if (options!=null && options.postKeySql!=null) {
            database.rawExecSQL(options.postKeySql);
          }
        }
      }, new DatabaseErrorHandler() {
        @Override
        public void onCorruption(SQLiteDatabase dbObj) {
          Database db = dbRef[0];

          if (db != null) {
            callback.onCorruption(db);
          }
        }
      });

      this.dbRef = dbRef;
      this.callback=callback;
    }

    synchronized SupportSQLiteDatabase getWritableSupportDatabase(byte[] passphrase) {
      migrated = false;

      SQLiteDatabase db=super.getWritableDatabase(passphrase);

      if (migrated) {
        close();
        return getWritableSupportDatabase(passphrase);
      }

      return getWrappedDb(db);
    }

    synchronized Database getWrappedDb(SQLiteDatabase db) {
      Database wrappedDb = dbRef[0];

      if (wrappedDb == null) {
        wrappedDb = new Database(db);
        dbRef[0] = wrappedDb;
      }

      return(dbRef[0]);
    }

    
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
      callback.onCreate(getWrappedDb(sqLiteDatabase));
    }

    
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
      migrated = true;
      callback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
    }

    
    @Override
    public void onConfigure(SQLiteDatabase db) {
      callback.onConfigure(getWrappedDb(db));
    }

    
    @Override
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
      migrated = true;
      callback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
    }

    
    @Override
    public void onOpen(SQLiteDatabase db) {
      if (!migrated) {
        // from Google: "if we've migrated, we'll re-open the db so we  should not call the callback."
        callback.onOpen(getWrappedDb(db));
      }
    }

    
    @Override
    public synchronized void close() {
      super.close();
      dbRef[0] = null;
    }
  }

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

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

免责声明:

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

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

Android Room数据库加密详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

这篇“Android Room数据库加密的示例分析”除了程序员外大部分人都不太理解,今天小编为了让大家更加理解“Android Room数据库加密的示例分析”,给大家总结了以下内容,具有一定借鉴价值,内容详细步骤清晰,细节处理妥当,希望大家
2023-06-26

详解Android数据存储之SQLCipher数据库加密

前言: 最近研究了Android Sqlite数据库以及ContentProvider程序间数据共享,我们清晰的知道Sqlite数据库默认存放位置data/data/pakage/database目录下,对于已经ROOT的手机来说的没有任何
2022-06-06

Android数据加密之Des加密详解

Android DES加密的相关实现,简单的实现了一下,今天来总结一下:DES加密介绍: DES是一种对称加密算法,所谓对称加密算法即:加密和解密使用相同密钥的算法。DES加密算法出自IBM的研究, 后来被美国政府正式采用,之后开始广泛流传
2022-06-06

Android使用AIDL共享Room数据库

什么是AIDL: AIDL(Android Interface Definition Language),翻译成中文就是安卓接口定义语言的意思,是用于定义服务端和客户端通信接口的一种描述语言。其主要作用是IPC(Android进程间通讯),
2022-06-06

Android本地数据存储之Room详细使用

@Database:Room数据库对象。该类需要继承自RoomDatabase,通过Room.databaseBuilder()结合单例设计模式,完成数据库的创建工作。我们创建的Dao对象,在这里以抽象方法的形式返回,只需一行代码即可。

Android数据加密之Rsa加密

前言:最近无意中和同事交流数据安全传输的问题,想起自己曾经使用过的Rsa非对称加密算法,闲下来总结一下。 其他几种加密方式: Android数据加密之Rsa加密 Android数据加密之Aes加密 Android数据加密之Des加密
2022-06-06

Android数据加密之Aes加密

前言:项目中除了登陆,支付等接口采用rsa非对称加密,之外的采用aes对称加密,今天我们来认识一下aes加密。 其他几种加密方式: Android数据加密之Rsa加密 Android数据加密之Aes加密 Android数据加密之Des
2022-06-06

Android数据加密之Des加密

前言: 有个同事咨询我有关Android DES加密的相关实现,简单的实现了一下,今天来总结一下。 其他几种加密方式: Android数据加密之Rsa加密 Android数据加密之Aes加密 Android数据加密之Des加密 An
2022-06-06

Android 加密解密字符串详解

加密和解密的字符串: 代码如下:package eoe.demo; import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.Key
2022-06-06

Android 安全加密:对称加密详解

Android安全加密专题文章索引Android安全加密:对称加密Android安全加密:非对称加密Android安全加密:消息摘要Message DigestAndroid安全加密:数字签名和数字证书Android安全加密:Https编程
2022-06-06

解析Android数据加密之异或加密算法

前言:这几天被公司临时拉到去做Android IM即时通信协议实现,大致看了下他们定的协议,由于之前没有参与,据说因服务器性能限制,只达成非明文传递,具体原因我不太清楚,不过这里用的加密方式是采用异或加密。这种加密方式在之前做Android
2022-06-06

SQL Server数据库备份加密的方法详解

目录引言1. 数据备份加密的重要性2. SQL Server备份加密的基本原理3. 使用透明数据加密(TDE)4. 使用备份加密选项5. 管理加密密钥6. 定期更换加密密钥7. 备份加密的测试和验证8. 监控和审计备份加密9. 结论引言在
SQL Server数据库备份加密的方法详解
2024-08-09

Android 安全加密:非对称加密详解

Android安全加密专题文章索引Android安全加密:对称加密Android安全加密:非对称加密Android安全加密:消息摘要Message DigestAndroid安全加密:数字签名和数字证书Android安全加密:Https编程
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录