zoukankan      html  css  js  c++  java
  • JAVA实现AES加密、解密

    一、什么是AES?

    高级加密标准(英语:Advanced Encryption Standard,缩写:AES),是一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。

    那么为什么原来的DES会被取代呢,,原因就在于其使用56位密钥,比较容易被破解。而AES可以使用128、192、和256位密钥,并且用128位分组加密和解密数据,相对来说安全很多。完善的加密算法在理论上是无法破解的,除非使用穷尽法。使用穷尽法破解密钥长度在128位以上的加密数据是不现实的,仅存在理论上的可能性。统计显示,即使使用目前世界上运算速度最快的计算机,穷尽128位密钥也要花上几十亿年的时间,更不用说去破解采用256位密钥长度的AES算法了。

    目前世界上还有组织在研究如何攻破AES这堵坚厚的墙,但是因为破解时间太长,AES得到保障,但是所用的时间不断缩小。随着计算机计算速度的增快,新算法的出现,AES遭到的攻击只会越来越猛烈,不会停止的。

    AES现在广泛用于金融财务、在线交易、无线通信、数字存储等领域,经受了最严格的考验,但说不定哪天就会步DES的后尘。

    二、AES加密方式简析

    * AES加密是对称加密 128 192 256 分别表示密钥的长度

    * AES的加密方式会将明文拆分成不同的块进行加密,例如一个256 位的数据用128的密钥加密,则分成

        明文1(128位)    明文2(128位)

        加密

        密文1(128位)    密文2(128位)

    填充:

      如果明文不是128位(16字节)的则需要填充,即在明文某个地方补充到16个字节整数倍的长度,加解密时需要采用同样的填充方式,否则无法解密成功,以下是几种填充方式

        ** NoPadding
        不进行填充,但是这里要求明文必须要是16个字节的整数倍,这个可以使用者本身自己去实现填充,除了该种模式以外的其他填充模式,如果已经是16个字节的数据的话,会再填充一个16字节的数据

        ** PKCS5Padding(默认)
        在明文的末尾进行填充,填充的数据是当前和16个字节相差的数量,例如:
          未填充明文
        1,2,3,4,5,6,7,8,9,10,11

          填充明文(缺少五个满足16个字节)
        1,2,3,4,5,6,7,8,9,10,11,5,5,5,5,5

     由于使用PKCS7Padding/PKCS5Padding填充时,最后一个字节肯定为填充数据的长度,所以在解密后可以准确删除填充的数据

        ** ISO10126Padding
        在明文的末尾进行填充,当前和16个字节相差的数量填写在最后,其余字节填充随机数,例如:
          未填充明文
        1,2,3,4,5,6,7,8,9,10,11

          填充明文(缺少五个满足16个字节)
        1,2,3,4,5,6,7,8,9,10,11,c,b,4,1,5

    模式

      模式是需要制定AES对明文进行加密时使用的模式(这里并不涉及具体的加密方法,只是加密步骤上的不同模式,在加解密时同样需要相同的模式,否则无法成功),一共提供了五种模式,模式的基本原理是近似的,但是细节上会有一些变化,如下:

        ** ECB模式(默认)电码本模式 Electronic Codebook Book

        这个模式是默认的,就只是根据密钥的位数,将数据分成不同的块进行加密,加密完成后,再将加密后的数据拼接起来,过程如下:

        明文(64字节) 密钥(16字节)

        明文1(16字节)  明文2(16字节)  明文3(16字节)  明文4(16字节)

        密文1(16字节)  密文2(16字节)  密文3(16字节)  密文4(16字节)

        密文(64字节)

        优点:简单、速度快、可并行
        缺点:如果明文块相同,则生成的密文块也相同,这样会导致安全性降低


        ** CBC模式   密码分组链接模式 Cipher Block Chaining

        为了解决ECB模式的密文块相同的缺点,CBC的模式引入了一个初始向量概念,该向量必须是一个与密钥长度相等的数据,在第一次加密前,会使用初始化向量与第一块数据做异或运算,生成的新数据再进行加密,加密第二块之前,会拿第一块的密文数据与第二块明文进行异或运算后再进行加密,以此类推,解密时也是在解密后,进行异或运算,生成最终的明文。过程如下:

        明文(63字节) 密钥 (16字节) 初始向量iv(16字节)

          明文1(16字节) 明文2(16字节) 明文3(16字节) 明文4+一个0(16字节) 

        异或  +初始向量        +密文1         +密文2         +密文3

          密文1(16字节) 密文2(16字节) 密文3(16字节) 密文4(16字节) 

        密文(64字节)

        这里需要注意如下几点:
        1.向量必须是一个与密钥长度相等的数据
        2.由于在加密前和解密后都会做异或运算,因此我们的明文可以不用补全,不是16个字节的倍数也可以,CBC中会自动用0补全进行异或运算
        3.在解密时是解密后才会再做异或运算,保证数据解密成功
        4.由于自动进行了补全,所以解密出的数据也会在后面补全0,因此获取到数据时,需要将末尾的0去除,或者根据源数据长度来截取解密后的数据

        优点:每次加密密钥不同,加强了安全性

        CBC的方式解决了EBC的缺点,但是也有其缺点:
        1.加密无法并行运算,但是解密可以并行,必须在前一个块加密完成后,才能加密后块,并且也需要填充0在后面,所以并不适合流数据(不适合的原因可能是,需要满足128位的数据之后才能进行加密,这样后面才不会有0的补全)
        2.如果前一个数据加密错误,那么后续的数据都是错的了
        3.两端需要同时约定初始向量iv


        ** CFB模式: 密码反馈模式 Cipher FeedBack
        这个模式只使用了加密方法,原理是用到了一个数值异或运算之后再进行一次异或运算,值不改变的原理。并且在加密的时候,如果数据并不满足一个密钥的字节,那么只做保存,待满足一个密钥的字节后再进行加密 过程如下:

        加密:

        明文(260个字节) iv(128个字节)

        明文1(128个字节)    明文2(128个字节)      明文3(4个字节)

        (iv+key)异或 明文1 (密文1+key)异或 明文1  (密文1+key)异或明文3

        密文1(128个字节)     密文2(128个字节)      密文3(4个字节)


        解密:

        密文(260个字节) iv(128个字节)密钥(128字节)

        密文1(128个字节)      密文2(128个字节)      密文3(4个字节)

        (iv+key)异或密文1   (密文1+key)异或密文2    (密文1+key)异或密文3

        明文1 (128个字节)     明文2  (128个字节)      明文3(4个字节)

        这里需要注意如下几点:
        1.加解密时会返回一个num,这个num表示还需要几个数字,才会使用上一个密文加密,否则一直使用上上一个
        2.加解密时也需要传入字符串的长度
        3.由于解密时使用的都是密文来进行解密,并没有使用上一次解密的明文,因此解密也可以并行
        4.由于CFB模式并不需要补全,或者一个完整的128字节才能加解密,综合第三点,所以适合流数据的传输。
        5.CFB模式不止有CFB128(即与密钥长度一致),还有CFB1 和CFB8 即加解密1或8位后,再调用一次加密器生成新的值,这样可以使加密更安全,但是就会处理更多的运算,CFB1的运算时间是CFB8的八倍 CFB128的128倍
        6.使用CFB128或者CFB8的时候传入的length单位是字节,CFB1是length的单位是位。
        7.使用CFB1和CFB8的时候,num值会始终为0

        优点:解密可同步,可以传入非16字节倍数的数据,适合流数据

        CFB模式当然也有一个缺点,解密的时候可以并行解密,但是加密的时候并不可以并行加密。并且也需要选择iv


        ** OFB模式: 输出反馈模式 Output FeedBack
        该模式与CFB类似,但是是将iv或者上一个iv加密后的数据加密,生成的key与明文做异或运算,解密时采用的是同样的方法,利用了异或运算的对称性来进行加解密,除了这一点,其余与CFB一致

        加密/解密:

        CFB:
        (iv+key)异或 明文1 (密文1+key)异或 明文1       (密文1+key)异或明文3

        OFB
        (iv+key)异或明文1 ((iv+key)+key)异或明文1  (((iv+key)+key)+key)异或明文3

        优点:与CFB一样,方便传输流数据

        缺点:由于依赖上一次的加密结果,所以并不能并行处理,特性是解密步骤完全一致,因此使用方法上不会有区别。


        ** CTR模式: 计算器模式 Counter
        OFB不能并行的原因就在于需要上一次的iv进行加密后的结果,因此在CTR中我们将(iv+key)+key替换成了(iv+1)+key,这样我们就不需要依赖上一次的加密结果了。对比如下:

        OFB
        (iv+key)异或明文1 ((iv+key)+key)异或明文1  (((iv+key)+key)+key)异或明文3

        CTR
        (iv+key)异或明文1 ((iv+1)+key)异或明文1    (((iv+1)+1)+key)异或明文3

        优点:由于加解密可以并行,因此CTR模式的加解密速度也很快

        缺点:iv+1的获取比较负责,需要获取瞬时iv

    三、提供两个示例

    1、java mysql 通用aes加密算法

    通用的aes加密,使用场景,插入数据时,使用java进行加密数据,查询时,通过sql进行解密,不用取出再遍历解密

    注:to_base64只适用mysql5.6之后的,之前的没有这个函数,不适用,可以使用HEX,UNHEX ,当然java要用对应的方法解密

    import javax.crypto.Cipher;
    import javax.crypto.spec.SecretKeySpec;
     
    import sun.misc.BASE64Decoder;
    import sun.misc.BASE64Encoder;
     
    /**
     * java使用AES加密解密 AES-128-ECB加密
     * 与mysql数据库aes加密算法通用
     * 数据库aes加密解密
     * -- 加密
     *    SELECT to_base64(AES_ENCRYPT('www.gowhere.so','jkl;POIU1234++=='));
     *    -- 解密
     *    SELECT AES_DECRYPT(from_base64('Oa1NPBSarXrPH8wqSRhh3g=='),'jkl;POIU1234++==');
     * @author 836508
     *
     */
    public class MyAESUtil {
     
        // 加密
        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("utf-8");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//"算法/模式/补码方式"
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
            byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
            return new BASE64Encoder().encode(encrypted);//此处使用BASE64做转码功能,同时能起到2次加密的作用。
        }
     
        // 解密
        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("utf-8");
                SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
                Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
                cipher.init(Cipher.DECRYPT_MODE, skeySpec);
                byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);//先用base64解密
                try {
                    byte[] original = cipher.doFinal(encrypted1);
                    String originalString = new String(original,"utf-8");
                    return originalString;
                } catch (Exception e) {
                    System.out.println(e.toString());
                    return null;
                }
            } catch (Exception ex) {
                System.out.println(ex.toString());
                return null;
            }
        }
     
        public static void main(String[] args) throws Exception {
            /*
             * 此处使用AES-128-ECB加密模式,key需要为16位。
             */
            String cKey = "jkl;POIU1234++==";
            // 需要加密的字串
            String cSrc = "www.gowhere.so";
            System.out.println(cSrc);
            // 加密
            String enString = MyAESUtil.Encrypt(cSrc, cKey);
            System.out.println("加密后的字串是:" + enString);
     
            // 解密
            String DeString = MyAESUtil.Decrypt(enString, cKey);
            System.out.println("解密后的字串是:" + DeString);
        }
    }

    2、java  AES-128-CBC加密模式

    package com.zhongzhi.utils;
    import javax.crypto.Cipher;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import org.apache.commons.codec.binary.Base64;
    /**
     * @Classname ZzSecurityHelper
     * @Description TODO
     * @Date 2019/6/24 16:50
     * @Created by whd
     */
    public class ZzSecurityHelper {
        /*
         * 加密用的Key 可以用26个字母和数字组成 使用AES-128-CBC加密模式,key需要为16位。
         */
        private static final String key="hj7x89H$yuBI0456";
        private static final String iv ="NIfb&95GUY86Gfgh";
        /**
         * @author miracle.qu
         * @Description AES算法加密明文
         * @param data 明文
         * @param key 密钥,长度16
         * @param iv 偏移量,长度16
         * @return 密文
         */
        public static String encryptAES(String data) throws Exception {
            try {
                Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
                int blockSize = cipher.getBlockSize();
                byte[] dataBytes = data.getBytes();
                int plaintextLength = dataBytes.length;
    
                if (plaintextLength % blockSize != 0) {
                    plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
                }
    
                byte[] plaintext = new byte[plaintextLength];
                System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
    
                SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
                IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());  // CBC模式,需要一个向量iv,可增加加密算法的强度
    
                cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
                byte[] encrypted = cipher.doFinal(plaintext);
    
                return ZzSecurityHelper.encode(encrypted).trim(); // BASE64做转码。
    
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * @author miracle.qu
         * @Description AES算法解密密文
         * @param data 密文
         * @param key 密钥,长度16
         * @param iv 偏移量,长度16
         * @return 明文
         */
        public static String decryptAES(String data) throws Exception {
            try
            {
                byte[] encrypted1 = ZzSecurityHelper.decode(data);//先用base64解密
    
                Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
                SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
                IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
    
                cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
    
                byte[] original = cipher.doFinal(encrypted1);
                String originalString = new String(original);
                return originalString.trim();
            }
            catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        /**
         * 编码
         * @param byteArray
         * @return
         */
        public static String encode(byte[] byteArray) {
            return new String(new Base64().encode(byteArray));
        }
    
        /**
         * 解码
         * @param base64EncodedString
         * @return
         */
        public static byte[] decode(String base64EncodedString) {
            return new Base64().decode(base64EncodedString);
        }
    }

    参考文章:

    https://blog.csdn.net/DamonREN/article/details/87601165

    https://blog.csdn.net/xy371661665/article/details/86423762

    https://blog.csdn.net/TurboAnho/article/details/98611138

    https://my.oschina.net/u/3784129/blog/3066112

  • 相关阅读:
    linux centos7 如何安装mysql
    Json转换 在java中的应用
    最最简单的spring mvc + Maven项目
    windows下 申请免费ssl证书的方法 (letsencrypt)
    PowerDesigner中Table视图同时显示Code和Name
    在linux中 部署 mongo 数据库服务端
    Java保存图片到数据库Blob格式
    MyBatis mapper记录
    vue防止多次点击,重复请求
    金额的单位转换,元转分
  • 原文地址:https://www.cnblogs.com/better-farther-world2099/p/13293291.html
Copyright © 2011-2022 走看看