Android数据库encryption

Android使用SQLite数据库来存储数据,我需要encryptionSQLite数据库,这怎么办呢? 我了解,应用程序数据是私人的。 不过,我需要明确地encryption我的应用程序正在使用的SQLite数据库。

SQLCipher是一种SQLite扩展,提供对数据库文件的透明256位AESencryption。

较早的sqlcipher是SQLite的开源完整数据库encryption不适用于Android。 但现在它可以作为Android平台的alpha版本。 开发人员已经更新了标准android应用程序'Notepadbot'来使用SQLCipher。

所以这绝对是目前最好也是最简单的select。

数据库被encryption以防止INDIRECT ATTACKS 。 这个术语和类: KeyManager.javaCrypto.java取自Sheran Gunasekera的书Android应用安全 。 我推荐所有这本书阅读。

INDIRECT ATTACKS是如此命名,因为病毒不会直接在您的应用程序之后。 相反,它遵循Android操作系统。 其目的是复制所有的SQLite数据库,希望病毒作者可以复制存储在那里的敏感信息。 但是,如果您添加了另一层保护,则所有病毒作者都会看到乱码数据。 让我们build立一个encryption库,我们可以在所有的应用程序中重用。 我们首先创build一个简短的规格集:

  • 使用对称algorithm:我们的库将使用对称algorithm或分组密码来encryption和解密我们的数据。 尽pipe我们可以在稍后修改,但我们将在AES上解决。

  • 使用一个固定的密钥:我们需要能够包含一个密钥,我们可以存储在设备上,将用于encryption和解密数据。

  • 存储在设备上的密钥:密钥将驻留在设备上。 虽然从直接攻击的angular度来看,这对我们的应用是一个风险,但它应该足以保护我们免受间接攻击。

我们从密钥pipe理模块开始(参见清单1 )。 因为我们打算使用一个固定的密钥,我们不需要像过去的例子那样产生一个随机的密钥。 KeyManager将因此执行以下任务:

  1. 接受一个键作为参数( setId(byte[] data)方法)
  2. 接受一个初始化向量作为参数( setIv(byte[] data)方法)
  3. 将密钥存储在内部存储的文件中
  4. 从内部存储中的文件( getId(byte[] data)方法)中检索密钥
  5. 从内部存储中的文件中检索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 )。 该模块负责encryption和解密。 我们在模块中添加了armorEncrypt()armorDecrypt()方法,以便将字节数组数据转换为可打印的Base64数据,反之亦然。 我们将使用密码分组链接(CBC)encryption模式和PKCS#5填充的AESalgorithm。

(清单2.encryption模块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))); } } 

您可以将这两个文件包含在任何需要encryption数据存储的应用程序中。 首先,确保您的密钥和初始化向量具有值,然后在存储数据之前调用数据的任何一种encryption或解密方法。 清单3清单4包含这些类使用的简单应用程序示例。 我们用3个button创build一个活动encryption,解密,删除; 1用于数据input的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> 

如果数据库很小,那么可以通过将整个文件解密到一个临时位置(不在SD卡上),然后在closures时重新进行encryption,从而获得一小部分安全性。 问题:应用程序过早死亡,媒体上的鬼影。

稍微好一点的解决scheme来encryption数据字段。 这会导致WHERE和ORDER BY子句出现问题。 如果encryption字段需要为等价searchbuild立索引,则可以存储该字段的encryption散列并search该字段。 但是这对范围search或sorting没有帮助。

如果你想更有趣,你可以深入研究Android NDK,并将一些encryption技术转换为SQLite的C代码。

考虑到所有这些问题和部分解决scheme,你确定你真的需要一个SQL数据库的应用程序? 你可能会更好,像一个包含一个encryption序列化对象的文件。

你当然可以在Android上有一个encryption的SQLite数据库。 但是,Google提供的课程不能使用Google提供的课程。

几个select:

  • 通过NDK编译你自己的SQLite,并包含例如wxSQLite3的encryption编解码器(一个很好的免费编解码器包含在包中)
  • SQLCipher现在包含对Android的支持

litereplica支持使用ChaCha密码进行encryption。

Chacha在基于ARMv7的便携式设备上比AES 快几乎3倍 。

有Android的绑定 。

要创build并打开encryption的数据库,我们使用这样的URI:

 "file:/path/to/file.db?cipher=...&key=..."