项目需要对特定数据进行加密,看了一下网上的很多实现,在不同版本的安卓上需要做一些特殊处理。那么我想试着编写一个通用性比较强,不需要对版本进行适配的AES加解密库。
这个库还可以将AES的密文进行Base64编码之后进行传输,算是功能上的补充。
AES 加密
生成加解密Key
使用SecretKeySpec来将给定数组生成对应的key,由于这里是AES加密,所以algorithm为"AES"
1
| SecretKeySpec keySpec = new SecretKeySpec(key, algorithm);
|
使用Cipher进行加密
Cipher是javax.crypto中的类,他可以提供加解密的功能。使用getInstance
方法来构建Cipher对象,并提供转换(加解密)模式。
这里我使用的是AES/ECB/PKCS5Padding
模式,这里可以简单的说一下这几个参数的含义。
transformation使用分隔符分割的三个参数分别为:算法/模式/填充,如果找不到对应的方法,会抛出NoSuchAlgorithmException
异常。
首先说算法部分,常用的算法有:
- AES - Advanced Encryption Standard 高级数据加密标准
- DES - Data Encryption Standard 数据加密标准
- 3DES - Triple DES、DESede 进行了三重DES加密的算法
第二部分是,分组密码的工作模式,常用的模式有:
- ECB - 电子密码本,每次加密均产生独立的密文分组,并且对其他的密文分组不会产生影响,也就是相同的明文加密后产生相同的密文
- CBC - 密文链接,明文加密前需要先和前面的密文进行异或运算,也就是相同的明文加密后产生不同的密文
第三部分,分组密码的填充模式:
- NoPadding - 不进行填充
- ZeroPadding - 数据长度不对齐时使用0填充,否则不填充
- PKCS5Padding - PKCS7Padding的子集,块大小固定为8字节
- PKCS7Padding - 假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n,如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小
1 2 3 4 5
| Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(data);
|
使用Cipher进行解密
解密方式与加密方式很相似,在init方法中使用的是Cipher.DECRYPT_MODE
,也就是解密模式。由于此时解密完毕的是原文的字节数组,所以需要按照编码格式转换为String。
charset中填入编码时使用的字符集,这里是UTF-8
。
1 2 3 4
| Cipher cipher = Cipher.getInstance(transformation); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] result = cipher.doFinal(data); return new String(result, charset);
|
使用Base64编码
由于加密之后的byte二进制不便于传输,这里将字节数组编码为Base64,在解密时,先将Base64解码为密文原文,然后再进行解密。
这里使用Android sdk包下面的Base64,如果使用java库下面的Base64,则需要API版本大于26,也就是Android8.0。
1 2 3 4
| Base64.encodeToString(input, Base64.NO_WRAP);
Base64.decode(input, Base64.NO_WRAP);
|
第一个参数为编解码字节数组,第二个参数为编码方式,这个编解码模式,一般采用NO_WRAP
。
Base64编解码模式:
- NO_WRAP:略去所有的换行符
- CRLF:Win风格的换行符,使用CR LF这一对作为一行的结尾而不是Unix风格的LF
- DEFAULT:使用默认的方法来编码
- NO_PADDING:略去编码字符串最后的"="
- URL_SAFE:编码时不使用对URL和文件名有特殊意义的字符来作为编码字符,具体就是以-和_取代+和/
完整工具类代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| public class AesUtil {
private static final String algorithm = "AES"; private static final String transformation = "AES/ECB/PKCS5Padding"; private static final String charset = "UTF-8";
public static String encryptToBase64(String key, String data) { try { if (!TextUtils.isEmpty(data)) { byte[] valueByte = encrypt(data.getBytes(charset), key.getBytes(charset)); return encodeBase64(valueByte); } return ""; } catch (Exception e) { e.printStackTrace(); return ""; } }
public static String decryptFromBase64(String key, String data) { try { return decrypt(decodeBase64(data), key.getBytes(charset)); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return ""; }
public static String encodeBase64(byte[] input) { return Base64.encodeToString(input, Base64.NO_WRAP); }
public static byte[] decodeBase64(String input) { try { return Base64.decode(input.getBytes(charset), Base64.NO_WRAP); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; }
public static byte[] encrypt(byte[] data, byte[] key) { try { SecretKeySpec keySpec = new SecretKeySpec(key, algorithm); Cipher cipher = Cipher.getInstance(transformation); cipher.init(Cipher.ENCRYPT_MODE, keySpec); return cipher.doFinal(data); } catch (Exception e) { e.printStackTrace(); } return null; }
public static String decrypt(byte[] data, byte[] key) { try { SecretKeySpec keySpec = new SecretKeySpec(key, algorithm); Cipher cipher = Cipher.getInstance(transformation); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] result = cipher.doFinal(data); return new String(result, charset); } catch (Exception e) { e.printStackTrace(); } return null; } }
|
最后推荐一本扩展阅读的书:《图解密码技术》这本书用通俗易懂的方式讲解了密码学的演进,里面就有关于AES具体的原理,以及分组密码究竟是什么,可以看一下。
参考