【问题标题】:How to encrypt sqlite texts without 3rd party libraries (Android)如何在没有第三方库的情况下加密 sqlite 文本(Android)
【发布时间】:2019-10-06 06:44:18
【问题描述】:

我在我的 android 应用程序中使用了一个外部数据库,它在编译后直接嵌入到 apk 包中。 因为我想在应用程序购买中实现以访问它的一些数据,所以我不想让它没有任何加密。 我使用了 Sqlcipher 库,但它使应用程序太大且太慢。 没有其他方法可以做到这一点吗?例如加密字符串的算法,所以我将加密的文本放入数据库并在应用程序代码中解密?

【问题讨论】:

  • 您想让潜在的攻击者解密您的秘密文本有多容易?
  • Yunnosch 不太复杂。足以防止他们直接看到数据库记录
  • 您可能会发现这很有趣Encrypt data in SQLite 它是部分数据加密/解密(旨在仅保护敏感数据以及可能有多个用户时的用户级别)。如果我理解正确,您必须在其他地方进行加密(可能是您提供密码的支持应用程序,它加密数据库购买者获取密码)。

标签: android sqlite android-studio


【解决方案1】:

以下是一个示例应用程序,它对部分数据进行加密,然后可以打开。它基于我评论中的代码。

第 1 阶段 - 主数据库。

首先您需要作为加密数据库基础的数据库(即不包含在应用程序中的MASTER数据库,它的用途是创建加密数据库(或数据库,也许是一个库,如果您想要更高的安全性,每个数据库都有一个唯一的密码/密钥))部分考虑这一点(在整个示例中使用):-

正如您所见,这将通过一个名为 FreeData 的表和另一个名为 PaidData 的表来工作。除了 PaidData 没有 ID 列之外,表定义相同(此方法的目的是在/如果请求且 SecretKey(密码)有效时将 PaidData 中的行解密为 FreeData。)。

所以 FreeData 表看起来像:-

PaidData 表如下所示:-

  • 因此,表之间的唯一区别是其中包含的实际数据以及缺少 id 列。
  • PaidData 表中提取加密数据、解密并插入到 FreeData 表中时会生成 ID。因此,只需一次解密即可访问数据。

第 2 阶段 - 生成加密数据库以与应用程序一起分发

这是由一个应用程序完成的,该应用程序使用与 Encrypt data in SQLite 非常相似的 EncryptDecrypt 类

根据 EncryptDecrypt.java

class EncryptDecrypt {
    private Cipher cipher;
    private static SecretKeySpec secretKeySpec;
    private static IvParameterSpec ivParameterSpec;
    private boolean do_encrypt = true;

    /**
     * Construct EncryptDecrypt instance that does not check user login-in
     * mode, thus the assumption is that this user is NOT the special user
     * NOUSER that doesn't require a password to login; this constructor
     * is designed to ONLY be used when a user has been added by NOUSER,
     * and to then encrypt the data using the enccryptForced method solely
     * to encrypt any existing card data for the new user that has a password.
     *
     * @param context   The context, required for database usage (user)
     * @param skey      The secret key to be used to encrypt/decrypt
     */
    EncryptDecrypt(Context context, String skey) {
        //DBUsersMethods users = new DBUsersMethods(context);
        String saltasString = "there is no dark side of the moon it is all dark.";
        String paddedskey = (skey + saltasString).substring(0,16);
        secretKeySpec = new SecretKeySpec(paddedskey.getBytes(),"AES/CBC/PKCS5Padding");
        ivParameterSpec = new IvParameterSpec((saltasString.substring(0,16)).getBytes());
        try {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        } catch (Exception e){
            //e.printStackTrace();
        }
    }

