几番折磨终有结果,现将Demo整理出来。。。
package com.king.zjc; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import android.util.Log; public class AESHelper { public static final String TAG = AESHelper.class.getSimpleName(); Runtime mRuntime = Runtime.getRuntime(); @SuppressWarnings("resource") public boolean <span style="color:#FF0000;">AESCipher</span>(int cipherMode, String sourceFilePath, String targetFilePath, String seed) { boolean result = false; FileChannel sourceFC = null; FileChannel targetFC = null; try { if (cipherMode != Cipher.ENCRYPT_MODE && cipherMode != Cipher.DECRYPT_MODE) { Log.d(TAG, "Operation mode error, should be encrypt or decrypt!"); return false; } Cipher mCipher = Cipher.getInstance("AES/CFB/NoPadding"); byte[] rawkey = getRawKey(seed.getBytes()); File sourceFile = new File(sourceFilePath); File targetFile = new File(targetFilePath); sourceFC = new RandomAccessFile(sourceFile, "r").getChannel(); targetFC = new RandomAccessFile(targetFile, "rw").getChannel(); SecretKeySpec secretKey = new SecretKeySpec(rawkey, "AES"); mCipher.init(cipherMode, secretKey, new IvParameterSpec( new byte[mCipher.getBlockSize()])); ByteBuffer byteData = ByteBuffer.allocate(1024); while (sourceFC.read(byteData) != -1) { // 通过通道读写交叉进行。 // 将缓冲区准备为数据传出状态 byteData.flip(); byte[] byteList = new byte[byteData.remaining()]; byteData.get(byteList, 0, byteList.length); //此处,若不使用数组加密解密会失败,因为当byteData达不到1024个时,加密方式不同对空白字节的处理也不相同,从而导致成功与失败。 byte[] bytes = mCipher.doFinal(byteList); targetFC.write(ByteBuffer.wrap(bytes)); byteData.clear(); } result = true; } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException e) { Log.d(TAG, e.getMessage()); } finally { try { if (sourceFC != null) { sourceFC.close(); } if (targetFC != null) { targetFC.close(); } } catch (IOException e) { Log.d(TAG, e.getMessage()); } } return result; } /** * 加密后的字符串 * * @param seed * @param clearText * @return */ public String encrypt(String seed, String source) { // Log.d(TAG, "加密前的seed=" + seed + ",内容为:" + clearText); byte[] result = null; try { byte[] rawkey = getRawKey(seed.getBytes()); result = encrypt(rawkey, source.getBytes()); } catch (Exception e) { e.printStackTrace(); } String content = toHex(result); return content; } /** * 解密后的字符串 * * @param seed * @param encrypted * @return */ public String decrypt(String seed, String encrypted) { byte[] rawKey; try { rawKey = getRawKey(seed.getBytes()); byte[] enc = toByte(encrypted); byte[] result = decrypt(rawKey, enc); String coentn = new String(result); return coentn; } catch (Exception e) { e.printStackTrace(); return null; } } /** * 使用一个安全的随机数来产生一个密匙,密匙加密使用的 * * @param seed * @return * @throws NoSuchAlgorithmException */ private byte[] <span style="color:#FF0000;">getRawKey</span>(byte[] seed) throws NoSuchAlgorithmException { // 获得一个随机数,传入的参数为默认方式。 SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); // 设置一个种子,一般是用户设定的密码 sr.setSeed(seed); // 获得一个key生成器(AES加密模式) KeyGenerator keyGen = KeyGenerator.getInstance("AES"); // 设置密匙长度128位 keyGen.init(128, sr); // 获得密匙 SecretKey key = keyGen.generateKey(); // 返回密匙的byte数组供加解密使用 byte[] raw = key.getEncoded(); return raw; } /** * 结合密钥生成加密后的密文 * * @param raw * @param input * @return * @throws Exception */ private byte[] encrypt(byte[] raw, byte[] input) throws Exception { // 根据上一步生成的密匙指定一个密匙 SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); // Cipher cipher = Cipher.getInstance("AES"); // 加密算法,加密模式和填充方式三部分或指定加密算 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // 初始化模式为加密模式,并指定密匙 cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec( new byte[cipher.getBlockSize()])); byte[] encrypted = cipher.doFinal(input); return encrypted; } /** * 根据密钥解密已经加密的数据 * * @param raw * @param encrypted * @return * @throws Exception */ private byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception { SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec( new byte[cipher.getBlockSize()])); byte[] decrypted = cipher.doFinal(encrypted); return decrypted; } public String toHex(String txt) { return toHex(txt.getBytes()); } public String fromHex(String hex) { return new String(toByte(hex)); } public byte[] toByte(String hexString) { int len = hexString.length() / 2; byte[] result = new byte[len]; for (int i = 0; i < len; i++) result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue(); return result; } public String toHex(byte[] buf) { if (buf == null || buf.length <= 0) return ""; StringBuffer result = new StringBuffer(2 * buf.length); for (int i = 0; i < buf.length; i++) { appendHex(result, buf[i]); } return result.toString(); } private void appendHex(StringBuffer sb, byte b) { final String HEX = "0123456789ABCDEF"; sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f)); } }1. 其实我的Demo中只用到了AESCipher和getRawKey两个方法。若要返回字符串,一定要注意编码,如
public static String encrypt(String data, String key) throws Exception { try { Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES"); cipher.init(Cipher.ENCRYPT_MODE, keyspec); byte[] encrypted = cipher.doFinal(data.getBytes()); return Base64.encodeToString(encrypted, Base64.DEFAULT); } catch (Exception e) { e.printStackTrace(); return null; } } public static String desEncrypt(String data, String key) throws Exception { try { byte[] encrypted1 = Base64.decode(data.getBytes(), Base64.DEFAULT); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES"); cipher.init(Cipher.DECRYPT_MODE, keyspec); byte[] original = cipher.doFinal(encrypted1); return new String(original, "UTF-8"); } catch (Exception e) { e.printStackTrace(); return null; } }
3. 先前一直失败,其重点是对“填充模式”的应用,我最终使用了AES/CFB/NoPadding,当不满16字节时,加密后数据长度不变。
算法/模式/填充 16字节加密后数据长度 不满16字节加密后长度 AES/CBC/NoPadding 16 不支持 AES/CBC/PKCS5Padding 32 16 AES/CBC/ISO10126Padding 32 16 AES/CFB/NoPadding 16 原始数据长度 AES/CFB/PKCS5Padding 32 16 AES/CFB/ISO10126Padding 32 16 AES/ECB/NoPadding 16 不支持 AES/ECB/PKCS5Padding 32 16 AES/ECB/ISO10126Padding 32 16 AES/OFB/NoPadding 16 原始数据长度 AES/OFB/PKCS5Padding 32 16 AES/OFB/ISO10126Padding 32 16 AES/PCBC/NoPadding 16 不支持 AES/PCBC/PKCS5Padding 32 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
package com.king.zjc; import javax.crypto.Cipher; import com.hisense.ad.encryption.AESHelper; import com.hisense.ad.encryption.R; import android.annotation.SuppressLint; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; @SuppressLint("SdCardPath") public class MainActivity extends Activity { private final String SDcardPath = "/mnt/sdcard/encry/"; private Button mEncryptButton; private Button mDecryptButton; private TextView mShowMessage; private EditText mFileName; private EditText mNewFileName; private AESHelper mAESHelper; private EncryptionOrDecryptionTask mTask = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mAESHelper = new AESHelper(); mFileName = (EditText) findViewById(R.id.file_name); mNewFileName = (EditText) findViewById(R.id.new_file_name); mShowMessage = (TextView) findViewById(R.id.message); mEncryptButton = (Button) findViewById(R.id.encrypt); mDecryptButton = (Button) findViewById(R.id.decrypt); mEncryptButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mShowMessage.setText("开始加密,请稍等..."); if (mTask != null) { mTask.cancel(true); } mTask = new EncryptionOrDecryptionTask(true, SDcardPath + mFileName.getText(), SDcardPath, mNewFileName .getText().toString(), "zjc"); mTask.execute(); } }); mDecryptButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mShowMessage.setText("开始解密,请稍等..."); if (mTask != null) { mTask.cancel(true); } mTask = new EncryptionOrDecryptionTask(false, SDcardPath + mFileName.getText(), SDcardPath, mNewFileName .getText().toString(), "zjc"); mTask.execute(); } }); } // ####################### /** * 加密解密 */ private class EncryptionOrDecryptionTask extends AsyncTask<Void, Void, Boolean> { private String mSourceFile = ""; private String mNewFilePath = ""; private String mNewFileName = ""; private String mSeed = ""; private boolean mIsEncrypt = false; public EncryptionOrDecryptionTask(boolean isEncrypt, String sourceFile, String newFilePath, String newFileName, String seed) { this.mSourceFile = sourceFile; this.mNewFilePath = newFilePath; this.mNewFileName = newFileName; this.mSeed = seed; this.mIsEncrypt = isEncrypt; } @Override protected Boolean doInBackground(Void... params) { boolean result = false; if (mIsEncrypt) { result = mAESHelper.AESCipher(Cipher.ENCRYPT_MODE, mSourceFile, mNewFilePath + mNewFileName, mSeed); } else { result = mAESHelper.AESCipher(Cipher.DECRYPT_MODE, mSourceFile, mNewFilePath + mNewFileName, mSeed); } return result; } @Override protected void onPostExecute(Boolean result) { super.onPostExecute(result); String showMessage = ""; if (mIsEncrypt) { showMessage = result ? "加密已完成" : "加密失败!"; } else { showMessage = result ? "解密完成" : "解密失败!"; } mShowMessage.setText(showMessage); } } }
main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="请输入文件名" /> <EditText android:id="@+id/file_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="请输入文件名" > <requestFocus /> </EditText> <EditText android:id="@+id/new_file_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="请输入新的文件名" > </EditText> <Button android:id="@+id/encrypt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="加密" /> <Button android:id="@+id/decrypt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="解密" /> <TextView android:id="@+id/message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" /> </LinearLayout>
注:许多代码来自网络,但已记不清最初来自哪里了。