【问题标题】:Android database encryptionAndroid数据库加密
【发布时间】:2011-01-13 07:45:42
【问题描述】:

Android使用SQLite数据库存储数据,我需要对SQLite数据库进行加密,怎么做?我了解应用程序数据是私有的。但是我需要明确加密我的应用正在使用的 SQLite 数据库。

【问题讨论】:

    标签: android database sqlite encryption


    【解决方案1】:

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

    早期的 sqlcipher 是用于 SQLite 的开源完整数据库加密,不适用于 android。但现在它可以作为 android 平台的 alpha 版本使用。 开发人员已更新标准 android 应用程序“Notepadbot”以使用 SQLCipher。

    所以这绝对是目前最好和最简单的选择。

    【讨论】:

    【解决方案2】:

    数据库被加密以防止INDIRECT ATTACKS。 这个术语和类:KeyManager.javaCrypto.java 取自 Sheran GunasekeraAndroid Apps Security。我推荐所有这本书阅读。

    INDIRECT ATTACKS 之所以如此命名,是因为病毒不会直接攻击您的应用程序。相反,它追随 Android 操作系统。目的是复制所有 SQLite 数据库,希望病毒作者可以复制存储在那里的任何敏感信息。但是,如果您添加了另一层保护,那么病毒作者将看到的只是乱码数据。 让我们构建一个可以在所有应用程序中重用的密码库。让我们从创建一组简短的规范开始:

    • 使用对称算法:我们的库将使用对称算法, 或分组密码,用于加密和解密我们的数据。我们将选择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

    (清单 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、Decrypt、Delete 的 Activity; 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>
    

    【讨论】:

    • 如果密钥存储在设备中,使用该密钥加密、加密数据有什么好处?
    • 如何从另一个文件中设置和获取密钥..你能举个例子吗?在读取(文件)时获取 NPE
    【解决方案3】:

    如果数据库很小,那么您可以通过将整个文件解密到临时位置(不在 sd 卡上)来获得少量安全性,然后在关闭它时重新加密。问题:应用程序过早死亡、媒体上出现鬼影。

    加密数据字段的稍微好一点的解决方案。这会导致 WHERE 和 ORDER BY 子句出现问题。如果需要对加密字段进行索引以进行等价搜索,则可以存储该字段的加密哈希并进行搜索。但这对范围搜索或排序没有帮助。

    如果您想变得更高级,可以深入研究 Android NDK 并将一些加密技术破解到 SQLite 的 C 代码中。

    考虑到所有这些问题和部分解决方案,您确定您真的需要一个用于应用程序的 SQL 数据库吗?使用包含加密序列化对象的文件可能会更好。

    【讨论】:

      【解决方案4】:

      您当然可以在 Android 上拥有一个加密的 SQLite 数据库。但是,您无法使用 Google 提供的开箱即用的课程来做到这一点。

      几个备选方案:

      • 通过 NDK 编译您自己的 SQLite,并包含来自例如 wxSQLite3 的加密编解码器(包中包含一个不错的免费编解码器)
      • SQLCipher 现在包括对 Android 的支持

      【讨论】:

        【解决方案5】:

        http://sqlite-crypt.com/ 可以帮助你创建一个加密数据库,虽然我从来没有在android上使用过它似乎可以使用源代码。

        【讨论】:

          猜你喜欢
          • 2014-08-25
          • 1970-01-01
          • 1970-01-01
          • 2015-06-27
          • 1970-01-01
          • 2012-03-19
          • 1970-01-01
          • 2017-06-27
          • 1970-01-01
          相关资源
          最近更新 更多