使用此代码,用户可以将我的数据库、共享首选项和其他内部应用程序数据压缩为备份文件.

zip file backup

content of backup file

用户还可以通过从文件管理器中 Select zip文件来恢复备份文件.

Although the restoring works, how can I prevent the user by restoring some "random" zip file which was not created by my app.

我的解决方案很少是:

  • 判断是否存在数据库文件夹,以及数据库sqlite方案是否与app sqlite数据库方案匹配(这是一个本地数据库).
  • 添加一些无法查看或编辑的"隐藏"元数据.(不确定这是否可行).
  • 判断ZIP文件是否加密,密码是否匹配,文件夹方案是否通常与备份文件夹方案匹配.
  • 一般来说,我相信用户输入的文件夹是正确的,尽管我不喜欢这种解决方案.

推荐答案

First是判断文件(而不是数据库)header中的魔法头字符串.i、 e.它是一个有效的数据库吗.

只需打开文件并读取前16个字节,它必须是SQLite format 3\00053 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00十六进制.

Second,然后可以判断用户_版本(偏移量60表示4字节),该版本应与数据库版本匹配(从而防止恢复过时的版本).如果使用SQLiteOpenHelper访问数据库,则会根据编译和生成发行版时使用的版本号维护此值.

添加一些无法查看或编辑的"隐藏"元数据.(不确定这是否可行).

Third,您可以再次使用头,但这次是偏移量68(4字节)处的应用程序ID,这将是未使用的.这可以以与版本号类似的方式使用,但您必须实现它的维护(设置/更新).

  • 前两种方法几乎不需要什么,而且可以防止大多数意外情况.

  • 第三个是应用程序ID,它提供了更多的保护,防止使用具有有效版本号的有效SQLite数据库.

  • 没有人能防止故意虐待(为什么这样的意图值得怀疑).然而,这可能会导致一个例外.

如果前3项不足,那么可以打开数据库,询问sqlite_master,查看模式是否符合预期.


也许考虑一下房间使用的元数据.

Room根据@Entity注释的类和存储在Room_master_表中的数据库中的哈希值,根据预期的模式哈希值进行模式判断.这相当于你的元数据方法.

e、 g.当一个Room项目被编译时,在生成的java中,它会有代码,在createAllTables方法中,比如:-

_db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
_db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c9583474ce546ff5ead43c63fe049bc8')");

现在,如果数据库不存在,则存储哈希.如果数据库确实存在,那么它会判断存储在room_master_表中的哈希是否匹配.如果没有,那么,如果对适当的迁移进行了编码,并且版本号已更改,则会调用适当的迁移,如果架构匹配,则会调用存储,否则会引发异常.如果哈希不匹配且没有适当的迁移,则会引发异常(需要迁移),或者如果对FallbackToDetrictiveMigration进行了编码,则会从头开始创建数据库.

因此,房间数据库(如上所述)将包括:-

enter image description here

First的替代方法是使用/覆盖DatabaseErrorHandler's onCorruption方法.

