(4.2.35)数据加密:SQLCipher和Conceal

Android中,通常使用SQLite来管理本地数据。但是如果手机被ROOT了,用户能够轻易查看到SQLite数据库中的信息。如果数据库中包含用户私密信息或者APP的关键信息,那么也就能够轻易被其他人访问。现在这是所有开发不希望看见的。

       这里讲两种数据加密方法。分别采用SQLCipher和ConCeal。并分别讲述这两种方法的优缺点。


       一. SQLCipher

       SQLCipher是在SQLite基础上进行扩展的开源数据库,它增加了数据加密功能,并且支持多种不同的平台。

首先需要下载Android项目所依赖的SQLCipher包,下载地址是

      https://s3.amazonaws.com/sqlcipher/SQLCipher+for+Android+v2.2.2.zip

 解压压缩包,有assets和libs这两个目录,assets文件夹中有icudt461.zip,libs文件夹中有armeabi文件夹,下面有三个.so的文件。需要将这两个目录中的内容复制添加到需要加密数据库的Android项目的assets和libs文件夹中。

         到此准备工作完毕,,我们需要建立一个SQLCipherDatabaseHelper继承自SQLiteOpenHelper,这里使用的不是Android API中的SQLiteOpenHelper,而是net.sqlcipher.database包下的SQLiteOpenHelper。代码如下:

[java]  view plain  copy
  1. import android.content.Context;  
  2. import net.sqlcipher.database.SQLiteDatabase;  
  3. import net.sqlcipher.database.SQLiteDatabase.CursorFactory;  
  4. import net.sqlcipher.database.SQLiteOpenHelper;  
  5.   
  6. public class MyDatabaseHelper extends SQLiteOpenHelper{  
  7.   
  8.      public static final String CREATE_TABLE = "create table Book(name text, pages integer)";    
  9.         
  10.         public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version) {    
  11.             super(context, name, factory, version);    
  12.         }    
  13.         
  14.         @Override    
  15.         public void onCreate(SQLiteDatabase db) {    
  16.             db.execSQL(CREATE_TABLE);    
  17.         }    
  18.         
  19.         @Override    
  20.         public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) {    
  21.         
  22.         }  
  23.   
  24. }  
      我们引入的是net.sqlcipher.database.SQLiteOpenHelper在这段代码中,建立了一个书籍的表,分别记录书名name和页数pages。

     除了引入的包不一样,其他的用法和SQLite都是完全相同。 添加和查询数据的代码和SQLite是相同的。