    /**
     * Normal encryption routine that will not encrypt data if the user is
     * the special case NOUSER (i.e LOGIN mode is NOUSER), otherwise data
     * is encrypted.
     *
     * @Param toEncrypt     The string to be encrypted
     * @return              The encryted (or not if NOUSER) data as a string
     */
    String encrypt(String toEncrypt) {
        if (!do_encrypt) {
            return toEncrypt;
        }
        byte[] encrypted;
        try {
            cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivParameterSpec);
            encrypted = cipher.doFinal(toEncrypt.getBytes());
        } catch (Exception e) {
            //e.printStackTrace();
            return null;
        }
        return Base64.encodeToString(encrypted, Base64.DEFAULT);
    }

    /**
     * Encryption, irrespective of the USER type, noting that this should
     * only be used in conjunction with an EncryptDecrypt instance created
     * using the 2nd/extended constructor
     *
     * @param toEncrypt     The string to be encrypted
     * @return              The encrypted data as a string
     */
    String encryptForced(String toEncrypt) {
        byte[] encrypted;
        try {
            cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivParameterSpec);
            encrypted = cipher.doFinal(toEncrypt.getBytes());
        } catch (Exception e) {
            //e.printStackTrace();
            return null;
        }
        return Base64.encodeToString(encrypted,Base64.DEFAULT);
    }

    /**
     * Decrypt an encrypted string
     * @param toDecrypt     The encrypted string to be decrypted
     * @return              The decrypted string
     */
    String decrypt(String toDecrypt)  {
        if (!do_encrypt) {
            return toDecrypt;
        }
        byte[] decrypted;
        try {
            cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,ivParameterSpec);
            decrypted = cipher.doFinal(Base64.decode(toDecrypt,Base64.DEFAULT));
        } catch (Exception e) {
            //e.printStackTrace();
            return null;
        }
        return new String(decrypted);
    }
}
  • 因为这是为用户登录和多个用户而设计的,其中盐是数据库的一部分,盐已经使用:-String saltasString = "there is no dark side of the moon it is all dark."; 硬编码,只要短语长度至少为 16 个字符,就可以更改(仅使用前 16 个字节)。

一个类用于满足潜在的灵活性/扩展性,其中可以指定或不指定多个表进行加密,以及可以加密、复制或跳过的多个列(例如,id 可能会被跳过(在示例中它不是甚至定义为列)。)。

这个类是 TableColumnConvertList.java 并且是:-

public class TableColumnConvertList {
    private ArrayList<TableEntry> tables;

    public TableColumnConvertList() {
        this.tables = new ArrayList<>();
    }

    public String[] getTables() {
        String[] tableList = new String[tables.size()];
        int ix = 0;
        for (TableEntry te: this.tables) {
                tableList[ix++] = te.getSourceTableName();
        }
        return tableList;
    }

    public String[] getTableColumnNamesToEncrypt(String tableName) {
        String[] rv = null;
        for(TableEntry te: this.tables) {
            if (te.getSourceTableName().equals(tableName)) {
                rv = new String[te.getColumnNamesToEncrypt().size()];
                int ix=0;
                for (String s: te.getColumnNamesToEncrypt()) {
                    rv[ix++] = s;
                }
            }
        }
        return rv;
    }

    public String[] getTableColumnNamesToCopyAsis(String tableName) {
        String[] rv = null;
        for (TableEntry te: this.tables) {
            if (te.getSourceTableName().equals(tableName)) {
                rv = new String[te.getColumnNamesToCopyAsis().size()];
                int ix=0;
                for (String s: te.getColumnNamesToCopyAsis()) {
                    rv[ix++] = s;
                }
            }
        }
        return rv;
    }

    public String[] getTableColumnNamesToSkip(String tableName) {
        String[] rv = null;
        for (TableEntry te: this.tables) {
            if (te.sourceTableName.equals(tableName)) {
                rv = new String[te.getColumnNamesToSkip().size()];
                int ix =0;
                for (String s: te.getColumnNamesToSkip()) {
                    rv[ix++] = s;
                }
            }
        }
        return rv;
    }


    public void addTable(
            String sourceTableName,
            String destinationTableName,
            String[] columnNamesToEncrypt,
            String[] columnNamesToCopyAsis,
            String[] columnNamesToSkip
    ) {
        tables.add(
                new TableEntry(
                        sourceTableName,
                        destinationTableName,
                        columnNamesToEncrypt,
                        columnNamesToCopyAsis,
                        columnNamesToSkip
                )
        );
    }

    private class TableEntry {
       private String sourceTableName;
       private String destinationTableName;
       private ArrayList<String> columnNamesToEncrypt;
       private ArrayList<String> columnNamesToCopyAsis;
       private ArrayList<String> columnNamesToSkip;

       private TableEntry() {}

       private TableEntry(String sourceTableName,
                          String destinationTableName,
                          String[] columnNamesToEncrypt,
                          String[] columnNamesToCopyAsis,
                          String[] columnNamesToSkip
       ) {
           this.sourceTableName = sourceTableName;
           this.destinationTableName = destinationTableName;
           this.columnNamesToEncrypt = new ArrayList<>();
           if (columnNamesToEncrypt != null && columnNamesToEncrypt.length > 0) {
               for (String s: columnNamesToEncrypt) {
                   addColumn(s);
               }
           }
       }

