Android AES 文件加密解密

几番折磨终有结果,现将Demo整理出来。。。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package com.king.zjc;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5. import java.io.RandomAccessFile;  
  6. import java.nio.ByteBuffer;  
  7. import java.nio.channels.FileChannel;  
  8. import java.security.InvalidAlgorithmParameterException;  
  9. import java.security.InvalidKeyException;  
  10. import java.security.NoSuchAlgorithmException;  
  11. import java.security.SecureRandom;  
  12. import javax.crypto.BadPaddingException;  
  13. import javax.crypto.Cipher;  
  14. import javax.crypto.IllegalBlockSizeException;  
  15. import javax.crypto.KeyGenerator;  
  16. import javax.crypto.NoSuchPaddingException;  
  17. import javax.crypto.SecretKey;  
  18. import javax.crypto.spec.IvParameterSpec;  
  19. import javax.crypto.spec.SecretKeySpec;  
  20. import android.util.Log;  
  21.   
  22. public class AESHelper {  
  23.     public static final String TAG = AESHelper.class.getSimpleName();  
  24.   
  25.     Runtime mRuntime = Runtime.getRuntime();  
  26.   
  27.     @SuppressWarnings("resource")  
  28.     public boolean <span style="color:#FF0000;">AESCipher</span>(int cipherMode, String sourceFilePath,  
  29.             String targetFilePath, String seed) {  
  30.         boolean result = false;  
  31.         FileChannel sourceFC = null;  
  32.         FileChannel targetFC = null;  
  33.   
  34.         try {  
  35.   
  36.             if (cipherMode != Cipher.ENCRYPT_MODE  
  37.                     && cipherMode != Cipher.DECRYPT_MODE) {  
  38.                 Log.d(TAG,  
  39.                         "Operation mode error, should be encrypt or decrypt!");  
  40.                 return false;  
  41.             }  
  42.   
  43.             Cipher mCipher = Cipher.getInstance("AES/CFB/NoPadding");  
  44.   
  45.             byte[] rawkey = getRawKey(seed.getBytes());  
  46.             File sourceFile = new File(sourceFilePath);  
  47.             File targetFile = new File(targetFilePath);  
  48.   
  49.             sourceFC = new RandomAccessFile(sourceFile, "r").getChannel();  
  50.             targetFC = new RandomAccessFile(targetFile, "rw").getChannel();  
  51.   
  52.             SecretKeySpec secretKey = new SecretKeySpec(rawkey, "AES");  
  53.   
  54.             mCipher.init(cipherMode, secretKey, new IvParameterSpec(  
  55.                     new byte[mCipher.getBlockSize()]));  
  56.   
  57.             ByteBuffer byteData = ByteBuffer.allocate(1024);  
  58.             while (sourceFC.read(byteData) != -1) {  
  59.                 // 通过通道读写交叉进行。  
  60.                 // 将缓冲区准备为数据传出状态  
  61.                 byteData.flip();  
  62.   
  63.                 byte[] byteList = new byte[byteData.remaining()];  
  64.                 byteData.get(byteList, 0, byteList.length);  
  65. //此处,若不使用数组加密解密会失败,因为当byteData达不到1024个时,加密方式不同对空白字节的处理也不相同,从而导致成功与失败。   
  66.                 byte[] bytes = mCipher.doFinal(byteList);  
  67.                 targetFC.write(ByteBuffer.wrap(bytes));  
  68.                 byteData.clear();  
  69.             }  
  70.   
  71.             result = true;  
  72.         } catch (IOException | NoSuchAlgorithmException | InvalidKeyException  
  73.                 | InvalidAlgorithmParameterException  
  74.                 | IllegalBlockSizeException | BadPaddingException  
  75.                 | NoSuchPaddingException e) {  
  76.             Log.d(TAG, e.getMessage());  
  77.   
  78.         } finally {  
  79.             try {  
  80.                 if (sourceFC != null) {  
  81.                     sourceFC.close();  
  82.                 }  
  83.                 if (targetFC != null) {  
  84.                     targetFC.close();  
  85.                 }  
  86.             } catch (IOException e) {  
  87.                 Log.d(TAG, e.getMessage());  
  88.             }  
  89.         }  
  90.   
  91.         return result;  
  92.     }  
  93.   
  94.     /** 
  95.      * 加密后的字符串 
  96.      *  
  97.      * @param seed 
  98.      * @param clearText 
  99.      * @return 
  100.      */  
  101.     public String encrypt(String seed, String source) {  
  102.         // Log.d(TAG, "加密前的seed=" + seed + ",内容为:" + clearText);  
  103.         byte[] result = null;  
  104.         try {  
  105.             byte[] rawkey = getRawKey(seed.getBytes());  
  106.             result = encrypt(rawkey, source.getBytes());  
  107.         } catch (Exception e) {  
  108.             e.printStackTrace();  
  109.         }  
  110.         String content = toHex(result);  
  111.         return content;  
  112.   
  113.     }  
  114.   
  115.     /** 
  116.      * 解密后的字符串 
  117.      *  
  118.      * @param seed 
  119.      * @param encrypted 
  120.      * @return 
  121.      */  
  122.     public String decrypt(String seed, String encrypted) {  
  123.         byte[] rawKey;  
  124.         try {  
  125.             rawKey = getRawKey(seed.getBytes());  
  126.             byte[] enc = toByte(encrypted);  
  127.             byte[] result = decrypt(rawKey, enc);  
  128.             String coentn = new String(result);  
  129.             return coentn;  
  130.         } catch (Exception e) {  
  131.             e.printStackTrace();  
  132.             return null;  
  133.         }  
  134.     }  
  135.   
  136.     /** 
  137.      * 使用一个安全的随机数来产生一个密匙,密匙加密使用的 
  138.      *  
  139.      * @param seed 
  140.      * @return 
  141.      * @throws NoSuchAlgorithmException 
  142.      */  
  143.     private byte[] <span style="color:#FF0000;">getRawKey</span>(byte[] seed) throws NoSuchAlgorithmException {  
  144.         // 获得一个随机数,传入的参数为默认方式。  
  145.         SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");  
  146.         // 设置一个种子,一般是用户设定的密码  
  147.         sr.setSeed(seed);  
  148.         // 获得一个key生成器(AES加密模式)  
  149.         KeyGenerator keyGen = KeyGenerator.getInstance("AES");  
  150.         // 设置密匙长度128位  
  151.         keyGen.init(128, sr);  
  152.         // 获得密匙  
  153.         SecretKey key = keyGen.generateKey();  
  154.         // 返回密匙的byte数组供加解密使用  
  155.         byte[] raw = key.getEncoded();  
  156.         return raw;  
  157.     }  
  158.   
  159.     /** 
  160.      * 结合密钥生成加密后的密文 
  161.      *  
  162.      * @param raw 
  163.      * @param input 
  164.      * @return 
  165.      * @throws Exception 
  166.      */  
  167.     private byte[] encrypt(byte[] raw, byte[] input) throws Exception {  
  168.         // 根据上一步生成的密匙指定一个密匙  
  169.         SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");  
  170.         // Cipher cipher = Cipher.getInstance("AES");  
  171.         // 加密算法,加密模式和填充方式三部分或指定加密算  
  172.         Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");  
  173.         // 初始化模式为加密模式,并指定密匙  
  174.         cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(  
  175.                 new byte[cipher.getBlockSize()]));  
  176.         byte[] encrypted = cipher.doFinal(input);  
  177.         return encrypted;  
  178.     }  
  179.   
  180.     /** 
  181.      * 根据密钥解密已经加密的数据 
  182.      *  
  183.      * @param raw 
  184.      * @param encrypted 
  185.      * @return 
  186.      * @throws Exception 
  187.      */  
  188.     private byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {  
  189.         SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");  
  190.         Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");  
  191.         cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(  
  192.                 new byte[cipher.getBlockSize()]));  
  193.         byte[] decrypted = cipher.doFinal(encrypted);  
  194.         return decrypted;  
  195.     }  
  196.   
  197.     public String toHex(String txt) {  
  198.         return toHex(txt.getBytes());  
  199.     }  
  200.   
  201.     public String fromHex(String hex) {  
  202.         return new String(toByte(hex));  
  203.     }  
  204.   
  205.     public byte[] toByte(String hexString) {  
  206.         int len = hexString.length() / 2;  
  207.         byte[] result = new byte[len];  
  208.         for (int i = 0; i < len; i++)  
  209.             result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2),  
  210.                     16).byteValue();  
  211.         return result;  
  212.     }  
  213.   
  214.     public String toHex(byte[] buf) {  
  215.         if (buf == null || buf.length <= 0)  
  216.             return "";  
  217.         StringBuffer result = new StringBuffer(2 * buf.length);  
  218.         for (int i = 0; i < buf.length; i++) {  
  219.             appendHex(result, buf[i]);  
  220.         }  
  221.         return result.toString();  
  222.     }  
  223.   
  224.     private void appendHex(StringBuffer sb, byte b) {  
  225.         final String HEX = "0123456789ABCDEF";  
  226.         sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));  
  227.     }  
  228. }  
