AES的介绍此处就不过多介绍了,网上很多,主要介绍本文中三种语言实现形式互相加解密的注意事项:
js的加解密方法返回的都为16进制字符的字符串,所以在java工具类中找16进制字符串相关的加解密方法,然后再通过16进制字符和正常字符串转换方法进行转换;
python的加解密方法返回的是正常字符串,所以在java工具类中找直接进行字符串加解密的方法即可。
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;
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="));
}
}
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=='))