AES加解密[支持中文](java、ptyhon、js)

一、简介

AES的介绍此处就不过多介绍了,网上很多,主要介绍本文中三种语言实现形式互相加解密的注意事项:

  1. js的加解密方法返回的都为16进制字符的字符串,所以在java工具类中找16进制字符串相关的加解密方法,然后再通过16进制字符和正常字符串转换方法进行转换;

  1. python的加解密方法返回的是正常字符串,所以在java工具类中找直接进行字符串加解密的方法即可。

二、代码实现

js

npm install crypto-js
/**
 * *@2020-07-20
 * *@describe 全局插件
 */

import CryptoJS from "crypto-js"; //AES加密

// const key = CryptoJS.enc.Utf8.parse("1234123412ABCDEF"); //十六位十六进制数作为密钥

const key = CryptoJS.enc.Utf8.parse("1fc7bf7da7a870e50b0f0fae5e17976b"); //十六位十六进制数作为密钥

const iv = CryptoJS.enc.Utf8.parse("[email protected]"); //十六位十六进制数作为密钥偏移量

const utils = {
  //加密方法
  $encrypt(word) {
    let srcs = CryptoJS.enc.Utf8.parse(word);
    let encrypted = CryptoJS.AES.encrypt(srcs, key, {
      iv: iv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7,
    });
    return encrypted.ciphertext.toString().toUpperCase();
  },
  //解密方法
  $decrypt(word) {
    let encryptedHexStr = CryptoJS.enc.Hex.parse(word);
    let srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr);
    let decrypt = CryptoJS.AES.decrypt(srcs, key, {
      iv: iv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7,
    });
    let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
    return decryptedStr.toString();
  },};
export default utils;

java

package com.fp.epower.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;


import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import static com.alibaba.fastjson.util.IOUtils.DIGITS;

/**
 * @ClassName AESUtils
 * @Description AES(对称加密)工具类
 * @Author amx
 * @Date 2022/1/12 9:05 下午
 **/
@Slf4j
public class AESUtils {


    /**
     * 算法/模式/补码方式
     */
    private final static  String ALGORITHM = "AES/CBC/PKCS5Padding";

    /**
     * 偏移量  must be 16 bytes long
     * 必须是16位
     */
    private final static String IV = "[email protected]";

    /**
     * 秘钥 必须是16、24、32位16进制数字符串  不可随便修改 修改必须通知前端
     */
    private final static String key = "1fc7bf7da7a870e50b0f0fae5e17976b";



    /**
     * 随机生成秘钥
     */
    public static void getKey() {
        try {
//            String seed = "fpwis";
//            SecureRandom rand = new SecureRandom(seed.getBytes());
            KeyGenerator kg = KeyGenerator.getInstance("AES");
            kg.init(128);
            //要生成多少位,只需要修改这里即可128, 192或256
            SecretKey sk = kg.generateKey();
            byte[] b = sk.getEncoded();
            String s = byteToHexString(b);
            System.out.println(new String(s));
            System.out.println(s);
            System.out.println("十六进制密钥长度为"+s.length());
            System.out.println("二进制密钥的长度为"+s.length()*4);
        }catch (Exception e) {
            log.error("随机生成秘钥异常",e);
            e.printStackTrace();
        }
    }


