关于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);
      */