[java]  view plain  copy
  1. public class MainActivity extends Activity {    
  2.   
  3.         
  4.   
  5.     private SQLiteDatabase db;    
  6.   
  7.     
  8.   
  9.     @Override    
  10.   
  11.     protected void onCreate(Bundle savedInstanceState) {    
  12.   
  13.         super.onCreate(savedInstanceState);    
  14.   
  15.         setContentView(R.layout.activity_main);    
  16.   
  17.         SQLiteDatabase.loadLibs(this);    
  18.   
  19.         MyDatabaseHelper dbHelper = new MyDatabaseHelper(this"demo.db"null1);    
  20.   
  21.         db = dbHelper.getWritableDatabase("secret_key");    
  22.   
  23.         Button addData = (Button) findViewById(R.id.add_data);    
  24.   
  25.         Button queryData = (Button) findViewById(R.id.query_data);    
  26.   
  27.         addData.setOnClickListener(new OnClickListener() {    
  28.   
  29.             @Override    
  30.   
  31.             public void onClick(View v) {    
  32.   
  33.                 ContentValues values = new ContentValues();    
  34.   
  35.                 values.put("name""达芬奇密码");    
  36.   
  37.                 values.put("pages"566);    
  38.   
  39.                 db.insert("Book"null, values);    
  40.   
  41.             }    
  42.   
  43.         });    
  44.   
  45.         queryData.setOnClickListener(new OnClickListener() {    
  46.   
  47.             @Override    
  48.   
  49.             public void onClick(View v) {    
  50.   
  51.                 Cursor cursor = db.query("Book"nullnullnullnullnullnull);    
  52.   
  53.                 if (cursor != null) {    
  54.   
  55.                     while (cursor.moveToNext()) {    
  56.   
  57.                         String name = cursor.getString(cursor.getColumnIndex("name"));    
  58.   
  59.                         int pages = cursor.getInt(cursor.getColumnIndex("pages"));    
  60.   
  61.                         Log.d("TAG""book name is " + name);    
  62.   
  63.                         Log.d("TAG""book pages is " + pages);    
  64.   
  65.                     }    
  66.   
  67.                 }    
  68.   
  69.                 cursor.close();    
  70.   
  71.             }    
  72.   
  73.         });    
  74.   
  75.     }    
  76.   
  77. }    

     在OnCreate()中,调用了SQLiteDatabase的loadLibs()静态方法将SQLCipher所以来的.so加载进来,注意引入的是net.sqlcipher.database下的SQLiteDatabase。然后我们创建了MyDatabaseHelper的实例,并调用getWritableDatabase()方法去获取SQLiteDatabase对象。这里在调用getWritableDatabase()方法的时候传入了一个字符串参数,它就是SQLCipher所依赖的key,在对数据库进行加解密的时候SQLCipher都将使用这里指定的key。

     在添加数据按钮的点击事件里面,我们通过ContentValues构建了一条数据,然后调用SQLiteDatabase的insert()方法将这条数据插入到Book表中。

     在查询数据按钮的点击事件里面,我们调用SQLiteDatabase的query()方法来查询Book表中的数据,查询到的结果会存放在Cursor对象中,注意这里使用的是net.sqlcipher包下的Cursor。然后对Cursor对象进行遍历,并将查询到的结果打印出来。

     现在运行一下程序,先点击添加数据按钮,再点击查询数据按钮,刚刚添加的那条数据就应该在控制台里打印出来了。

     SQLCipher提供的API与Android原生的API操作起来是几乎一模一样的,因为SQLCipher对Android SDK中所有与数据库相关的API做了一份镜像。这样开发者可以向操作普通数据库文件一样来操作SQLCipher,而所有的加密解密操作,对开发人员而言都是黑盒的,SQLCipher在背后帮我们做好了。

      二。ConCeal

     ConCeal是Facebook推出的用于对数据进行快速加密和认证的开发包,Facebook用它来加密手机、平板电脑SD卡中的数据和图片。能够高效快速地在磁盘上加密大文件,对于低版本的android系统、低内存、较慢的处理器,都有很好的效率。ConCeal的加密算法基于OpenSSL,经过编译之后,apk包大小只增加0.1MB左右,能够兼容众多Android版本,减少后期维护。

      项目主页是:http://www.open-open.com/lib/view/home/1394779937806

     Android的jar包下载地址:http://facebook.github.io/conceal/documentation/

     ConCeal的加密代码判断是:

[java]  view plain  copy
  1. //使用秘钥链和原生库的默认实现,来创建一个新的加密对象  
  2. Crypto crypto = new Crypto(  
  3.   new SharedPrefsBackedKeyChain(context),  
  4.   new SystemNativeCryptoLibrary());  
  5.    
  6. //检查加密功能是否可用  
  7. //如果Android没有正确载入库,则此步骤可能失败  
  8. if (!crypto.isAvailable()) {  
  9.   return;  
  10. }  
  11.    
  12. OutputStream fileStream = new BufferedOutputStream(  
  13.   new FileOutputStream(file));  
  14.    
  15. //创建输出流,当数据写入流的时候进行加密,并将加密后的数据输出到文件  
  16. OutputStream outputStream = crypto.getCipherOutputStream(  
  17.   fileStream,  
  18.   entity);  
  19.    
  20. //将纯文本写入其中  
  21. outputStream.write(plainText);  
  22. outputStream.close();  
      ConCeal的解密代码段是:

[java]  view plain  copy
  1. // 打开用ConCeal加密的文件  
  2. FileInputStream fileStream = new FileInputStream(file);  
  3.   
  4. // 创建一个输入流,当数据读入的时候用ConCeal进行解密  
  5. InputStream inputStream = crypto.getCipherInputStream(  
  6.   fileStream,entity);  
  7.   
  8. // 变为字节  
  9. int read;  
  10. byte[] buffer = new byte[1024];  
