Android使用SQLite数据库来存储数据,我需要加密SQLite数据库,如何才能做到这一点?我知道应用数据是私有的。但是,我需要显式加密我的应用正在使用的SQLite数据库。
hiz5n14c1#
SQLCipher是一个SQLite扩展,它提供数据库文件的透明256位AES加密。早期的sqlcipher是SQLite的开源全数据库加密,不适用于Android。但现在它作为Android平台的alpha版本可用。开发者已经更新了标准的Android应用程序'Notepadbot'来使用SQLCipher。所以这绝对是目前最好也是最简单的选择。
sbdsn5lh2#
数据库被加密以防止INDIRECT ATTACKS。此术语和类:* KeyManager.java , Crypto.java * 都是从 * SheranGunasekera * 的书Android Apps Security中提取的。我推荐所有这本书阅读。INDIRECT ATTACKS之所以如此命名,是因为该病毒不会直接攻击您的应用程序。相反,它会攻击Android操作系统。其目的是复制所有SQLite数据库,希望病毒作者能够复制存储在那里的任何敏感信息。然而,如果您添加了另一层保护,那么病毒作者看到的将是乱码数据。让我们构建一个可以在所有应用程序中重用的加密库。让我们从创建一组简短的规范开始:
INDIRECT ATTACKS
让我们从密钥管理模块开始(见清单1)。因为我们计划使用固定密钥,所以不需要像前面的示例那样生成随机密钥。因此,KeyManager 将执行以下任务:1.接受密钥做为参数(setId(byte[] data)方法)1.接受初始化向量作为参数(setIv(byte[] data)方法)1.将密钥存储在内部存储区中的文件内1.从内部存储区的文件中检索密钥(getId(byte[] data)方法)1.从内部存储区的文件中检索IV(getIv(byte[] data)方法)
setId(byte[] data)
setIv(byte[] data)
getId(byte[] data)
getIv(byte[] data)
(清单1.密钥管理器模块 KeyManager.java)
package com.yourapp.android.crypto; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import android.content.Context; import android.util.Log; public class KeyManager { private static final String TAG = "KeyManager"; private static final String file1 = "id_value"; private static final String file2 = "iv_value"; private static Context ctx; public KeyManager(Context cntx) { ctx = cntx; } public void setId(byte[] data){ writer(data, file1); } public void setIv(byte[] data){ writer(data, file2); } public byte[] getId(){ return reader(file1); } public byte[] getIv(){ return reader(file2); } public byte[] reader(String file){ byte[] data = null; try { int bytesRead = 0; FileInputStream fis = ctx.openFileInput(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; while ((bytesRead = fis.read(b)) != -1){ bos.write(b, 0, bytesRead); } data = bos.toByteArray(); } catch (FileNotFoundException e) { Log.e(TAG, "File not found in getId()"); } catch (IOException e) { Log.e(TAG, "IOException in setId(): " + e.getMessage()); } return data; } public void writer(byte[] data, String file) { try { FileOutputStream fos = ctx.openFileOutput(file, Context.MODE_PRIVATE); fos.write(data); fos.flush(); fos.close(); } catch (FileNotFoundException e) { Log.e(TAG, "File not found in setId()"); } catch (IOException e) { Log.e(TAG, "IOException in setId(): " + e.getMessage()); } } }
接下来,我们执行 Crypto 模块(请参见清单2)。该模块负责加密和解密。我们在模块中添加了armorEncrypt()和armorDecrypt()方法,以便更容易地将字节数组数据转换为可打印的Base64数据,反之亦然。我们将使用AES算法和Cipher Block Chaining (CBC) encryption mode和PKCS#5 padding。
armorEncrypt()
armorDecrypt()
*(清单2.加密模块 * Crypto.java )
package com.yourapp.android.crypto; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import android.content.Context; import android.util.Base64; public class Crypto { private static final String engine = "AES"; private static final String crypto = "AES/CBC/PKCS5Padding"; private static Context ctx; public Crypto(Context cntx) { ctx = cntx; } public byte[] cipher(byte[] data, int mode) throws NoSuchAlgorithmException,NoSuchPaddingException,InvalidKeyException,IllegalBlockSizeException,BadPaddingException,InvalidAlgorithmParameterException { KeyManager km = new KeyManager(ctx); SecretKeySpec sks = new SecretKeySpec(km.getId(), engine); IvParameterSpec iv = new IvParameterSpec(km.getIv()); Cipher c = Cipher.getInstance(crypto); c.init(mode, sks, iv); return c.doFinal(data); } public byte[] encrypt(byte[] data) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { return cipher(data, Cipher.ENCRYPT_MODE); } public byte[] decrypt(byte[] data) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { return cipher(data, Cipher.DECRYPT_MODE); } public String armorEncrypt(byte[] data) throws InvalidKeyException,NoSuchAlgorithmException, NoSuchPaddingException,IllegalBlockSizeException, BadPaddingException,InvalidAlgorithmParameterException { return Base64.encodeToString(encrypt(data), Base64.DEFAULT); } public String armorDecrypt(String data) throws InvalidKeyException,NoSuchAlgorithmException, NoSuchPaddingException,IllegalBlockSizeException, BadPaddingException,InvalidAlgorithmParameterException { return new String(decrypt(Base64.decode(data, Base64.DEFAULT))); } }
您可以将这两个文件包含在任何需要加密数据存储的应用程序中。首先,确保您有密钥和初始化向量的值,然后在存储数据之前对数据调用任何一个加密或解密方法。清单3和清单4包含这些类使用的简单应用程序示例。我们创建了一个带有3个按钮Encrypt、解密、删除; 1 EditText用于数据输入; 1个TextView用于数据输出。
(清单3.一个示例。MainActivity.java)
package com.yourapp.android.crypto; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import android.os.Bundle; import android.app.Activity; import android.content.Context; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends Activity { TextView encryptedDataView; EditText editInputData; private Context cntx; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.cntx = getApplicationContext(); Button btnEncrypt = (Button) findViewById(R.id.buttonEncrypt); Button btnDecrypt = (Button) findViewById(R.id.buttonDecrypt); Button btnDelete = (Button) findViewById(R.id.buttonDelete); editInputData = (EditText)findViewById(R.id.editInputData) ; encryptedDataView = (TextView) findViewById(R.id.encryptView); /**********************************************/ /**INITIALIZE KEY AND INITIALIZATION VECTOR**/ String key = "12345678909876543212345678909876"; String iv = "1234567890987654"; KeyManager km = new KeyManager(getApplicationContext()); km.setIv(iv.getBytes()); km.setId(key.getBytes()); /**********************************************/ btnEncrypt.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String Data = editInputData.getText().toString(); String Encrypted_Data = "data"; try { Crypto crypto = new Crypto(cntx); Encrypted_Data = crypto.armorEncrypt(Data.getBytes()); } catch (InvalidKeyException e) { Log.e("SE3", "Exception in StoreData: " + e.getMessage()); } catch (NoSuchAlgorithmException e) { Log.e("SE3", "Exception in StoreData: " + e.getMessage()); } catch (NoSuchPaddingException e) { Log.e("SE3", "Exception in StoreData: " + e.getMessage()); } catch (IllegalBlockSizeException e) { Log.e("SE3", "Exception in StoreData: " + e.getMessage()); } catch (BadPaddingException e) { Log.e("SE3", "Exception in StoreData: " + e.getMessage()); } catch (InvalidAlgorithmParameterException e) { Log.e("SE3", "Exception in StoreData: " + e.getMessage()); } encryptedDataView.setText(Encrypted_Data); } }); btnDecrypt.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String Data = encryptedDataView.getText().toString(); String Decrypted_Data = "data"; try { Crypto crypto = new Crypto(cntx); Decrypted_Data = crypto.armorDecrypt(Data); } catch (InvalidKeyException e) { Log.e("SE3", "Exception in StoreData: " + e.getMessage()); } catch (NoSuchAlgorithmException e) { Log.e("SE3", "Exception in StoreData: " + e.getMessage()); } catch (NoSuchPaddingException e) { Log.e("SE3", "Exception in StoreData: " + e.getMessage()); } catch (IllegalBlockSizeException e) { Log.e("SE3", "Exception in StoreData: " + e.getMessage()); } catch (BadPaddingException e) { Log.e("SE3", "Exception in StoreData: " + e.getMessage()); } catch (InvalidAlgorithmParameterException e) { Log.e("SE3", "Exception in StoreData: " + e.getMessage()); } encryptedDataView.setText(Decrypted_Data); } }); btnDelete.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { encryptedDataView.setText(" Deleted "); } }); } }
(清单4.一个示例. activity_main. xml)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#363636" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <EditText android:id="@+id/editInputData" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:ems="10" android:textColor="#FFFFFF" > <requestFocus /> </EditText> <TextView android:id="@+id/encryptView" android:layout_width="fill_parent" android:layout_height="100dp" android:layout_alignLeft="@+id/editInputData" android:layout_alignRight="@+id/editInputData" android:layout_below="@+id/buttonEncrypt" android:layout_marginTop="26dp" android:background="#000008" android:text="Encrypted/Decrypted Data View" android:textColor="#FFFFFF" android:textColorHint="#FFFFFF" android:textColorLink="#FFFFFF" /> <Button android:id="@+id/buttonEncrypt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/encryptView" android:layout_alignRight="@+id/editInputData" android:layout_below="@+id/editInputData" android:layout_marginTop="26dp" android:text="Encrypt" /> <Button android:id="@+id/buttonDelete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/buttonDecrypt" android:layout_alignRight="@+id/buttonDecrypt" android:layout_below="@+id/buttonDecrypt" android:layout_marginTop="15dp" android:text="Delete" /> <Button android:id="@+id/buttonDecrypt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/encryptView" android:layout_alignRight="@+id/encryptView" android:layout_below="@+id/encryptView" android:layout_marginTop="21dp" android:text="Decrypt" /> </RelativeLayout>
z9ju0rcb3#
如果数据库很小,那么你可以通过将整个文件解密到一个临时位置(而不是SD卡上),然后在关闭它时重新加密来获得少量的安全性。问题:应用程序过早死亡,介质上出现重影。加密数据字段的一个稍微好一点的解决方案。这会导致WHERE和ORDER BY子句出现问题。如果加密字段需要为等价搜索建立索引,则可以存储该字段的加密哈希值并进行搜索。但这对范围搜索或排序没有帮助。如果你想变得更花哨,你可以深入研究Android NDK,并为SQLite的C代码破解一些密码。考虑到所有这些问题和部分解决方案,您确定您真的需要为应用程序使用SQL数据库吗?使用包含加密序列化对象的文件可能会更好。
pgccezyw4#
你当然可以在Android上有一个加密的SQLite数据库,但是你不能用Google提供的开箱即用的类来做。
几个备选方案:
5ktev3wc5#
我已经对不需要索引的数据库字段使用了强RSA加密。搜索从来没有通过电话号码、电子邮件、卡号来完成。这花了一天的时间。RSA使用2048位的密钥。破解这样的代码要困难得多。我已经完成了加密数据库的导出和私钥的混淆。
wko9yo5t6#
在您的应用中使用SQLCipher for Android有两个主要选项:与Room或androidx.sqlite API的其他使用者一起使用使用适用于Android的本机SQLCipher类在这两种情况下,您都需要在net.zetetic:android-database-sqlcipher上添加一个依赖项,例如在您的模块的build.gradle dependencies闭包中添加以下行:
implementation "net.zetetic:android-database-sqlcipher:4.5.2" implementation "androidx.sqlite:sqlite:2.0.1"
https://github.com/sqlcipher/android-database-sqlcipher
6条答案
按热度按时间hiz5n14c1#
SQLCipher是一个SQLite扩展,它提供数据库文件的透明256位AES加密。
早期的sqlcipher是SQLite的开源全数据库加密,不适用于Android。但现在它作为Android平台的alpha版本可用。开发者已经更新了标准的Android应用程序'Notepadbot'来使用SQLCipher。
所以这绝对是目前最好也是最简单的选择。
sbdsn5lh2#
数据库被加密以防止
INDIRECT ATTACKS
。此术语和类:* KeyManager.java , Crypto.java * 都是从 * SheranGunasekera * 的书Android Apps Security中提取的。我推荐所有这本书阅读。INDIRECT ATTACKS
之所以如此命名,是因为该病毒不会直接攻击您的应用程序。相反,它会攻击Android操作系统。其目的是复制所有SQLite数据库,希望病毒作者能够复制存储在那里的任何敏感信息。然而,如果您添加了另一层保护,那么病毒作者看到的将是乱码数据。让我们构建一个可以在所有应用程序中重用的加密库。让我们从创建一组简短的规范开始:让我们从密钥管理模块开始(见清单1)。因为我们计划使用固定密钥,所以不需要像前面的示例那样生成随机密钥。因此,KeyManager 将执行以下任务:
1.接受密钥做为参数(
setId(byte[] data)
方法)1.接受初始化向量作为参数(
setIv(byte[] data)
方法)1.将密钥存储在内部存储区中的文件内
1.从内部存储区的文件中检索密钥(
getId(byte[] data)
方法)1.从内部存储区的文件中检索IV(
getIv(byte[] data)
方法)(清单1.密钥管理器模块 KeyManager.java)
接下来,我们执行 Crypto 模块(请参见清单2)。该模块负责加密和解密。我们在模块中添加了
armorEncrypt()
和armorDecrypt()
方法,以便更容易地将字节数组数据转换为可打印的Base64数据,反之亦然。我们将使用AES算法和Cipher Block Chaining (CBC) encryption mode和PKCS#5 padding。*(清单2.加密模块 * Crypto.java )
您可以将这两个文件包含在任何需要加密数据存储的应用程序中。首先,确保您有密钥和初始化向量的值,然后在存储数据之前对数据调用任何一个加密或解密方法。清单3和清单4包含这些类使用的简单应用程序示例。我们创建了一个带有3个按钮Encrypt、解密、删除; 1 EditText用于数据输入; 1个TextView用于数据输出。
(清单3.一个示例。MainActivity.java)
(清单4.一个示例. activity_main. xml)
z9ju0rcb3#
如果数据库很小,那么你可以通过将整个文件解密到一个临时位置(而不是SD卡上),然后在关闭它时重新加密来获得少量的安全性。问题:应用程序过早死亡,介质上出现重影。
加密数据字段的一个稍微好一点的解决方案。这会导致WHERE和ORDER BY子句出现问题。如果加密字段需要为等价搜索建立索引,则可以存储该字段的加密哈希值并进行搜索。但这对范围搜索或排序没有帮助。
如果你想变得更花哨,你可以深入研究Android NDK,并为SQLite的C代码破解一些密码。
考虑到所有这些问题和部分解决方案,您确定您真的需要为应用程序使用SQL数据库吗?使用包含加密序列化对象的文件可能会更好。
pgccezyw4#
你当然可以在Android上有一个加密的SQLite数据库,但是你不能用Google提供的开箱即用的类来做。
几个备选方案:
5ktev3wc5#
我已经对不需要索引的数据库字段使用了强RSA加密。搜索从来没有通过电话号码、电子邮件、卡号来完成。这花了一天的时间。RSA使用2048位的密钥。破解这样的代码要困难得多。我已经完成了加密数据库的导出和私钥的混淆。
wko9yo5t6#
使用SQL密码
在您的应用中使用SQLCipher for Android有两个主要选项:
与Room或androidx.sqlite API的其他使用者一起使用
使用适用于Android的本机SQLCipher类
在这两种情况下,您都需要在net.zetetic:android-database-sqlcipher上添加一个依赖项,例如在您的模块的build.gradle dependencies闭包中添加以下行:
https://github.com/sqlcipher/android-database-sqlcipher