       private void addColumn(String s) {
           this.columnNamesToEncrypt.add(s);
       }

        private String getSourceTableName() {
            return sourceTableName;
        }

        public String getDestinationTableName() {
            return destinationTableName;
        }

        public void setSourceTableName(String sourceTableName) {
            this.sourceTableName = sourceTableName;
        }

        public void setDestinationTableName(String destinationTableName) {
            this.destinationTableName = destinationTableName;
        }

        private ArrayList<String> getColumnNamesToEncrypt() {
            return columnNamesToEncrypt;
        }

        public void setColumnNamesToEncrypt(ArrayList<String> columnNamesToEncrypt) {
            this.columnNamesToEncrypt = columnNamesToEncrypt;
        }

        private ArrayList<String> getColumnNamesToCopyAsis() {
            return columnNamesToCopyAsis;
        }

        public void setColumnNamesToCopyAsis(ArrayList<String> columnNamesToCopyAsis) {
            this.columnNamesToCopyAsis = columnNamesToCopyAsis;
        }

        public ArrayList<String> getColumnNamesToSkip() {
            return columnNamesToSkip;
        }

        public void setColumnNamesToSkip(ArrayList<String> columnNamesToSkip) {
            this.columnNamesToSkip = columnNamesToSkip;
        }
    }
}

目前,这个基本应用程序的其余部分都在一个使用两个输入(EditTexts)的单个活动中:-

  • 用于加密的密钥
  • 加密数据库的数据库名称(文件名)。
    • App 中的代码防止使用与基础数据库相同的名称,需要将其复制到 assets 文件夹中。 和一个按钮,用于在输入正确时启动加密(以一种方式,即验证有限)。

这个布局xml activiy_main.xml是:-

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Database EncryptTool" />

    <EditText
        android:id="@+id/secretkey"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="Secret Key to use to Encrypt the Database."
        >
    </EditText>
    <EditText
        android:id="@+id/databasename"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="MyDatabase"
        android:hint="Database Name"
        >
    </EditText>

    <Button
        android:id="@+id/encrypt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ENCRYPT"
        android:visibility="gone"
        >
    </Button>
</LinearLayout>

MainActivity.java 工作完成的地方是:-

public class MainActivity extends AppCompatActivity {

    public static final String ASSETDB_NAME = "basedb.db";
    public static final int ASSETDB_NOT_FOUND = -10;
    public static final int ASSETFILE_OPEN_ERROR = -11;
    public static final int ASSETDB_OPEN_ERROR = -12;
    public static final int ASSETDB_COPY_ERROR = -13;
    public static final int ASSETDB_FLUSH_ERROR = -14;
    public static final int ASSETDB_CLOSE_ERROR = -15;
    public static final int ASSETFILE_CLOSE_ERROR = -16;
    public static final int ASSETDB_CREATED_SUCCESSFULLY = 0;
    public static final int BUFFERSIZE = 1024 * 4;

    EditText mSecretKey, mDBName;
    Button mEncryptButton;
    TableColumnConvertList mTCCL = new TableColumnConvertList();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDBName = this.findViewById(R.id.databasename);
        mSecretKey = this.findViewById(R.id.secretkey);
        mEncryptButton = this.findViewById(R.id.encrypt);

        //<<<<<<<<< set what data to encrypt i.e. table(s) and the column(s) in the table >>>>>>>>>
        mTCCL.addTable(
                "PaidData",
                "FreeData",
                new String[]{"theData"},
                new String[]{},
                new String[]{"id"}
                );