    /**
     * 使用指定的字符串生成秘钥
     */
    public static void getKeyByPass() {
        //生成秘钥
        String password="testkey";
        try {
            SecureRandom rand = new SecureRandom();
            KeyGenerator kg = KeyGenerator.getInstance("AES");
            // kg.init(128);//要生成多少位,只需要修改这里即可128, 192或256
            //SecureRandom是生成安全随机数序列,password.getBytes()是种子,只要种子相同,序列就一样,所以生成的秘钥就一样。
            kg.init(128, new SecureRandom(password.getBytes()));
            SecretKey sk = kg.generateKey();
            byte[] b = sk.getEncoded();
            String s = byteToHexString(b);
            System.out.println(s);
            System.out.println("十六进制密钥长度为"+s.length());
            System.out.println("二进制密钥的长度为"+s.length()*4);
        }
        catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            System.out.println("没有此算法。");
        }
    }

    /**
     * byte数组转化为16进制字符串
     * @param bytes
     * @return
     */
    public static String byteToHexString(byte[] bytes) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            String strHex=Integer.toHexString(bytes[i]);
            if(strHex.length() > 3) {
                sb.append(strHex.substring(6));
            } else {
                if(strHex.length() < 2) {
                    sb.append("0" + strHex);
                } else {
                    sb.append(strHex);
                }
            }
        }
        return sb.toString();
    }

    /*****************************************************
     * AES加密
     * @param content 加密内容
     * @param key 加密密码,由字母或数字组成
      此方法使用AES-128-CBC加密模式,key可以为16、24、32位
      加密解密key必须相同,如:abcd1234abcd1234
     * @return 加密密文
     ****************************************************/

    public static String enCode(String content, String key) {
        if (key == null || "".equals(key)) {
            log.info("key为空!");
            return null;
        }
        try {
            byte[] raw = key.getBytes();  //获得密码的字节数组
            SecretKeySpec skey = new SecretKeySpec(raw, "AES"); //根据密码生成AES密钥
            Cipher cipher = Cipher.getInstance(ALGORITHM);  //根据指定算法ALGORITHM自成密码器
            IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
            cipher.init(Cipher.ENCRYPT_MODE, skey,iv); //初始化密码器,第一个参数为加密(ENCRYPT_MODE)或者解密(DECRYPT_MODE)操作,第二个参数为生成的AES密钥
            byte [] byte_content = content.getBytes("utf-8"); //获取加密内容的字节数组(设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
            byte [] encode_content = cipher.doFinal(byte_content); //密码器加密数据
            return Base64.encodeBase64String(encode_content); //将加密后的数据转换为字符串返回
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 加密字符串
     * @param content  待加密内容
     * 此方法使用AES-128-CBC加密模式,key可以为16、24、32位
     * 加密解密key必须相同,如:abcd1234abcd1234
     * @return
     */
    public static String enCodeString(String content) {
        return enCode(content,key);
    }

    /*****************************************************
     * AES解密
     * @param content 加密密文
     * @param key 加密密码,由字母或数字组成
      此方法使用AES-128-CBC加密模式,key可以为16、24、32位
      加密解密key必须相同
     * @return 解密明文
     ****************************************************/

    public static String deCode(String content, String key) {
        if (key == null || "".equals(key)) {
            log.info("key为空!");
            return null;
        }
        try {
            byte[] raw = key.getBytes("UTF-8");  //获得密码的字节数组
            SecretKeySpec skey = new SecretKeySpec(raw, "AES"); //根据密码生成AES密钥
            Cipher cipher = Cipher.getInstance(ALGORITHM);  //根据指定算法ALGORITHM自成密码器
            IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, skey, iv); //初始化密码器,第一个参数为加密(ENCRYPT_MODE)或者解密(DECRYPT_MODE)操作,第二个参数为生成的AES密钥
            byte [] encode_content = Base64.decodeBase64(content); //把密文字符串转回密文字节数组
            byte [] byte_content = cipher.doFinal(encode_content); //密码器解密数据
            return new String(byte_content,"utf-8"); //将解密后的数据转换为字符串返回

        } catch (Exception e) {
            log.error("解码出错:",e);
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 解密加密密文
     * @param content 加密密文
     * 此方法使用AES-128-CBC加密模式,key可以为16、24、32位
     * 加密解密key必须相同
     * @return
     */
    public static String deCodeString(String content) {
        return deCode(content,key);
    }


    /**
     * base64编码格式String转hexString
      *@param base64String
     * @return
     * @throws IOException
     */
    public static final String base64StringToHexString(String base64String) throws IOException {
        BASE64Decoder decoder = new BASE64Decoder();
        byte[] decoded = decoder.decodeBuffer(base64String);
        final StringBuffer hex = new StringBuffer(decoded.length * 2);
        for (int i = 0; i < decoded.length; i++) {
            hex.append(DIGITS[(decoded[i] >>> 4) & 0x0F]);
            hex.append(DIGITS[decoded[i] & 0x0F]);
        }
        return hex.toString();
    }


    /**
     * hexString转base64编码格式String
     * @param hexString
     * @return
     * @throws IOException
     */
    public static final String hexStringToBase64String(String hexString) throws IOException {
        int m = 0, n = 0;
        int byteLen = hexString.length() / 2; // 每两个字符描述一个字节
        byte[] ret = new byte[byteLen];
        for (int i = 0; i < byteLen; i++) {
            m = i * 2 + 1;
            n = m + 1;
            int intVal = Integer.decode("0x" + hexString.substring(i * 2, m) + hexString.substring(m, n));
            ret[i] = Byte.valueOf((byte)intVal);
        }
        BASE64Encoder encoder = new BASE64Encoder();
        String base64String = encoder.encode(ret);
        return base64String;
    }

    /**
     * 前端传输过来的hexString解密
     * @param content
     * @return
     */
    public static String hexStringDeCode(String content) {
        if (key == null || "".equals(key)) {
            log.info("key为空!");
            return null;
        }
        try {
            //现将前端传过来的hexString转为Base64String
            content = hexStringToBase64String(content);

            byte[] raw = key.getBytes("UTF-8");  //获得密码的字节数组
            SecretKeySpec skey = new SecretKeySpec(raw, "AES"); //根据密码生成AES密钥
            Cipher cipher = Cipher.getInstance(ALGORITHM);  //根据指定算法ALGORITHM自成密码器
            IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, skey, iv); //初始化密码器,第一个参数为加密(ENCRYPT_MODE)或者解密(DECRYPT_MODE)操作,第二个参数为生成的AES密钥
            byte [] encode_content = Base64.decodeBase64(content); //把密文字符串转回密文字节数组
            byte [] byte_content = cipher.doFinal(encode_content); //密码器解密数据
            return new String(byte_content,"utf-8"); //将解密后的数据转换为字符串返回

        } catch (Exception e) {
            log.error("解码出错:",e);
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 前端的加密方式
     * @param content
     * @return
     */
    public static String enCodeToHexString(String content) {
        if (key == null || "".equals(key)) {
            log.info("key为空!");
            return null;
        }
        try {
            byte[] raw = key.getBytes();  //获得密码的字节数组
            SecretKeySpec skey = new SecretKeySpec(raw, "AES"); //根据密码生成AES密钥
            Cipher cipher = Cipher.getInstance(ALGORITHM);  //根据指定算法ALGORITHM自成密码器
            IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
            cipher.init(Cipher.ENCRYPT_MODE, skey,iv); //初始化密码器,第一个参数为加密(ENCRYPT_MODE)或者解密(DECRYPT_MODE)操作,第二个参数为生成的AES密钥
            byte [] byte_content = content.getBytes("utf-8"); //获取加密内容的字节数组(设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
            byte [] encode_content = cipher.doFinal(byte_content); //密码器加密数据
            return base64StringToHexString(Base64.encodeBase64String(encode_content)); //将加密后的数据转换为字符串返回
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) throws IOException {
//        getKey();
        getKeyByPass();
//        String content = "123456";
//        String enResult = enCode(content, key);
//        System.out.println(base64StringToHexString(enResult));
//        System.out.println(base64StringToHexString(enResult));
//        System.out.println(enResult);
//        String deResult = deCode(enResult, key);
//        System.out.println(deResult);
//        System.out.println(hexStringDeCode("c0abc82d1bed9b88aa9e8bbea2f5c8f7"));
//        System.out.println(deCode("wKvILRvtm4iqnou+ovXI9w==",key));
        //测试hexString和base64String互转
        System.out.println(base64StringToHexString("r78/r3SzDMRbJdIUlcU3xA=="));
        System.out.println(hexStringToBase64String("AFBF3FAF74B30CC45B25D21495C537C4"));
//         String password = "123456";
//         String hexCodePassword = enCodeToHexString(password);
//         String decodePassword = hexStringDeCode(hexCodePassword);
//        System.out.println(decodePassword);

        // fp_ocr 加解密测试
//        System.out.println(enCodeString("机组按月度晚高峰期间(16:15-21:00)"));
//        System.out.println(deCodeString(enCodeString("机组按月度晚高峰期间(16:15-21:00))));
//        System.out.println(deCodeString("s0B2xLqS29c5j50YRQld1D/QovvI9BoTKYfc93u+JPSINiizKp8kqYdMy0CzrlQ2a8moAZ3Yc+yDGWMtY+Sls5LFRFWbFc/1yJhx9gpEJsEdEtfV3fYQEAjPZoY5O6soIIMDjJB16RH3QPszI4kMSk1tODyLBdguBr4JS03qT2eA2vAzuwcIgOjir046Hsbg8pUHbnk//7R5zwxNO36qqqbOcj4/wTTkB8Vph3ju8FbgwzDdxD9aH1tnBh3dzUcKr4QhMQYNUEU2EPaKP/OwObVQfzAX2//cKO1g/EaBdX/0C3LtOTyxJkKsz9LoH8TSlsLazngJudJut9Xow99JXDN2YHNUIa7hDizSReUvV9etlohNUXOUXsxUMPTYKIz1ZnBwOfpIGrGIkFyqbwx8QHa9dzFJlh+1aNTJDCsq9wHgoJOy1AjZBY+b5rq7I/xhGengBiBlZHxS783hH5LqKY2sNBLC+Sa4znMFRHYU0izMsxtKyQn4yTejyE3LbHCEEbDfAhQ1YzYlvGUYgxfQCbcBO6PyBYtmp/N6mZam3CEvHovRXkWLX8qz6dATD6N6Aqh9awAAR7o0NdTgapVOybD1DYXkuOhNAk37rX285vYu9HdMdcxmEZQB0g308HT9FbaZXXF6WDv3/kKZNkG+l3UuWgqPaEjIW/lVSpfq1/5VUP2Vbly/gk3PzEsAuuQJXGRKiMwnzN7gqLiXJ4B9D8zgzAIEaJ1uNOJWC7plrx1jDoKUWEU/rK7mFC0ul1Ao5RIVGv9csVK/8YMcZ306p9x30m6jbUSUC34FBT+PriG2UsDZRtlqdC3VYqrfDB9S7idXxvq4r/ARKzSHUL3HUPFIEGTjBMdaiZeXlhQxvFk="));
    }
}

python

pip install pycrypto==2.6.1 -i https://pypi.douban.com/simple
# author:"amx"
# date:2023/3/14 7:58 下午

from Crypto.Cipher import AES
import base64

"""
此工具类是用于AES加解密(支持中文)   基于CBC模式
此代码最早是从网上找的一个demo,cp下来后发现这个只支持英文数字的加解密,中文的话会提示加密内容不是16字节的整数倍
然后经过简单的优化,目前已经支持中文加解密
"""

# Aes 加解密工具类
# 密钥(key), 密斯偏移量(iv) CBC模式加密


BLOCK_SIZE = 16  # Bytes

# 填充方法
"""
  作用: 主要是将传入的字符字符串(str) 或者字节字符串(bytes) 进行填充让其达到16的倍数  
  如果是字符字符串的话字符个数将会达到16的倍数,但是这样的话如果有中文则字节长度不能保证是16的倍数,进行加密会报错,
  如果是字节字符串的话则会保证字节个数是16的倍数,这样加密才不会有问题
  
  逻辑: 先判断这个字符串长度与16取模缺几个字符或者字节,然后利用chr()生成一个字符,因为是16取模所以得到的肯定是0-15,
  那么chr(0-15)是一个不可打印的字符,  然后将缺的个数和字符相乘拼接在原始字符串后面这样就满足的16的倍数,而且这样有个好处就是解密的时候
  去除填充的时候只要找到最后一个字符,并且将这个字符代表数据个数的尾进行去除即可
  
  注意点: 网上同样的例子不支持中文的原因是因为对字符串填充的时机不对,网上是在加密方法刚接收到str类型的字符串以后进行填充,
  这样如果有中文的话填充完只是字符个数是16的倍数,字节则不一定是16的倍数,则会报错, 正确的做法是将传入待加密的str进行encode('uft-8')
  以后得到b''形式的字节字符串以后进行填充,这样则可以保证绝对是16字节的整数倍。
"""

pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \
                chr(BLOCK_SIZE - len(s) % BLOCK_SIZE).encode('utf8')
# 去除填充的方法
"""
  作用: 去除加密时候为了补足16字节整数倍最后几位添加的字节
  
  逻辑: 不管是java还是本工具类的pad函数,在加密时候会用字节长度和16进行取余,如果余数不为0,假设为n(1-15)则会在最后n位用n作为字节补足,保证加密内容为16整数倍
"""
unpad = lambda s: s[:-ord(s[len(s) - 1:])]

# 偏移量
iv = '[email protected]'

# 偏移量
key = '1fc7bf7da7a870e50b0f0fae5e17976b'


# AES 加密
def AES_Encrypt(data, key=key):
    """

    :param data:  加密字符串
    :param key:   秘钥
    :return:      加密后的字符串
    """

    # 将传入的字符串转为字节字符串 然后进行填充,因为在CBC模式下加密的内容必须是16字节的整数倍
    data = pad(data.encode('utf8'))
    # new一个加解密工具
    cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, iv.encode('utf8'))
    # 加密
    encryptedbytes = cipher.encrypt(data)

    # 加密后得到的是bytes类型的数据,使用Base64进行编码,返回base64的byte字符串
    encodestrs = base64.b64encode(encryptedbytes)

    # 对base64的byte字符串按utf-8进行解码 得到字符串
    enctext = encodestrs.decode('utf8')

    return enctext


# AES 解密
def AES_Decrypt(data, key=key):
    """

    :param data: 需要解密的字符串
    :param key:  秘钥
    :return:     解密后的字符串
    """

    # 对解密字符串进行uft8编码 得到base64字节字符串
    data = data.encode('utf8')

    # 对base64的字节字符串进行解码为 字节字符串
    encodebytes = base64.decodebytes(data)

    cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, iv.encode('utf8'))
    # 解码
    text_decrypted = cipher.decrypt(encodebytes)
    # 去除加密时候补位的内容
    text_decrypted = unpad(text_decrypted)

    # 将字节字符串根据utf8解码为字符串
    text_decrypted = text_decrypted.decode('utf8')
    return text_decrypted


if __name__ == '__main__':
    data = '机组按月度晚高峰期间(16:15-21:00)bc'
    enctext = AES_Encrypt(data)
    # print(enctext)
    print(AES_Decrypt('wKvILRvtm4iqnou+ovXI9w=='))

你可能感兴趣的:(工具插件,java,python,网络安全,python,java,javascript,AES加解密)