这里有一个方法(尽管相当冗长),它使用这种技术,并额外判断是否有任何表(但不是全部):-

  /**************************************************************************
     *
     * @return false if the backup file is invalid.
     *
     *  determine by creating a differently name database (prefixed with IC),
     *  openeing it with it's own helper (does nothing) and then trying to
     *  check if there are tables in the database.
     *  No tables reflects that file is invalid type.
     *
     *  Note! if an attempt to open an invalid database file then SQLite deletes the file.
     */
    private boolean dataBaseIntegrityCheck() {
        String methodname = new Object() {
        }.getClass().getEnclosingMethod().getName();
        LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL, LOGTAG, "Invoked", this, methodname);

        @SuppressWarnings("UnusedAssignment") final String THIS_METHOD = "dataBaseIntegrityCheck";
        //String sqlstr_mstr = "SELECT name FROM sqlite_master WHERE type = 'table' AND name!='android_metadata' ORDER by name;";
        Cursor iccsr;
        boolean rv = true;

        //Note no use having the handler as it actually introduces problems  as SQLite assumes that
        // the handler will restore the database.
        // No need to comment out as handler can be disabled by not not passing it as a parameter
        // of the DBHelper
        @SuppressWarnings("UnusedAssignment") DatabaseErrorHandler myerrorhandler = new DatabaseErrorHandler() {
            @Override
            public void onCorruption(SQLiteDatabase sqLiteDatabase) {
            }
        };
        try {
            FileInputStream bkp = new FileInputStream(backupfilename);
            OutputStream ic = new FileOutputStream(icdbfilename);
            while ((copylength = bkp.read(buffer)) > 0) {
                ic.write(buffer, 0, copylength);
            }
            ic.close();
            bkp.close();
            icfile = new File(icdbfilename);


            //Note SQLite will actually check for corruption and if so delete the file
            //
            IntegrityCheckDBHelper icdbh = new IntegrityCheckDBHelper(this, null, null, 1, null);
            SQLiteDatabase icdb = icdbh.getReadableDatabase();
            iccsr = icdb.query("sqlite_master",
                    new String[]{"name"},
                    "type=? AND name!=?",
                    new String[]{"table", "android_metadata"},
                    null, null,
                    "name"
            );

            //Check to see if there are any tables, if wrong file type shouldn't be any
            //iccsr = icdb.rawQuery(sqlstr_mstr,null);
            if (iccsr.getCount() < 1) {
                errlist.add("Integrity Check extract from sqlite_master returned nothing - Propsoed file is corrupt or not a database file.");
                rv = false;
            }
            iccsr.close();
            icdb.close();

        } catch (IOException e) {
            e.printStackTrace();
            errlist.add("Integrity Check Failed Error Message was " + e.getMessage());
        }

        if (!rv) {
            AlertDialog.Builder notokdialog = new AlertDialog.Builder(this);
            notokdialog.setTitle("Invalid Restore File.");
            notokdialog.setCancelable(true);
            String msg = "File " + backupfilename + " is an invalid file." +
                    "\n\nThe Restore cannot continue and will be canclled. " +
                    "\n\nPlease Use a Valid Database Backup File!";
            notokdialog.setMessage(msg);
            notokdialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                }
            }).show();
        }
        // Delete the Integrity Check File (Database copy)
        //noinspection ResultOfMethodCallIgnored
        icfile.delete();
        return rv;
    }
  • 注意:这包括打开日志(log)记录时的日志(log)记录和消息存储/检索,因此,如果遇到许多消息,可以检索它们.这就是长风的一部分.

Android相关问答推荐

如何将Hilt添加到Android Studio中的Kotlin项目中?

在Android Studio Iguana 2023.2.1中,哪里可以找到能量分析器?

在Kotlin Jetpack Compose中点击按钮后启动另一个Android应用程序

用于小部件泄漏警告的伙伴对象中的Kotlin Lateinit

使用Jetpack Compose创建特定于电视的布局

第一次使用onBackPressed()、NavigateUp()加载时MapView崩溃

如何在DownloadManager Android中显示ProgressBar和Complete Listener

制作圆形SupportMapFragment

如何在C++中使用JNI_GetCreatedJavaVMs调用Java代码

为什么第二个代码可以安全地在 map 中进行网络调用,因为它已被缓存?

如何在这段代码android jetpack compose中实现全屏滚动

在单元测试下断言协程未完成

在 kotlin 中动态添加 GridView

java.lang.ExceptionInInitializerError -- 原因:java.lang.NullPointerException

在Android RoomDB中使用Kotlin Flow和删除数据时如何解决错误?

未解决的参考:pagerTabIndicatorOffset

如何在 JetpackCompose 的 LazyColumn 中 Select 多个项目

如何在 Jetpack Compose 中的 VisualTransformation 之后将光标保持在文本字段的末尾

更改 Android SDK 版本 33 后建议在 xml 布局文件中不起作用

线圈单元测试 - 如何做到这一点?