        if (getDBFromAsset() >= 0) {
            mEncryptButton.setVisibility(View.VISIBLE);
            mEncryptButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mDBName.getText().toString().length() < 1) {
                        Toast.makeText(
                                v.getContext(),
                                "The Database Name cannot be blank.",
                                Toast.LENGTH_LONG
                        ).show();
                        mDBName.requestFocus();
                        return;
                    }
                    if (mDBName.getText().toString().equals(ASSETDB_NAME)) {
                        Toast.makeText(
                                v.getContext(),
                                "Database Name cannot be "
                                        + ASSETDB_NAME
                                        + ". Please change the name.",
                                Toast.LENGTH_LONG
                        ).show();
                        mDBName.requestFocus();
                        return;
                    }
                    if (mSecretKey.getText().toString().length() < 1) {
                        Toast.makeText(
                                v.getContext(),
                                "The Secret Key cannot be blank.",
                                Toast.LENGTH_LONG
                        ).show();
                        mSecretKey.requestFocus();
                        return;
                    }
                    if (createEncryptedDatabase(mTCCL,
                            mDBName.getText().toString(),
                            mSecretKey.getText().toString()
                    ) == 0) {
                        Toast.makeText(v.getContext(),"Successfully Encrypted Database " + mDBName + " using Secret Key " + mSecretKey,Toast.LENGTH_LONG).show();
                    }
                }
            });

        }
    }

    private boolean checkIfDataBaseExists(String databaseName) {
        File dbFile = new File(this.getDatabasePath(databaseName).getPath());
        if (dbFile.exists()) {
            return true;
        } else {
            if (!dbFile.getParentFile().exists()) {
                dbFile.getParentFile().mkdirs();
            }
        }
        return false;
    }

    private boolean checkIfAssetDBExists() {
        try {
            InputStream is = this.getAssets().open(ASSETDB_NAME);
            is.close();
            return true;
        } catch (IOException e) {
            return false;
        }
    }

    private int getDBFromAsset() {
        int rv = ASSETDB_NOT_FOUND;
        File dbFile = new File(this.getDatabasePath(ASSETDB_NAME).getPath());
        InputStream is;
        FileOutputStream os;
        int read_length;
        byte[] buffer = new byte[BUFFERSIZE];
        if (!checkIfAssetDBExists()) {
            return ASSETDB_NOT_FOUND;
        }
        if (checkIfDataBaseExists(ASSETDB_NAME)) {
            dbFile.delete();
        }
        try {
            rv = ASSETFILE_OPEN_ERROR;
            is = this.getAssets().open(ASSETDB_NAME);
            rv = ASSETDB_OPEN_ERROR;
            os = new FileOutputStream(dbFile);
            rv = ASSETDB_COPY_ERROR;
            while ((read_length = is.read(buffer)) > 0) {
                os.write(buffer,0,read_length);
            }
            rv = ASSETDB_FLUSH_ERROR;
            os.flush();
            rv = ASSETDB_CLOSE_ERROR;
            os.close();
            rv = ASSETFILE_CLOSE_ERROR;
            is.close();
            rv = ASSETDB_CREATED_SUCCESSFULLY;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return rv;
    }

    private int createEncryptedDatabase(TableColumnConvertList tableColumnConvertList, String databaseName, String key) {
        File copiedAssetDB = new File(this.getDatabasePath(ASSETDB_NAME).getPath());
        File encryptedDB = new File(this.getDatabasePath(databaseName).getPath());
        if (encryptedDB.exists()) {
            encryptedDB.delete();
        }
        try {
            byte[] buffer = new byte[BUFFERSIZE];
            int read_length;
            InputStream is = new FileInputStream(copiedAssetDB);
            OutputStream os = new FileOutputStream(encryptedDB);
            while ((read_length = is.read(buffer)) > 0) {
                os.write(buffer,0,read_length);
            }
            os.flush();
            os.close();
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
            return -1;
        }

        SQLiteDatabase db = SQLiteDatabase.openDatabase(encryptedDB.getPath(),null,SQLiteDatabase.OPEN_READWRITE);
        EncryptDecrypt ed = new EncryptDecrypt(this,key);
        int errorcount = 0;

        db.beginTransaction();
        for (String t: tableColumnConvertList.getTables()) {
            ContentValues cv = new ContentValues();
            String[] columnsToEncrypt = tableColumnConvertList.getTableColumnNamesToEncrypt(t);
            String[] columnOriginalValues = new String[columnsToEncrypt.length];

            Cursor c = db.query(true,t,columnsToEncrypt,null,null,null,null,null, null);
            int totalRows = c.getCount();
            int updatedRows = 0;
            while (c.moveToNext()) {
                cv.clear();
                int ovix=0;
                StringBuilder whereClause = new StringBuilder();
                for (String s: c.getColumnNames()) {
                    for (String ec: columnsToEncrypt ) {
                        if (s.equals(ec)) {
                            cv.put(s,ed.encrypt(c.getString(c.getColumnIndex(s))));
                            columnOriginalValues[ovix++] = c.getString(c.getColumnIndex(s));
                            if (whereClause.length() > 0) {
                                whereClause.append(" AND ");
                            }
                            whereClause.append(s).append("=?");
                        }
                    }
                }
                updatedRows += db.update(t,cv,whereClause.toString(),columnOriginalValues);
            }
            c.close();
            Log.d("ENCRYPTRESULT","Read " + totalRows + " DISTINCT ROWS. Updated " + updatedRows);
            errorcount += totalRows - updatedRows;
        }
        if (errorcount == 0) {
            db.setTransactionSuccessful();
        } else  {
            Toast.makeText(
                    this,
                    "Errors encountered Encrypting Database. Rolled back (not changed)",
                    Toast.LENGTH_LONG
            ).show();
        }
        db.endTransaction();
        return errorcount;
    }
}

