安卓数据库加密

82

Android使用SQLite数据库存储数据,我需要对SQLite数据库进行加密,请问如何操作?我知道应用程序数据是私有的,但我需要显式地对我的应用程序正在使用的SQLite数据库进行加密。

6个回答

72

请查看SQLCipher

SQLCipher是SQLite的扩展,提供数据库文件的透明256位AES加密

早期的Open Source Full Database Encryption for SQLite(全面的SQLite数据库加密)并不适用于Android。但现在它已经作为Android平台的alpha版本发布了。 开发人员已经更新了标准的Android应用程序“Notepadbot”以使用SQLCipher。

因此,这绝对是目前最好和最简单的选择。


2
SQLCipher for Android现在已成为官方SQLCipher项目的一部分:http://sqlcipher.net/sqlcipher-for-android/ - Display name
1
许可证信息可在 GitHub 页面 https://github.com/sqlcipher/android-database-sqlcipher/blob/master/LICENSE 上找到。 - vaichidrewar
2
@vaichidrewar 你会发现那个特定的许可证文件仅适用于Android支持部分,SQLCIPHER部分还有其他许可证文件(https://github.com/sqlcipher/android-database-sqlcipher/blob/master/SQLCIPHER_LICENSE),以及IBM部分(https://github.com/sqlcipher/android-database-sqlcipher/blob/master/ICU_LICENSE)。 - Hamid
1
关于在Android中使用SQLCipher的简单示例,请参考以下链接:http://myownandroid.blogspot.in/2013/09/sqlcipher-in-android.html - jrh

30

为了防止间接攻击,数据库会进行加密。

间接攻击之所以被这样命名,是因为病毒并不直接攻击您的应用程序。相反,它会攻击Android操作系统。其目的是复制所有SQLite数据库,希望病毒作者可以复制其中存储的任何敏感信息。但如果您添加了另一层保护,那么病毒作者将只能看到乱码数据。

让我们构建一个加密库,以便在所有应用程序中重复使用。让我们从创建一组简要的规范开始:

这个术语和类:KeyManager.javaCrypto.java来自于Sheran Gunasekera的书《Android Apps Security》,我推荐大家阅读这本书。

  • 使用对称算法:我们的库将使用一个对称算法或分组密码来加密和解密数据。我们将采用AES,不过在以后应该能够进行修改。

  • 使用固定密钥:我们需要包含一把密钥,可以存储在设备上并用于加密和解密数据。

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

让我们从密钥管理模块开始(见清单1)。因为我们计划使用固定密钥,所以无需像之前的示例中那样生成随机密钥。因此,KeyManager将执行以下任务:

  1. 接受一个参数作为密钥(setId(byte[] data) 方法)
  2. 接受一个参数作为初始化向量(setIv(byte[] data) 方法)
  3. 将密钥存储在内部存储器中的文件中
  4. 从内部存储器中的文件中检索密钥(getId(byte[] data) 方法)
  5. 从内部存储器中的文件中检索初始化向量(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 算法,采用 Cipher Block Chaining (CBC) encryption modePKCS#5 padding
        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的Activity。

(清单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>

9
如果密钥存储在设备中,使用该密钥加密数据有什么好处? - minhaz

14
如果数据库很小,您可以将整个文件解密到临时位置(不在SD卡上),然后在关闭它时重新加密以获得一定的安全性。 问题:应用程序意外终止,媒体上会留下幽灵镜像。
更好的解决方案是对数据字段进行加密。这会导致WHERE和ORDER BY子句的问题。如果需要对加密字段进行等值搜索,那么可以存储该字段的加密哈希并搜索它。但这不能解决范围搜索或排序的问题。
如果想要更高级的解决方案,可以深入了解Android NDK并将一些密码技术嵌入SQLite的C代码中。
考虑到所有这些问题和部分解决方案,您确定是否真的需要应用程序的SQL数据库?您可能最好使用包含加密序列化对象的文件。

4
你可以在安卓上使用加密的SQLite数据库。然而,你不能使用谷歌提供的开箱即用类来完成这个任务。
以下是几种替代方法:
- 通过NDK编译自己的SQLite并包含例如wxSQLite3中的加密编解码器(该软件包中包含一个不错的免费编解码器)。 - SQLCipher现在已经支持安卓。

1

使用 SQLCipher

在您的应用程序中,使用 SQLCipher for Android 有两个主要选项:

与 Room 或其他 androidx.sqlite API 的消费者一起使用它

使用本机的 SQLCipher for Android 类

在这两种情况下,您都需要添加对 net.zetetic:android-database-sqlcipher 的依赖,比如在模块的 build.gradle 依赖项中添加以下行:

implementation "net.zetetic:android-database-sqlcipher:4.5.2"
implementation "androidx.sqlite:sqlite:2.0.1"

https://github.com/sqlcipher/android-database-sqlcipher


0

我已经为不需要索引的个别数据库字段使用了强大的RSA加密。搜索从未通过电话号码、电子邮件或卡号进行。这花费了一天时间。RSA使用2048位密钥,破解此类代码要困难得多。我已经导出了加密数据库并对私钥进行了混淆。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接