aes是什么?
aes是对称加密的一种,什么是对称加密呢?就是加密和解密使用相同的秘钥的加密算法,显而易见,aes加密和md5不同,aes是可逆的,aes加密算法用于替代以前的des加密算法。
使用场景:
工作的过程中,我们有些文件因为需要携带出公司,可能需要加密,最常见的比如源代码加密。如果我想让加密可逆,又能自己指定加密和解密使用的秘钥,那么aes加密算法真的是一个不错的选择哦。因为既然可以自己指定秘钥,我们控制起来就会让aes这个加密算法安全很多。
看下面的例子:
package com.lzzcms.utils;
import java.io.File;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.io.FileUtils;
public class CryptoUtilTest {
public static Cipher getCipher(int type,String seed){
try {
// 创建AES的KeyGenerator对象
KeyGenerator kgen = KeyGenerator.getInstance("AES");
// 利用用户传入的字符串作为种子初始化出128位的key生产者不能使用
//kgen.init(128, new SecureRandom(seed.getBytes()));因为在linux上会导致加解密出错,
//见代码后边的说明。
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );
secureRandom.setSeed(seed.getBytes());
kgen.init(128, secureRandom);
//生成秘钥
SecretKey secretKey = kgen.generateKey();
//返回基本编码格式的密钥,如果此密钥不支持编码,则返回 null。
byte[] enCodeFormat = secretKey.getEncoded();
// 转换为AES专用密钥
SecretKeySpec specKey = new SecretKeySpec(enCodeFormat, "AES");
// 创建密码器
Cipher cipher = Cipher.getInstance("AES");
// 初始化为加密模式Cipher.ENCRYPT_MODE或解密模式Cipher.DECRYPT_MODE的密码器
cipher.init(type, specKey);
return cipher;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 加密
* @param content :要加密的内容
* @param seed :种子
* @return :加密后的字节数组
*/
public static byte[] encrypt(String content, String seed) {
try {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE,seed);
byte[] contentBytes = content.getBytes("utf-8");
byte[] result = cipher.doFinal(contentBytes);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 解密AES加密过的字节数组
* @param contentBytes :AES加密过的字节数组
* @param seed :加密时传入的种子
* @return :解密后的字节数组
*/
public static byte[] decrypt(byte[] contentBytes, String seed) {
try {
Cipher cipher = getCipher(Cipher.DECRYPT_MODE,seed);
byte[] result = cipher.doFinal(contentBytes);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**将字节数组转换成16进制字符组成的字符串
* @param buf :
* @return
*/
public static String parseByte2HexStr(byte buf[]) {
StringBuffer sb = new StringBuffer();
int length = buf.length;
for (int i = 0; i < length; i++) {
String hex = Integer.toHexString(buf[i]&0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
/**
* 把16进制字符组成的字符串转为字节数组并返回
* 这里传入的十六进制一定是偶数的长度,因为parseByte2HexStr处理了
* @param hexStr
* @return
*/
public static byte[] parseHexStr2Byte(String hexStr) {
if (hexStr.length() < 1) return null;
byte[] result = new byte[hexStr.length()/2];
for (int i = 0;i< hexStr.length()/2; i++) {
//两个十六进制字符构成一个byte.high表示高4位,low表示低四位。Integer.parseInt(s,radix),
//作用:把s转为10进制,radix指定s是什么进制
int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);
int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
public static void main(String[] args) throws Exception {
test();
}
private static void test() throws Exception {
String content = FileUtils.readFileToString(new File("D:\\zhao\\tmp\\app.py"), "utf-8");
String seed = "123";//秘钥
System.out.println("加密之前:" + content);
// 加密
byte[] encrypt = CryptoUtil.encrypt(content, seed);
System.out.println("加密后返回的字节数组转为string:" + new String(encrypt,"utf-8"));
//如果想要加密内容不显示乱码,需要将加密后返回的字节数组转换为16进制
String hexStrResult = CryptoUtil.parseByte2HexStr(encrypt);
System.out.println("加密后返回的字节数组转为16进制字符组成的字符串:"+hexStrResult);
//把16进制字符组成的字符串还原为字节数组
byte[] twoBytes = CryptoUtil.parseHexStr2Byte(hexStrResult);
System.out.println("16进制字符组成的字符串还原的字节数组:"+new String(twoBytes,"utf-8"));
//解密1
byte[] decrypt1 = CryptoUtil.decrypt(twoBytes, seed);
System.out.println("通过16进制字符组成的字符串还原的字节数组解密:"+new String(decrypt1,"utf-8"));
/* 解密2:与解密1结果完全一样,之所以通过16进制转换了一下,
仅仅是为了在存储的时候不想存储乱码二是存储16进制字符组成的字符串而已*/
byte[] decrypt = CryptoUtil.decrypt(encrypt, seed);
System.out.println("通过加密后的原始字节数组解密:" + new String(decrypt,"utf-8"));
}
}
结果截图如下:
代码说明:
1.功能解释:读入一个python文件,并把源代码的内容进行aes加密,因为转为字符串之后是乱码,需要转为16进制字符组成的字符串,也相当于进行了一次二次加密。
2.不能使用kgen.init(128, new SecureRandom(seed.getBytes()));因为在linux上会导致加解密出错:
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
at javax.crypto.Cipher.doFinal(DashoA13*..)
at chb.test.crypto.AESUtils.crypt(AESUtils.java:386)
at chb.test.crypto.AESUtils.AesDecrypt(AESUtils.java:254)
at chb.test.crypto.AESUtils.main(AESUtils.java:40)
虽然kgen.init(128, new SecureRandom(seed.getBytes()));和
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );
secureRandom.setSeed(seed.getBytes());
kgen.init(128, secureRandom);
看起来差不多,但是如果使用kgen.init(128, new SecureRandom(seed.getBytes()));就会报上边的错,当初想起来也是让我折腾了好久呀,后来查资料说是SecureRandom 的内部实现与操作系统有关,但是如果在getInstance 方法之后又调用了 setSeed 方法,就可以避免这种因为操作系统的不同而一起的差异,以便确保在 windows 和所有的linux上每次初始化的 key 都相同。
3.上面我只是打印出了各个步骤加密和解密后的结果,实际存储的时候可以把16进制字符组成的字符串存放在文件中作为加密文件的内容,解密的时候读取该加密内容按照上边代码中的方法解密就可以了。