我在我的 android 应用程序中使用了一个外部数据库,它在编译后直接嵌入到 apk 包中。因为我想在应用程序购买中实现以访问它的一些数据,所以我不想在没有任何加密的情况下离开它。我使用了 Sqlcipher 库,但它使应用程序太大且速度太慢。没有其他方法可以做到这一点吗?例如,一种加密字符串的算法,因此我将加密文本放入数据库并在应用程序代码中解密?
以下是加密部分数据的示例应用程序,然后可以打开。它基于我评论中的代码。
首先,您需要作为加密数据库基础的数据库(即不包含在应用程序中的MASTER数据库,它的用途是创建加密数据库(或数据库,可能是一个库,每个数据库都有一个如果您想要更高的安全性,则唯一的密码/密钥))部分考虑这一点(在整个示例中使用):-
正如您所看到的,这将通过一个名为 FreeData 的表和另一个名为 PaidData 的表来工作。表定义是相同的,除了对于 PaidData 没有 ID 列(此方法的目的是在/如果请求的和 SecretKey(密码)有效时将 PaidData 中的行解密为 FreeData。)。
所以FreeData表看起来像:-
该PaidData表如下所示: -
第 2 阶段 - 生成加密数据库以与应用程序一起分发
这是由一个应用程序为此目的使用 EncryptDecrypt 类完成的,该类与SQLite中的Encrypt data 中的类非常相似
根据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)的活动中:-
这个布局 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"}
);
这会将一个表添加到要加密的表列表中。它的参数是:-
最终结果是资产文件夹中的数据库(名为basedb.db)中的数据库,该数据库对PaidData表的theData列中的数据进行了加密,但FreeData表没有变化。然后可以复制该数据库(例如使用设备浏览器),然后将其作为资产包含在要分发的应用程序中。该应用程序可以包括使用密钥和 EncryptDecrypt 类的解密部分的加密反转。
例如
FreeData 表:-
付费数据表:-
当应用程序启动时,如果从它存在的资产文件夹中复制数据库(硬编码为 basedb.db)并使加密按钮可见。
如果出现加密按钮,那么加密只是点击按钮的问题。
点击按钮后调用createEncryptedDatabase方法。
这将创建一个副本,这将是加密数据库,通过将文件复制到它的新名称(根据给定的数据库名称,该名称必须与资产的文件名不同)从资产文件夹复制的数据库。
它使用复制的数据库查询在mTCCL(TableColumnConvertList类的实例)中定义的表。
该查询将仅提取已指定为要加密的列的数据。查询仅获取不同的行(即,如果存在多行且列中具有相同数据,则仅提取行之一)。
对于每个提取的行:-
cv.put(s,ed.encrypt(c.getString(c.getColumnIndex(s))));
数据库将在 data/data/package_name/databases 文件夹中,例如:-
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句