Encriptación de base de datos Android

Android usa la base de datos SQLite para almacenar datos, necesito encriptar la base de datos SQLite, ¿cómo se puede hacer esto? Entiendo que los datos de la aplicación son privados. Sin embargo, necesito encriptar explícitamente la base de datos SQLite que está usando mi aplicación.

SQLCipher es una extensión de SQLite que proporciona cifrado AES transparente de 256 bits de archivos de base de datos.

Anterior sqlcipher que es Open Source Full Database Encryption para SQLite no estaba disponible para android. Pero ahora está disponible como versión alfa para la plataforma Android. Los desarrolladores han actualizado la aplicación estándar de Android ‘Notepadbot’ para usar SQLCipher.

Así que esta es definitivamente la mejor y más simple opción a partir de ahora.

Las bases de datos están encriptadas para prevenir INDIRECT ATTACKS . Este término y clases: KeyManager.java , Crypto.java están tomados de Sheran Gunasekera book Android Apps Security . Recomiendo todo este libro para leer.

INDIRECT ATTACKS reciben ese nombre porque el virus no persigue su aplicación directamente. En cambio, va después del sistema operativo Android. El objective es copiar todas las bases de datos SQLite con la esperanza de que el autor del virus pueda copiar cualquier información sensible almacenada allí. Sin embargo, si hubiera agregado otra capa de protección, todo lo que el autor del virus vería son datos confusos. Construyamos una biblioteca criptográfica que podamos reutilizar en todas nuestras aplicaciones. Comencemos por crear un breve conjunto de especificaciones:

  • Utiliza algoritmos simétricos: nuestra biblioteca utilizará un algoritmo simétrico o cifrado de bloque para cifrar y descifrar nuestros datos. Nos instalaremos en AES, aunque deberíamos poder modificar esto en una fecha posterior.

  • Utiliza una clave fija: debemos poder incluir una clave que podamos almacenar en el dispositivo que se utilizará para cifrar y descifrar datos.

  • Clave almacenada en el dispositivo: la clave residirá en el dispositivo. Si bien este es un riesgo para nuestra aplicación desde la perspectiva de ataques directos, debería ser suficiente para protegernos contra ataques indirectos.

Comencemos con nuestro módulo de gestión de claves (consulte el Listado 1 ). Debido a que planeamos usar una clave fija, no necesitaremos generar una aleatoria como hicimos en los ejemplos anteriores. Por lo tanto, KeyManager realizará las siguientes tareas:

  1. Aceptar una clave como parámetro (el setId(byte[] data) )
  2. Aceptar un vector de inicialización como parámetro (el setIv(byte[] data) )
  3. Almacene la clave dentro de un archivo en la tienda interna
  4. Recupere la clave de un archivo en el almacén interno (el getId(byte[] data) )
  5. Recupere el IV de un archivo en el almacén interno (el getIv(byte[] data) )

(Listado 1. El KeyManager Module 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()); } } } 

A continuación, hacemos el módulo Crypto (ver el Listado 2 ). Este módulo se ocupa del cifrado y descifrado. Hemos agregado un armorEncrypt() y armorDecrypt() al módulo para facilitar la conversión de los datos de la matriz de bytes en datos Base64 imprimibles y viceversa. Utilizaremos el algoritmo AES con el modo de encriptación Cipher Block Chaining (CBC) y el relleno PKCS # 5 .

(Listado 2. El Cryptographic Module 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))); } } 

Puede incluir estos dos archivos en cualquiera de sus aplicaciones que requieren el almacenamiento de datos para ser encriptados. Primero, asegúrese de tener un valor para la clave y el vector de inicialización, luego llame a cualquiera de los métodos de cifrado o descifrado en sus datos antes de almacenarlos. El Listado 3 y el Listado 4 contienen simplemente un ejemplo de aplicación de estas clases. Creamos una actividad con 3 botones Cifrar, Descifrar, Eliminar; 1 EditText para la entrada de datos; 1 TextView para salida de datos.

(Listado 3. Un ejemplo. 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 "); } }); } } 

(Listado 4. Un ejemplo. Activity_main.xml)

          

Si la base de datos será pequeña, entonces podría obtener una pequeña cantidad de seguridad descifrando el archivo completo en una ubicación temporal (no en la tarjeta SD), luego vuelva a cifrarlo cuando lo haya cerrado. Problemas: muerte prematura de la aplicación, imagen fantasma en los medios.

Una solución ligeramente mejor para encriptar los campos de datos. Esto causa un problema para las cláusulas WHERE y ORDER BY. Si los campos encriptados necesitan ser indexados para la búsqueda de equivalencia, entonces puede almacenar un hash criptográfico del campo y buscar eso. Pero eso no ayuda con las búsquedas de rango u ordenamiento.

Si quieres ser más elegante, puedes profundizar en el NDK de Android y hackear algunas criptografías en código C para SQLite.

Considerando todos estos problemas y soluciones parciales, ¿está seguro de que realmente necesita una base de datos SQL para la aplicación? Es posible que esté mejor con algo así como un archivo que contiene un objeto serializado encriptado.

Sin duda puede tener una base de datos SQLite encriptada en Android. Sin embargo, no puedes hacerlo con las clases provistas por Google.

Un par de alternativas:

  • Comstack tu propio SQLite a través del NDK e incluye el códec de cifrado de, por ejemplo, wxSQLite3 (un buen códec gratuito está incluido en el paquete)
  • SQLCipher ahora incluye soporte para Android

http://sqlite-crypt.com/ puede ayudarte a crear una base de datos encriptada, aunque nunca la he usado en Android parece posible con el código fuente.