最近想要对字符串加密,但又不需要太严格的,于是就想到了对称加密,百度一搜,正好就搜索到了廖雪峰老师的代码。自己在使用中遇到并解决了一些问题。记录一下。
以下是老师的源码:
import java.security.*;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;
public class Main {
public static void main(String[] args) throws Exception {
// 原文:
String message = "Hello, world!";
System.out.println("Message: " + message);
// 128位密钥 = 16 bytes Key:
byte[] key = "1234567890abcdef".getBytes("UTF-8");
// 加密:
byte[] data = message.getBytes("UTF-8");
byte[] encrypted = encrypt(key, data);
System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(key, encrypted);
System.out.println("Decrypted: " + new String(decrypted, "UTF-8"));
}
// 加密:
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
// 解密:
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
}
运行当然没有问题,效果也很好。但使用之后发现并不是很顺手。问题出在了这:
// 加密:
byte[] data = message.getBytes("UTF-8");
byte[] encrypted = encrypt(key, data);
System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
// 解密:可以看到解密是直接把加密出来的byte[]型数据进行解密。
byte[] decrypted = decrypt(key, encrypted);
可以看到解密中传入的第二个参数是直接把加密出来的byte[]型数据传入进行解密。这里如果对encrypted进行.toString、String.ValueOf、Base64.getEncoder().encodeToString()等字符串强转再用getBytes(“UTF-8”)转回byte型放进decrypt()中解密,jvm将会报错,而我们肯定不会加完密就解密,通常要进行显示、存储、传输,这时候用byte[]类型就显得有些别扭,如果能全程使用字符串进行加解密,想必会非常顺手。于是我开始研究,显然加密后的byte数组是不能强制转换成字符串的,所以我们将二进制数据转换成十六进制表示然后需要的时候再转回来即可,再略加修改得到以下工具类:
import java.security.*;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;
/**
* Created by bob
* Date : 21-9-10
* Describe :
*/
public class testutil {
// 加密:
public static String encrypt(String key, String input) throws Exception {
byte[] mKey = key.getBytes(StandardCharsets.UTF_8);
byte[] data = input.getBytes(StandardCharsets.UTF_8);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(mKey, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return parseByte2HexStr(cipher.doFinal(data));
}
// 解密:
public static String decrypt(String key, String input) throws Exception {
byte[] mKey = key.getBytes(StandardCharsets.UTF_8);
byte[] data = parseHexStr2Byte(input);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(mKey, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return new String(cipher.doFinal(data), StandardCharsets.UTF_8);
}
/**
* 将二进制转换成16进制
*
* @param buf
* @return
*/
public static String parseByte2HexStr(byte buf[]) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < buf.length; i++) {
String hex = Integer.toHexString(buf[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
/**
* 将16进制转换为二进制
*
* @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++) {
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;
}
}
经过略微修改后,就完成了上述工具类,非常方便,加密只需要调用encrypt(秘钥,内容);就能返回加密后的字符串,解密只需要调用decrypt(秘钥,加密后的内容);就能返回解密后也就是加密前的内容【哦,这里提个醒,秘钥必须是16位的倍数(16、32、…)。】全程传字符串类型就好了,非常方便。
参考1:JAVA实现AES 解密报错Input length must be multiple of 16 when decrypting with padded cipher
参考2:廖雪峰对称加密算法