这一行/代码很重要:-

TableColumnConvertList mTCCL = new TableColumnConvertList();

..........

    //<<<<<<<<< set what data to encrypt i.e. table(s) and the column(s) in the table >>>>>>>>>
    mTCCL.addTable(
            "PaidData",
            "FreeData",
            new String[]{"theData"},
            new String[]{},
            new String[]{"id"}
            );

这会将一个表添加到要加密的表列表中。它的参数是:-

  • 要包含在加密中的表的名称。
  • 要存储加密数据的表的名称。
    • 请注意,此功能不存在,但可以添加。因此,该值被忽略。
  • 要加密的列的列表。
  • 要按原样复制的列的列表。
    • 此功能不存在,但可以添加。因此,该列表被忽略。
  • 要跳过的列列表(例如 id 列)。
    • 虽然已编码,但功能不存在。因此,该列表被忽略。

应用程序的作用。

最终结果是根据 assets 文件夹中的数据库(名为 basedb.db)的数据库,该数据库已加密 PaidData 表的 theData 列中的数据,但 FreeData 表是不变。然后可以复制该数据库(例如使用设备资源管理器),然后将其作为资产包含在要分发的应用程序中。该应用程序可能包括使用密钥和 EncryptDecrypt 类的解密部分来反转加密。

例如

FreeData 表:-

付费数据表:-

当应用程序启动时,如果从 assets 文件夹中复制数据库(硬编码为 basedb.db),则它存在并使 Encrypt 按钮可见。 p>

  • 如果“加密”按钮不可见,则说明未找到资产文件。所以是时候纠正问题了(提供正确的数据库文件)。
    • 请注意,这只是一个演示,为简洁起见,跳过了许多可以/应该完成或添加的检查/选项。

如果出现“加密”按钮,则只需点击该按钮即可进行加密。

点击按钮后,createEncryptedDatabase 方法被调用。

这将创建一个副本,这将是加密数据库,该副本是从资产文件夹复制的数据库,方法是将文件复制到它的新名称(根据给定的数据库名称,该名称必须与资产的文件名不同)。

使用复制的数据库查询在 mTCCL 中定义的表(TableColumnConvertList 类的一个实例)。

查询将仅提取已指定为要加密的列的数据。查询仅获取不同的行(即,如果存在多行且列中具有相同数据,则仅提取其中一行)。

对于每个提取的行:-

  • 常用的 ContentValues 实例被清除。
  • whereClause StringBuilder 被清除。
  • 检查游标中的每一列以查看它是否是在正在处理的表中定义的列(应该是只提取要加密的列)。
    • 如果不是,则跳过。
  • 原始值保存在字符串数组columnOriginalValues的适当元素中(这将用作UPDATE的WHERE子句的绑定参数)
  • ContentValues 实例的一个元素添加了当前列名和加密数据。
    • 这是按照cv.put(s,ed.encrypt(c.getString(c.getColumnIndex(s)))); 完成的
  • 如果 whereClause 的长度大于 0 则 AND 被添加到 whereClause 中,然后列名以 = 为后缀?被添加到正在构建的 whereClause 中。 在处理完所有列之后,调用 SQLiteDatabase update 方法来更新将值设置为加密值的列,其中所有列都与原始数据匹配。
  • 处理完所有行后,光标关闭并处理下一个表。
  • 如果在处理完所有表后错误计数为 0,则将事务设置为成功,否则显示消息为 Toasted 加密数据库遇到错误。回滚(未更改)
  • 然后事务结束(如果未设置为成功,则数据不会更新而是回滚)。

数据库将位于 data/data/package_name/databases 文件夹中,例如:-