1. 其实我的Demo中只用到了AESCipher和getRawKey两个方法。若要返回字符串,一定要注意编码,如

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public static String encrypt(String data, String key) throws Exception {    
  2.         try {    
  3.             Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");    
  4.             SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");    
  5.             cipher.init(Cipher.ENCRYPT_MODE, keyspec);    
  6.             byte[] encrypted = cipher.doFinal(data.getBytes());    
  7.             return Base64.encodeToString(encrypted, Base64.DEFAULT);    
  8.         } catch (Exception e) {    
  9.             e.printStackTrace();    
  10.             return null;    
  11.         }    
  12.     }    
  13.     
  14.     public static String desEncrypt(String data, String key) throws Exception {    
  15.         try {    
  16.             byte[] encrypted1 = Base64.decode(data.getBytes(), Base64.DEFAULT);    
  17.             Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");    
  18.             SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");    
  19.             cipher.init(Cipher.DECRYPT_MODE, keyspec);    
  20.             byte[] original = cipher.doFinal(encrypted1);    
  21.             return new String(original, "UTF-8");    
  22.         } catch (Exception e) {    
  23.             e.printStackTrace();    
  24.             return null;    
  25.         }    
  26.     }    

2. 如果先把一个文件转换成字节数组,然后再加密,最后生成文件,这样很大机率很会产生OOM,所以这里利用了FileChannel,一次读取一定的字节数,而后再进行加密解密,最后再通过Channel生成新文件。