[java]  view plain  copy
  1. // 读入整个输入流,在输入流结束时进行验证,如果不一直验证到流结束,将有安全漏洞  
  2. while ((read = inputStream.read(buffer)) != -1) {  
  3.   out.write(buffer, 0, read);  
  4. }  
  5.   
  6. inputStream.close();  
      创建新的KeyChain。程序中有默认的KeyChain,但是也可以继承KeyChain以实现一个自己的KeyChain。

[java]  view plain  copy
  1. public class CustomKeyChain implements KeyChain {  
  2.   ...  
  3. }  
      下面讲述ConCeal的具体实现

      1. 从http://facebook.github.io/conceal/documentation/中下载工程所需要的两个Jar包和so文件,分别点击ConCeal JarAndroid JarNative Binaries下载即可。将libconceal.jar与conceal_android.jar放入工程的libs文件夹下。下载的libs.rar解压后,把armeabi文件夹下的libconceal.so文件翻入自己工程libs文件夹下的armeabi文件夹下即可。





      2. 加密

[java]  view plain  copy
  1. private void encryption(){  
  2.     // Creates a new Crypto object with default implementations of   
  3.     // a key chain as well as native library.  
  4.     Crypto crypto = new Crypto(  
  5.       new SharedPrefsBackedKeyChain(context),  
  6.       new SystemNativeCryptoLibrary());  
  7.   
  8.     // Check for whether the crypto functionality is available  
  9.     // This might fail if Android does not load libaries correctly.  
  10.     if (!crypto.isAvailable()) {  
  11.         Toast.makeText(context, "ENCRYPTION FAIL!", Toast.LENGTH_SHORT).show();  
  12.         return;  
  13.     }  
  14.   
  15.     try {  
  16.         //加密后的文件路径  
  17.         File file = new File(GPUtils.getSharePicPath() + "/text.txt");  
  18.           
  19.         OutputStream fileStream = new BufferedOutputStream(new FileOutputStream(file));  
  20.           
  21.         com.facebook.crypto.Entity entity = new com.facebook.crypto.Entity("text");  
  22.           
  23.         // Creates an output stream which encrypts the data as  
  24.         // it is written to it and writes it out to the file.  
  25.         OutputStream outputStream;  
  26.         outputStream = crypto.getCipherOutputStream(fileStream, entity);  
  27.         //需要加密的text  
  28.         String plainString = "TEST!!!测试!!!";  
  29.         //将String变为byt[]类型  
  30.         byte[] plainText = plainString.getBytes();  
  31.           
  32.         // Write plaintext to it.  
  33.         outputStream.write(plainText);  
  34.         outputStream.close();  
  35.     } catch (IOException e) {  
  36.         // TODO Auto-generated catch block  
  37.         e.printStackTrace();  
  38.     } catch (CryptoInitializationException e) {  
  39.         // TODO Auto-generated catch block  
  40.         e.printStackTrace();  
  41.     } catch (KeyChainException e) {  
  42.         // TODO Auto-generated catch block  
  43.         e.printStackTrace();  
  44.     }  
  45. }  
          Entity应该是加密所使用秘钥。但是具体没有研究。

      3. 解密

