关于3DES
加密方式
加密:C = Ek3(Dk2(Ek1(M))) 即对明文数据进行,加密 --> 解密 --> 加密的过程,最后得到密文数据
解密:M = Dk1(Ek2(Dk3(C))) 即对密文数据进行,解密 --> 加密 --> 解密的过程,最后得到明文数据
这里可以K1=K3,但不能K1=K2=K3(如果相等的话就成了DES算法了)
默认模式
默认模式:DESede 密码实际上是DESede/ECB/PKCS5Padding。请注意,该模式是不使用IV的ECB
为了提高安全性,我们通常使用 DESede/CBC/PKCS5Padding, 该方法使用 IV
密钥长度
DES密钥长度为56位+8位的校验位
3DES长度应该是56*3+8*3=192位 24个字节
代码实现
3des 不带IV
/**
* 加密
* @param data 加密数据
* @param key 加密密码
* @return
* @throws Exception
*/
public static String encrypt3DES(byte[] data, byte[] key) throws Exception {
// 恢复密钥
SecretKey secretKey = new SecretKeySpec(key, DESede);
// Cipher完成加密
Cipher cipher = Cipher.getInstance(DESede);
// cipher初始化
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encrypt = cipher.doFinal(data);
//转码
// return new String(Base64.encode(encrypt, Base64.DEFAULT), "UTF-8");
return byte2HexStr(encrypt);
}
/**
* bytes转换成十六进制字符串
*
* @param byte[] b byte数组
* @return String 每个Byte值之间空格分隔
*/
public static String byte2HexStr(byte[] b) {
String stmp = "";
StringBuilder sb = new StringBuilder("");
for (int n = 0; n < b.length; n++) {
stmp = Integer.toHexString(b[n] & 0xFF);
sb.append((stmp.length() == 1) ? "0" + stmp : stmp);
}
return sb.toString().toLowerCase().trim();
}
3DES 带 IV
public final class Crypto {
public static String code = "UTF-8";
public static final String TripleDESencrypt(String data, String secretKey) {
try {
// 3DES加密
byte[] encrpyted = tripleDES(Cipher.ENCRYPT_MODE, data.getBytes(code), secretKey.getBytes());
byte[] encoded = Base64.encodeBase64(encrpyted); // Base64编码
return new String(encoded);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return "";
}
}
public static final String TripleDESdecrypt(String data, String secretKey) {
try {
byte[] decoded = Base64.decodeBase64(data); // Base64解码
byte[] decrypted = tripleDES(Cipher.DECRYPT_MODE, decoded, secretKey.getBytes());// 3DES解密
return new String(decrypted, code);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return "";
}
}
private static byte[] tripleDES(int opmode, byte[] data, byte[] secretKey) {
return cipher("DESede", "DESede/CBC/PKCS5Padding", opmode, data, "01234567".getBytes(), secretKey);
}
/**
* 通用的对称加密算法
*
* @param algorithm , 算法名称
* @param transformation , 算法名称/工作模式/填充模式
* @param opmode :Cipher.ENCRYPT_MODE和Cipher.DECRYPT_MODE
* @param data , 明文或密文数据
* @param iv , 初始化向量
* @param secretKey ,密钥
* @return 加密或解密的结果
*/
private static final byte[] cipher(String algorithm, String transformation, int opmode, byte[] data, byte[] iv,
byte[] secretKey) {
try {
// 转换密钥
Key key = SecretKeyFactory.getInstance(algorithm).generateSecret(new DESedeKeySpec(secretKey));
// 转换初始化向量
IvParameterSpec spec = new IvParameterSpec(iv);
// 加密解密器
Cipher cipher = Cipher.getInstance(transformation);
cipher.init(opmode, key, spec);
// 加密解密操作
return cipher.doFinal(data);
} catch (InvalidKeyException | InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException
| IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
}
}
关于AES
从一段有问题的代码开始:
public class AAESUtils {
/**密钥长度*/
private static final int KEY_LENGTH = 32;
/**默认填充位数*/
private static final String DEFAULT_VALUE = "0";
/**
* 加密
* @param key 密钥
* @param src 加密文本
* @return 加密后的文本
* @throws Exception
*/
public static String encrypt(String key, String src) throws Exception {
// 对源数据进行Base64编码
src = Base64.encodeToString(src.getBytes(), Base64.DEFAULT);
// 补全KEY为16位
byte[] rawKey = toMakeKey(key, KEY_LENGTH, DEFAULT_VALUE).getBytes();
// 获取加密后的字节数组
byte[] result = getBytes(rawKey, src.getBytes("utf-8"), Cipher.ENCRYPT_MODE);
// 对加密后的字节数组进行Base64编码
result = Base64.encode(result, Base64.DEFAULT);
// 返回字符串
return new String(result, Charset.defaultCharset());
}
/**
* 解密
* @param key 密钥
* @param encrypted 待解密文本
* @return 返回解密后的数据
* @throws Exception
*/
public static String decrypt(String key, String encrypted) throws Exception {
// 补全KEY为16位
byte[] rawKey = toMakeKey(key, KEY_LENGTH, DEFAULT_VALUE).getBytes();
// 获取加密后的二进制字节数组
byte[] enc = encrypted.getBytes(Charset.defaultCharset());
// 对二进制数组进行Base64解码
enc = Base64.decode(enc, Base64.DEFAULT);
// 获取解密后的二进制字节数组
byte[] result = getBytes(rawKey, enc, Cipher.DECRYPT_MODE);
// 对解密后的二进制数组进行Base64解码
result = Base64.decode(result, Base64.DEFAULT);
// 返回字符串
return new String(result, "utf-8");
}
/**
* 密钥key ,默认补的数字,补全16位数,以保证安全补全至少16位长度,android和ios对接通过
* @param key 密钥key
* @param length 密钥应有的长度
* @param text 默认补的文本
* @return 密钥
*/
private static String toMakeKey(String key, int length, String text) {
// 获取密钥长度
int strLen = key.length();
// 判断长度是否小于应有的长度
if (strLen < length) {
// 补全位数
StringBuilder builder = new StringBuilder();
// 将key添加至builder中
builder.append(key);
// 遍历添加默认文本
for (int i = 0; i < length - strLen; i++) {
builder.append(text);
}
// 赋值
key = builder.toString();
}
return key;
}
/**
* 加解密过程
* 1. 通过密钥得到一个密钥专用的对象SecretKeySpec
* 2. Cipher 加密算法,加密模式和填充方式三部分或指定加密算 (可以只用写算法然后用默认的其他方式)Cipher.getInstance("AES");
* @param key 二进制密钥数组
* @param src 加解密的源二进制数据
* @param mode 模式,加密为:Cipher.ENCRYPT_MODE;解密为:Cipher.DECRYPT_MODE
* @return 加解密后的二进制数组
* @throws NoSuchAlgorithmException 无效算法
* @throws NoSuchPaddingException 无效填充
* @throws InvalidKeyException 无效KEY
* @throws InvalidAlgorithmParameterException 无效密钥
* @throws IllegalBlockSizeException 非法块字节
* @throws BadPaddingException 坏数据
*
*/
private static byte[] getBytes(byte[] key, byte[] src, int mode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
// 密钥规格
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
// 密钥实例
Cipher cipher = Cipher.getInstance("AES");
// 初始化密钥模式
cipher.init(mode, secretKeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
// 加密数据
return cipher.doFinal(src);
}
/ * update 2020.09.24
*
* 记录一个 Cipher 的小bug
* AES 默认是 :AES/ECB/PKCS5Padding
*
* 在下面的代码中:
* cipher.init(mode, secretKeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
* 等效于
* cipher.init(mode, secretKeySpec, new IvParameterSpec("0000000000000000".getBytes()));
*
* 也就是说在 cipher 初始化时传入了 长度不为0向量
*
* 当采用
* Cipher.getInstance("AES");
* 初始化时,虽然等效 ECB,但是 init 时传入 向量不会报错。
* 但是直接声明时
* Cipher.getInstance("AES/ECB/PKCS5Padding");
* 会报错:要求向量长度为0
*
* 因此下面的代码,正确的应该在初始化时不要传入向量
* cipher.init(mode, secretKeySpec);
*/