Android使用SQLite数据库来存储数据,我需要加密SQLite数据库,怎么做呢?我知道应用程序数据是私有的.但是,我需要明确加密我的应用程序正在使用的SQLite数据库.
SQLCipher 是一个SQLite扩展,为数据库文件提供透明的256位AES加密.
早期的sqlcipher是SQLite的开源全数据库加密,不适用于Android.但现在它可用作Android平台的alpha版本.开发人员更新了标准的Android应用程序'Notepadbot'以使用SQLCipher.
所以这绝对是目前最好,最简单的选择.
数据库经过加密以防止INDIRECT ATTACKS
.这个术语和类:KeyManager.java,Crypto.java取自Sheran Gunasekera的书籍Android Apps Security.我推荐阅读这本书.
INDIRECT ATTACKS
如此命名,因为病毒不会直接在您的应用程序之后.相反,它追随Android操作系统.目的是复制所有SQLite数据库,希望病毒作者可以复制存储在那里的任何敏感信息.但是,如果您添加了另一层保护,那么所有病毒作者都会看到乱码数据.让我们构建一个加密库,我们可以在所有应用程序中重用它.让我们首先创建一组简短的规范:
使用对称算法:我们的库将使用对称算法或分组密码来加密和解密我们的数据.我们将以AES为基础,尽管我们应该能够在以后修改它.
使用固定密钥:我们需要能够包含一个密钥,我们可以将其存储在将用于加密和解密数据的设备上.
存储在设备上的密钥:密钥将驻留在设备上.虽然从直接攻击的角度来看这对我们的应用程序是一种风险,但它应该足以保护我们免受间接攻击.
让我们从我们的密钥管理模块开始(参见清单1).因为我们计划使用固定密钥,所以我们不需要像过去的示例那样生成随机密钥.因此,KeyManager将执行以下任务:
接受一个键作为参数(setId(byte[] data)
方法)
接受初始化向量作为参数(setIv(byte[] data)
方法)
将密钥存储在内部存储中的文件中
从内部存储中的文件中检索密钥(getId(byte[] data)
方法)
从内部存储中的文件中检索IV(getIv(byte[] data)
方法)
(清单1. KeyManager模块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算法与密码块链接(CBC)加密模式和PKCS#5填充.
(清单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个按钮加密,解密,删除创建一个活动; 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.示例.activation_main.xml)
如果数据库很小,那么您可以通过将整个文件解密到临时位置(而不是SD卡)来获得少量安全性,然后在关闭它时重新加密.问题:应用程序过早死亡,媒体上出现鬼影.
加密数据字段的稍好的解决方案.这会导致WHERE和ORDER BY子句出现问题.如果需要将加密字段编入索引以进行等效搜索,则可以存储该字段的加密哈希并搜索该字段.但这对范围搜索或订购没有帮助.
如果你想获得更好的体验,你可以深入研究Android NDK并将一些加密内容加入到SQLite的C代码中.
考虑到所有这些问题和部分解决方案,您确定您确实需要一个SQL数据库用于该应用程序吗?使用包含加密序列化对象的文件之类的东西可能会更好.