[java]  view plain  copy
  1. private void decryption(){  
  2.     // Creates a new Crypto object with default implementations of   
  3.     // a key chain as well as native library.  
  4.     Crypto crypto = new Crypto(  
  5.       new SharedPrefsBackedKeyChain(context),  
  6.       new SystemNativeCryptoLibrary());  
  7.   
  8.     try {  
  9.         //需要解密的文件路径  
  10.         File file = new File(GPUtils.getSharePicPath() + "/text.txt");  
  11.           
  12.         // Get the file to which ciphertext has been written.  
  13.         FileInputStream fileStream = new FileInputStream(file);  
  14.   
  15.         com.facebook.crypto.Entity entity = new com.facebook.crypto.Entity("text");  
  16.           
  17.         // Creates an input stream which decrypts the data as  
  18.         // it is read from it.  
  19.         InputStream inputStream;  
  20.         inputStream = crypto.getCipherInputStream(fileStream,entity);  
  21.   
  22.         // Read into a byte array.  
  23.         int read;  
  24.         byte[] buffer = new byte[1024];  
  25.         //解密后的文本  
  26.         String plainString = new String();  
  27.           
  28.         // You must read the entire stream to completion.  
  29.         // The verification is done at the end of the stream.  
  30.         // Thus not reading till the end of the stream will cause  
  31.         // a security bug.   
  32.         StringBuilder stringBuilder = new StringBuilder();  
  33.         while((read = inputStream.read(buffer)) > 0){  
  34.             stringBuilder.append(new String(buffer, 0, read));  
  35.         }  
  36.               
  37.         plainString = stringBuilder.toString();  
  38.         Toast.makeText(context, plainString, Toast.LENGTH_SHORT).show();  
  39.         inputStream.close();  
  40.     } catch (IOException e) {  
  41.         // TODO Auto-generated catch block  
  42.         e.printStackTrace();  
  43.     } catch (CryptoInitializationException e) {  
  44.         // TODO Auto-generated catch block  
  45.         e.printStackTrace();  
  46.     } catch (KeyChainException e) {  
  47.         // TODO Auto-generated catch block  
  48.         e.printStackTrace();  
  49.     }  
  50. }  
       在解密时,new Entity时使用的String必须与解密时相同,否则不能正确解密。

       4. 扩展

       目前只是实现了加密String类型。其余类型没有实现。猜测JSON类型的应该也是可以的。这样的话,对于结构化的数据也能较好得加解密了。

     三。总结

       总结一下就是,SQLite对程序猿而言,只需要选择秘钥,其余查询、插入等数据库操作方法与SQLite几乎一模一样,以为它是SQLite的镜像,容易上手,加密结构化数据很便利。但是导入的jar包较大,会增加4MB左右apk大小。Conceal上手相对于SQLite而言,稍微慢一点点,加密大型的结构化的数据较为麻烦。但是体量小,对apk包大小影响很小,只增加0.1MB左右。如果进行快速小型的加密时,使用ConCeal较为方便。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自动控制节水灌溉技术的高低代表着农业现代化的发展状况,灌溉系统自动化水平较低是制约我国高效农业发展的主要原因。本文就此问题研究了单片机控制的滴灌节水灌溉系统,该系统可对不同土壤的湿度进行监控,并按照作物对土壤湿度的要求进行适时、适量灌水,其核心是单片机和PC机构成的控制部分,主要对土壤湿度与灌水量之间的关系、灌溉控制技术及设备系统的硬件、软件编程各个部分进行了深入的研究。 单片机控制部分采用上下位机的形式。下位机硬件部分选用AT89C51单片机为核心,主要由土壤湿度传感器,信号处理电路,显示电路,输出控制电路,故障报警电路等组成,软件选用汇编语言编程。上位机选用586型以上PC机,通过MAX232芯片实现同下位机的电平转换功能,上下位机之间通过串行通信方式进行数据的双向传输,软件选用VB高级编程语言以建立友好的人机界面。系统主要具有以下功能:可在PC机提供的人机对话界面上设置作物要求的土壤湿度相关参数;单片机可将土壤湿度传感器检测到的土壤湿度模拟量转换成数字量,显示于LED显示器上,同时单片机可采用串行通信方式将此湿度值传输到PC机上;PC机通过其内设程序计算出所需的灌水量和灌水时间,且显示于界面上,并将有关的灌水信息反馈给单片机,若需灌水,则单片机系统启动鸣音报警,发出灌水信号,并经放大驱动设备,开启电磁阀进行倒计时定时灌水,若不需灌水,即PC机上显示的灌水量和灌水时间均为0,系统不进行灌水。