3. 先前一直失败,其重点是对“填充模式”的应用,我最终使用了AES/CFB/NoPadding,当不满16字节时,加密后数据长度不变。

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. 算法/模式/填充                16字节加密后数据长度        不满16字节加密后长度    
  2. AES/CBC/NoPadding             16                          不支持    
  3. AES/CBC/PKCS5Padding          32                          16    
  4. AES/CBC/ISO10126Padding       32                          16    
  5. AES/CFB/NoPadding             16                          原始数据长度    
  6. AES/CFB/PKCS5Padding          32                          16    
  7. AES/CFB/ISO10126Padding       32                          16    
  8. AES/ECB/NoPadding             16                          不支持    
  9. AES/ECB/PKCS5Padding          32                          16    
  10. AES/ECB/ISO10126Padding       32                          16    
  11. AES/OFB/NoPadding             16                          原始数据长度    
  12. AES/OFB/PKCS5Padding          32                          16    
  13. AES/OFB/ISO10126Padding       32                          16    
  14. AES/PCBC/NoPadding            16                          不支持    
  15. AES/PCBC/PKCS5Padding         32                          16    
  16. AES/PCBC/ISO10126Padding      32                          16    
当原始数据长度为16的整数倍时,假如原始数据长度等于16*n,则使用NoPadding时加密后数据长度等于16*n,其它情况下加密数据长度等于16*(n+1)。在不足16的整数倍的情况下,假如原始数据长度等于16*n+m[其中m小于16],除了NoPadding填充之外的任何方 式,加密数据长度都等于16*(n+1);NoPadding填充情况下,CBC、ECB和PCBC三种模式是不支持的,CFB、OFB两种模式下则加密数据长度等于原始数据长度。