【讨论】:

    【解决方案2】:

    答案 1 的示例解密应用程序

    请注意,数据库(MyDatabase)是从应用程序的data/data/package_name/databases从上一个答案(即加密数据库)复制到此应用程序的资产文件夹中

    以下是一个非常基本的应用程序,最初只有免费数据,但作为一个编辑文本和一个按钮,允许解密和检索付费数据。可用数据(最初是免费数据)列在 ListView 中;解密后,已复制到 FreeData 的付费数据可以使用并列出。

    • 注意事项
      • 数据可以多次解密,每次成功尝试都会添加和显示更多行。

    EncryptDEcrypt.java 与 Encrypt 工具中使用的相同。

    数据库助手是:-

    public class DBHelper extends SQLiteOpenHelper {
    
        public static final String DBNAME = "MyDatabase";
        public static final int DBVERSION = 1;
    
        public static final String TBL_FREEDATA = "FreeData";
        public static final String COL_FREEDATA_ID = "id";
        public static final String COL_THEDATA = "theData";
    
        SQLiteDatabase mDB;
    
        public DBHelper(Context context) {
            super(context, DBNAME, null, DBVERSION);
            loadDBFromAssets(context);
            mDB = this.getWritableDatabase();
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
    
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    
        }
    
        public long insertFreeDataRow(String theData) {
            ContentValues cv = new ContentValues();
            cv.put(COL_THEDATA,theData);
            return mDB.insert(TBL_FREEDATA,null,cv);
        }
    
        public Cursor getAllAvialableData() {
            return mDB.query(TBL_FREEDATA,new
                    String[]{"*",COL_FREEDATA_ID + " AS " + BaseColumns._ID},
                    null,null,null,null,null
            );
        }
    
        public void decryptAndLoadPaidData(Context context, String secretKey) {
            EncryptDecrypt ed = new EncryptDecrypt(context,secretKey);
            mDB.beginTransaction();
            Cursor c = mDB.query("PaidData",null,null,null,null,null,null);
            while (c.moveToNext()) {
                String decrypted_data = ed.decrypt(c.getString(c.getColumnIndex(COL_THEDATA)));
                if (decrypted_data != null) {
                    insertFreeDataRow(decrypted_data);
                } else {
                    Toast.makeText(context,"Naughty, that's not the password.",Toast.LENGTH_LONG).show();
                }
            }
            c.close();
            mDB.setTransactionSuccessful();
            mDB.endTransaction();
        }
    
        private boolean loadDBFromAssets(Context context) {
    
            File dbFile = new File(context.getDatabasePath(DBNAME).getPath());
            byte[] buffer = new byte[1024 * 4];
            int read_length = 0;
            if (dbFile.exists()) return true;
            if (!dbFile.getParentFile().exists()) {
                dbFile.getParentFile().mkdirs();
            }
            try {
                InputStream assetdb = context.getAssets().open(DBNAME);
                OutputStream realdb = new FileOutputStream(dbFile);
                while ((read_length = assetdb.read(buffer)) > 0) {
                    realdb.write(buffer,0,read_length);
                }
                realdb.flush();
                realdb.close();
                assetdb.close();
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }
    }
    

    MainActivity.java 是:-

    public class MainActivity extends AppCompatActivity {
    
        ListView mListView;
        EditText mSecretKeyInput;
        Button mDecrypt;
    
        SimpleCursorAdapter mSCA;
        Cursor mAllTheData;
        DBHelper mDBhlpr;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mListView = this.findViewById(R.id.list);
            mSecretKeyInput = this.findViewById(R.id.secretKey);
            mDecrypt = this.findViewById(R.id.decrypt);
            mDBhlpr = new DBHelper(this);
            manageListView();
            manageDecryptButton();
        }
    
        private void manageListView() {
    
            mAllTheData = mDBhlpr.getAllAvialableData();
            if (mSCA == null) {
                mSCA = new SimpleCursorAdapter(
                        this,android.R.layout.simple_list_item_1,mAllTheData,new String[]{DBHelper.COL_THEDATA},new int[]{android.R.id.text1},0);
                mListView.setAdapter(mSCA);
            } else {
                mSCA.swapCursor(mAllTheData);
            }
        }
    
        private void manageDecryptButton() {
            mDecrypt.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mSecretKeyInput.getText().toString().length() > 0) {
                        mDBhlpr.decryptAndLoadPaidData(v.getContext(),mSecretKeyInput.getText().toString());
                        manageListView();
                    }
                }
            });
        }
    }
    

    结果

    第一次运行时,应用程序仅显示免费数据:-

    如果输入正确的密码/密钥并按下“获取付费数据”按钮,则会添加额外数据:-

    如果提供的密码不正确,则不会加载数据,并会出现提示密码错误的提示。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-10-13
      • 1970-01-01
      • 2015-06-21
      • 1970-01-01
      • 2012-05-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多