优化这段代码 if ( this.queryParam.tonnageOne == '' || this.queryParam.tonnageTwo == '' || this.queryParam.tonnageThree == '' || this.dataSource.some((item) => !item.feiGangName) || this.dataSource.some((item) => !item.feiGangPrice) || this.dataSourceFeWater.some((item) => !item.tscfhcbName) || this.dataSourceFeWater.some((item) => !item.temp) || this.dataSourceFeWater.some((item) => !item.tscfhcb_C) || this.dataSourceFeWater.some((item) => !item.tscfhcb_Si) || this.dataSourceFeWater.some((item) => !item.tscfhcb_Mn) || this.dataSourceFeWater.some((item) => !item.tscfhcb_P) || this.dataSourceFeWater.some((item) => !item.tscfhcb_S) || this.dataSourceFeWater.some((item) => !item.tscfhcb_Price) ) { this.$message.error('请检查值,或者名称不能为空!!!') } else { this.dataSource.forEach((item) => { item['acoursewareId'] = 3343772731908188 const editableItem = this.editableData[item.id] if (editableItem) { Object.assign(item, editableItem) } }) this.editableData = [] this.dataSourceFeWater.forEach((item) => { item['acoursewareId'] = 3343772731908188 const editableItem = this.editableDataFeWater[item.id] if (editableItem) { Object.assign(item, editableItem) } }) this.editableDataFeWater = [] console.log(this.dataSourceFeWater) // 清空可编辑数据 this.conceal = false this.concealFeWater = false var addZldwInfo = { AcoursewareId: 3343772731908188, tons1: this.queryParam.tonnageOne, tons2: this.queryParam.tonnageTwo, tons3: this.queryParam.tonnageThree, } let param = { addZldwInfo: addZldwInfo, addFgList: this.dataSource, b_TscfhcbListAdd: this.dataSourceFeWater, } FeAdd(param) .then((res) => {}) .catch((error) => {}) }
06-07
你可以先将一些重复的代码提取成函数,以减少代码冗余。同时,你可以将这些验证条件提取出来,方便进行管理和修改。下面是一个简单的示例: ```javascript function validateData(dataSource, dataSourceFeWater, queryParam) { // 验证条件 const conditions = [ queryParam.tonnageOne === '', queryParam.tonnageTwo === '', queryParam.tonnageThree === '', dataSource.some((item) => !item.feiGangName), dataSource.some((item) => !item.feiGangPrice), dataSourceFeWater.some((item) => !item.tscfhcbName), dataSourceFeWater.some((item) => !item.temp), dataSourceFeWater.some((item) => !item.tscfhcb_C), dataSourceFeWater.some((item) => !item.tscfhcb_Si), dataSourceFeWater.some((item) => !item.tscfhcb_Mn), dataSourceFeWater.some((item) => !item.tscfhcb_P), dataSourceFeWater.some((item) => !item.tscfhcb_S), dataSourceFeWater.some((item) => !item.tscfhcb_Price), ]; if (conditions.some((condition) => condition)) { // 验证失败 return false; } // 验证成功 return true; } function updateData(dataSource, editableData) { dataSource.forEach((item) => { item['acoursewareId'] = 3343772731908188; const editableItem = editableData[item.id]; if (editableItem) { Object.assign(item, editableItem); } }); editableData = []; } // 在原来的代码中调用新的函数 if (validateData(this.dataSource, this.dataSourceFeWater, this.queryParam)) { updateData(this.dataSource, this.editableData); updateData(this.dataSourceFeWater, this.editableDataFeWater); console.log(this.dataSourceFeWater); // 清空可编辑数据 this.conceal = false; this.concealFeWater = false; var addZldwInfo = { AcoursewareId: 3343772731908188, tons1: this.queryParam.tonnageOne, tons2: this.queryParam.tonnageTwo, tons3: this.queryParam.tonnageThree, }; let param = { addZldwInfo: addZldwInfo, addFgList: this.dataSource, b_TscfhcbListAdd: this.dataSourceFeWater, }; FeAdd(param) .then((res) => {}) .catch((error) => {}); } else { this.$message.error('请检查值,或者名称不能为空!!!'); } ``` 上面的代码,我们将验证条件和更新数据的操作分别提取成了两个函数:`validateData` 和 `updateData`。这样可以让代码更加清晰和易于维护。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值