AES加解

1.简述

  高级加密标准(Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。

  :使用jdk自带的jce.jar包实现

  加密标准

    AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。密钥的长度可以使用128位、192位、256位。密钥的长度不同,加密轮数也不同。

AES

密钥长度(32位bit)

分组长度(32位bit)

加密轮数

JDK

AES-128

4(16个字符)

4

10

支持

AES-192

6(24个字符)

4

12

不支持

AES-256

8(32个字符)

4

14

不支持

2.AES加密模式

  JCE支持模式:

  • 电码本模式(Electronic Codebook Book,缩写:ECB):最基本的加密模式,是将整个明文分成若干段相同的小段,然后对每一小段进行加密。
  • 密码分组链接模式(Cipher Block Chaining,缩写:CBC):先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
  • 计算器模式(Counter,缩写:CTR)计算器模式不常见,在CTR模式中, 有一个自增的算子,这个算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。这种加密方式简单快速,安全可靠,而且可以并行加密,但是在计算器不能维持很长的情况下,密钥只能使用一次
  • 密码反馈模式(Cipher FeedBack ,缩写:CFB):能够将块密文转换为流密文。
  • 输出反馈模式(Output FeedBack,缩写:OFB):先用块加密器生成密钥流,然后再将密钥流与明文流异或得到密文流,解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以加密和解密的流程是完全一样的。

  JCE5种模式优缺点

    1. ECB

      优点:简单、可并行计算、误差不传递。

      缺点:不能隐藏明文的模式、可能对明文进行主动攻击。

    2. CBC

      优点:不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。

      缺点:需要初始化向量IV、不利于并行计算、误差传递。

    3. CTR

      优点同明文不同密、每个块单独运算,适合并行运算。

      缺点:主动攻击(改明文,后续内容不影响,只要误差不传递该缺点就存在)。

    4. CFB

      优点隐藏了明文模式、分组密码转化为流模式、可以及时加密传送小于分组的数据。

      缺点:不利于并行计算、误差传送:一个明文单元损坏影响多个单元。

    5. OFB

      优点:同明文不同密文,分组密钥转换为流密码。

      缺点串行运算不利并行、误差传送:一个明文单元损坏影响多个单元。

3.AES填充方式

  JCE中AES支持三种填充:NoPadding,PKCS5Padding,ISO10126Padding。

  注:不带模式和填充来获取AES算法的时候,其默认使用ECB/PKCS5Padding

填充

16字节加密后数据长度

不满16字节加密后长度

NoPadding

16

不支持

PKCS5Padding

32

支持

ISO10126Padding

32

支持

4.AES加密实现

(1)NoPadding补码方式实现

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**测试类
 */
public class Test{
    private static final String AESKEY = "WEBTEST123456789";//密钥
    public static void main(String[] args) {
        try {
            String enString = "asd#测试#13#";//有了#号区分,这样做是为了方便过滤不是16的倍数时填充的0
            System.out.println("加密前的字串是:" + enString);
            enString = AesUtils.Encrypt(enString, AESKEY);
            System.out.println("加密后的字串是:" + enString);
            enString = AesUtils.Decrypt(enString, AESKEY);
            System.out.println("解密后的字串是:" + enString);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**AES加密工具类
 */
class AesUtils {
    // 加密
    public static String Encrypt(String sSrc, String sKey) throws Exception {
        if (sKey == null) {
            System.out.print("Key为空null");
            return null;
        }
        // 判断Key是否为16位
        if (sKey.length() != 16) {
            System.out.print("Key长度不是16位");
            return null;
        }
        //注意这一步,不是16的倍数,则后面填充0,不填充的话会报错
        while (sSrc.getBytes().length % 16 > 0) {
            sSrc += "0";
        }
        byte[] raw = sKey.getBytes();
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        //注意,不是CBC模式,去除IvParameterSpec
        IvParameterSpec iv = new IvParameterSpec("1234567812345678".getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
        byte[] encrypted = cipher.doFinal(sSrc.getBytes());
        return byte2hex(encrypted);
    }

    // 解密
    public static String Decrypt(String sSrc, String sKey) throws Exception {
        try {
            // 判断Key是否正确
            if (sKey == null) {
                System.out.print("Key为空null");
                return null;
            }
            // 判断Key是否为16位
            if (sKey.length() != 16) {
                System.out.print("Key长度不是16位");
                return null;
            }
            byte[] raw = sKey.getBytes("ASCII");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            IvParameterSpec iv = new IvParameterSpec("1234567812345678".getBytes());
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] encrypted1 = hex2byte(sSrc);
            try {
                byte[] original = cipher.doFinal(encrypted1);
                String originalString = new String(original);
                return originalString;
            } catch (Exception e) {
                System.out.println(e.toString());
                return null;
            }
        } catch (Exception ex) {
            System.out.println(ex.toString());
            return null;
        }
    }
    
    /**自定义二次加密,将二进制转为16进制
     */
    public static String byte2hex(byte[] b) {
        String hs = "";
        String stmp = "";
        for (int n = 0; n < b.length; n++) {
            stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
            if (stmp.length() == 1) {
                hs = hs + "0" + stmp;
            } else {
                hs = hs + stmp;
            }
        }
        return hs.toUpperCase();
    }
    /**二次加密内容解密,将16进制转换为二进制
     */
    public static byte[] hex2byte(String strhex) {
        if (strhex == null) {
            return null;
        }
        int l = strhex.length();
        if (l % 2 == 1) {
            return null;
        }
        byte[] b = new byte[l / 2];
        for (int i = 0; i != l / 2; i++) {
            b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2), 16);
        }
        return b;
    }
}
View Code

  :使用NoPadding补码方式,需要加密、解密的数据是16字节的倍数,否则会报错。

(2)其他补码方式实现

/**测试类
 */
public class Test{
    private static final String AESKEY = "WEBTEST123456789";//密钥
    public static void main(String[] args) {
        try {
            String enString = "asd#测试#13#";//有了#号区分,这样做是为了方便过滤不是16的倍数时填充的0
            System.out.println("加密前的字串是:" + enString);
            enString = AesUtils.Encrypt(enString, AESKEY);
            System.out.println("加密后的字串是:" + enString);
            enString = AesUtils.Decrypt(enString, AESKEY);
            System.out.println("解密后的字串是:" + enString);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**AES加密工具类
 */
class AesUtils {
    // 加密
    public static String Encrypt(String sSrc, String sKey) throws Exception {
        if (sKey == null) {
            System.out.print("Key为空null");
            return null;
        }
        // 判断Key是否为16位
        if (sKey.length() != 16) {
            System.out.print("Key长度不是16位");
            return null;
        }
        byte[] raw = sKey.getBytes();
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        //注意,不是CBC模式,去除IvParameterSpec
        IvParameterSpec iv = new IvParameterSpec("1234567812345678".getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
        byte[] encrypted = cipher.doFinal(sSrc.getBytes());
        return byte2hex(encrypted);
    }

    // 解密
    public static String Decrypt(String sSrc, String sKey) throws Exception {
        try {
            // 判断Key是否正确
            if (sKey == null) {
                System.out.print("Key为空null");
                return null;
            }
            // 判断Key是否为16位
            if (sKey.length() != 16) {
                System.out.print("Key长度不是16位");
                return null;
            }
            byte[] raw = sKey.getBytes("ASCII");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec iv = new IvParameterSpec("1234567812345678".getBytes());
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] encrypted1 = hex2byte(sSrc);
            try {
                byte[] original = cipher.doFinal(encrypted1);
                String originalString = new String(original);
                return originalString;
            } catch (Exception e) {
                System.out.println(e.toString());
                return null;
            }
        } catch (Exception ex) {
            System.out.println(ex.toString());
            return null;
        }
    }
    
    /**自定义二次加密,将二进制转为16进制
     */
    public static String byte2hex(byte[] b) {
        String hs = "";
        String stmp = "";
        for (int n = 0; n < b.length; n++) {
            stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
            if (stmp.length() == 1) {
                hs = hs + "0" + stmp;
            } else {
                hs = hs + stmp;
            }
        }
        return hs.toUpperCase();
    }
    /**二次加密内容解密,将16进制转换为二进制
     */
    public static byte[] hex2byte(String strhex) {
        if (strhex == null) {
            return null;
        }
        int l = strhex.length();
        if (l % 2 == 1) {
            return null;
        }
        byte[] b = new byte[l / 2];
        for (int i = 0; i != l / 2; i++) {
            b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2), 16);
        }
        return b;
    }
}
View Code

  注:使用PKCS5Padding、ISO10126Padding补码方式,不是16字节的倍数也能进行加密、解密。

posted on 2020-09-29 09:23  码农记录  阅读(436)  评论(0)    收藏  举报

导航