4. 文件大小不同,用时不定,所以把加密解密过程放到一个AsyncTask内进行

MainActivity.java

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package com.king.zjc;  
  2.   
  3. import javax.crypto.Cipher;  
  4.   
  5. import com.hisense.ad.encryption.AESHelper;  
  6. import com.hisense.ad.encryption.R;  
  7.   
  8. import android.annotation.SuppressLint;  
  9. import android.app.Activity;  
  10. import android.os.AsyncTask;  
  11. import android.os.Bundle;  
  12. import android.view.View;  
  13. import android.view.View.OnClickListener;  
  14. import android.widget.Button;  
  15. import android.widget.EditText;  
  16. import android.widget.TextView;  
  17.   
  18. @SuppressLint("SdCardPath")  
  19. public class MainActivity extends Activity {  
  20.   
  21.     private final String SDcardPath = "/mnt/sdcard/encry/";  
  22.     private Button mEncryptButton;  
  23.     private Button mDecryptButton;  
  24.     private TextView mShowMessage;  
  25.     private EditText mFileName;  
  26.     private EditText mNewFileName;  
  27.     private AESHelper mAESHelper;  
  28.     private EncryptionOrDecryptionTask mTask = null;  
  29.   
  30.     @Override  
  31.     protected void onCreate(Bundle savedInstanceState) {  
  32.         super.onCreate(savedInstanceState);  
  33.         setContentView(R.layout.main);  
  34.         mAESHelper = new AESHelper();  
  35.   
  36.         mFileName = (EditText) findViewById(R.id.file_name);  
  37.         mNewFileName = (EditText) findViewById(R.id.new_file_name);  
  38.         mShowMessage = (TextView) findViewById(R.id.message);  
  39.         mEncryptButton = (Button) findViewById(R.id.encrypt);  
  40.         mDecryptButton = (Button) findViewById(R.id.decrypt);  
  41.   
  42.         mEncryptButton.setOnClickListener(new OnClickListener() {  
  43.   
  44.             @Override  
  45.             public void onClick(View v) {  
  46.   
  47.                 mShowMessage.setText("开始加密,请稍等...");  
  48.                 if (mTask != null) {  
  49.                     mTask.cancel(true);  
  50.                 }  
  51.   
  52.                 mTask = new EncryptionOrDecryptionTask(true, SDcardPath  
  53.                         + mFileName.getText(), SDcardPath, mNewFileName  
  54.                         .getText().toString(), "zjc");  
  55.                 mTask.execute();  
  56.             }  
  57.         });  
  58.   
  59.         mDecryptButton.setOnClickListener(new OnClickListener() {  
  60.   
  61.             @Override  
  62.             public void onClick(View v) {  
  63.   
  64.                 mShowMessage.setText("开始解密,请稍等...");  
  65.   
  66.                 if (mTask != null) {  
  67.                     mTask.cancel(true);  
  68.                 }  
  69.   
  70.                 mTask = new EncryptionOrDecryptionTask(false, SDcardPath  
  71.                         + mFileName.getText(), SDcardPath, mNewFileName  
  72.                         .getText().toString(), "zjc");  
  73.                 mTask.execute();  
  74.   
  75.             }  
  76.         });  
  77.     }  
  78.   
  79.     // #######################  
  80.     /** 
  81.      * 加密解密 
  82.      */  
  83.     private class EncryptionOrDecryptionTask extends  
  84.             AsyncTask<Void, Void, Boolean> {  
  85.   
  86.         private String mSourceFile = "";  
  87.         private String mNewFilePath = "";  
  88.         private String mNewFileName = "";  
  89.         private String mSeed = "";  
  90.         private boolean mIsEncrypt = false;  
  91.   
  92.         public EncryptionOrDecryptionTask(boolean isEncrypt, String sourceFile,  
  93.                 String newFilePath, String newFileName, String seed) {  
  94.             this.mSourceFile = sourceFile;  
  95.             this.mNewFilePath = newFilePath;  
  96.             this.mNewFileName = newFileName;  
  97.             this.mSeed = seed;  
  98.             this.mIsEncrypt = isEncrypt;  
  99.         }  
  100.   
  101.         @Override  
  102.         protected Boolean doInBackground(Void... params) {  
  103.   
  104.             boolean result = false;  
  105.   
  106.             if (mIsEncrypt) {  
  107.                 result = mAESHelper.AESCipher(Cipher.ENCRYPT_MODE, mSourceFile,  
  108.                         mNewFilePath + mNewFileName, mSeed);  
  109.             } else {  
  110.                 result = mAESHelper.AESCipher(Cipher.DECRYPT_MODE, mSourceFile,  
  111.                         mNewFilePath + mNewFileName, mSeed);  
  112.             }  
  113.   
  114.             return result;  
  115.         }  
  116.   
  117.         @Override  
  118.         protected void onPostExecute(Boolean result) {  
  119.             super.onPostExecute(result);  
  120.             String showMessage = "";  
  121.   
  122.             if (mIsEncrypt) {  
  123.                 showMessage = result ? "加密已完成" : "加密失败!";  
  124.             } else {  
  125.                 showMessage = result ? "解密完成" : "解密失败!";  
  126.             }  
  127.   
  128.             mShowMessage.setText(showMessage);  
  129.         }  
  130.     }  
  131.   
  132. }  

main.xml

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical" >  
  6.   
  7.     <TextView  
  8.         android:id="@+id/textView1"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:text="请输入文件名" />  
  12.   
  13.     <EditText  
  14.         android:id="@+id/file_name"  
  15.         android:layout_width="match_parent"  
  16.         android:layout_height="wrap_content"  
  17.         android:ems="10"  
  18.         android:hint="请输入文件名" >  
  19.   
  20.         <requestFocus />  
  21.     </EditText>  
  22.   
  23.     <EditText  
  24.         android:id="@+id/new_file_name"  
  25.         android:layout_width="match_parent"  
  26.         android:layout_height="wrap_content"  
  27.         android:ems="10"  
  28.         android:hint="请输入新的文件名" >  
  29.     </EditText>  
  30.   
  31.     <Button  
  32.         android:id="@+id/encrypt"  
  33.         android:layout_width="wrap_content"  
  34.         android:layout_height="wrap_content"  
  35.         android:text="加密" />  
  36.   
  37.     <Button  
  38.         android:id="@+id/decrypt"  
  39.         android:layout_width="wrap_content"  
  40.         android:layout_height="wrap_content"  
  41.         android:text="解密" />  
  42.   
  43.     <TextView  
  44.         android:id="@+id/message"  
  45.         android:layout_width="wrap_content"  
  46.         android:layout_height="wrap_content"  
  47.         android:text="TextView" />  
  48.   
  49. </LinearLayout>  

注:许多代码来自网络,但已记不清最初来自哪里了。


转载地址:http://blog.csdn.net/zjclugger/article/details/34838447


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值