目录
1.说明
2.ECB模式(base64)
3.CBC模式
4.总结
AES是常见的对称加密算法,加密和解密使用相同的密钥,流程如下:
主要概念如下:
①明文
②密钥
用来加密明文的密码,在对称加密算法中,加密与解密的密钥是相同的。密钥为接收方与发送方协商产生,但不可以直接在网络上传输,否则会导致密钥泄漏,通常是通过加密算法(如非对称加密或者md5加密等)加密密钥,然后再通过网络传输给对方,或者直接面对面商量密钥。密钥是绝对不可以泄漏的,否则会被攻击者还原密文,窃取机密数据。
③加密函数
将明文,密钥及IV变量作为参数,生成加密后的密文,有的加密模式没有IV变量
④解密函数
将密文,密钥及IV变量作为参数,生成解密后的明文,有的加密模式没有IV变量
⑤密文
2.加密模式的组成
①密钥长度
分为AES128,AES192,AES256。
AES128:要求密钥长度为16个字节,分为四行四列的矩阵。
AES192:要求密钥长度为24个字节,分为四行六列的矩阵。
AES256:要求密钥长度为32个字节,分为四行八列的矩阵。
②加密模式
ECB,CBC,CFB,OFB,CTR,CCM,GCM,XTS
③填充模式
AES加密是以16个字节为一组进行分组加密,要求明文的长度一定是16个字节的整数倍,如果不够16个字节的倍数,需要按照填充模式进行填充。填充模式如下:
NONE:不填充,要求明文长度必须是16个字节的整数倍。
PKCS7:缺多少就补充多少个字节数量。
ZERO:缺多少就补充多少个字节的0。
ANSI923:最后一个字节填充缺少的数量,其他字节填充0。
ISO7816_4:填充的第一个字节为16进制的0x80,其他字节填充0。
ISO10126:最后一个字节填充缺少的数量,其他字节填充随机数。
注意:当明文长度已经是16个字节的整数倍时,当填充模式为NONE以外的模式时,也会进行填充,填充16个16。
加密执行流程:
(1)前端加密及解密
①引入依赖
npm install crypto-js
②加密及解密的工具类
import CryptoJS from 'crypto-js'
// ECB模式
export function Encrypt(word,key) {
let srcs = CryptoJS.enc.Utf8.parse(word);
let keyInfo = CryptoJS.enc.Utf8.parse(key);
let encrypted = CryptoJS.AES.encrypt(srcs, keyInfo, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
})
//返回base64格式的加密结果
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
}
export function Decrypt(word,key) {
let keyInfo = CryptoJS.enc.Utf8.parse(key);
let base64 = CryptoJS.enc.Base64.parse(word);
let srcs = CryptoJS.enc.Base64.stringify(base64);
const decrypt = CryptoJS.AES.decrypt(srcs, keyInfo, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
return decryptedStr;
}
密钥长度必须是16个字节,24个字节或者32个字节,如果不是,可以进行加密,但是不能进行解密。
前端填充方式:
/**
* Padding namespace.
*/
export namespace pad {
/**
* PKCS #5/7 padding strategy.
*/
const Pkcs7: Padding;
/**
* ANSI X.923 padding strategy.
*/
const AnsiX923: Padding;
/**
* ISO 10126 padding strategy.
*/
const Iso10126: Padding;
/**
* ISO/IEC 9797-1 Padding Method 2.
*/
const Iso97971: Padding;
/**
* Zero padding strategy.
*/
const ZeroPadding: Padding;
/**
* A noop padding strategy.
*/
const NoPadding: Padding;
}
(2)后端加密及解密
①引入依赖
commons-codec
commons-codec
1.14
②后端加密及解密工具类
package com.example.utils;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
/**
* ECB加解密工具类
*/
public class AesTool {
private static String ALGO = "AES";
private static byte[] keyValue = "TD2g45EfXqGnBLaXpzPZ6A==".getBytes();
/**
* description: 加密
* param: [Data]
* return: java.lang.String
* author: zhangcj
* date: 2021/12/29
*/
public static String encrypt(String data) {
try {
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGO);
c.init(1, key);
byte[] encVal = c.doFinal(data.getBytes());
String encryptedValue = Base64.encodeBase64String(encVal);
return encryptedValue;
} catch (Exception var5) {
throw new RuntimeException(var5);
}
}
// public static String decrypt(String encryptedData, String keyValue) {
// try {
// Key key = generateKey(keyValue);
// Cipher c = Cipher.getInstance(ALGO);
// c.init(2, key);
// byte[] decordedValue = Base64.decodeBase64(encryptedData);
// byte[] decValue = c.doFinal(decordedValue);
// String decryptedValue = new String(decValue);
// return decryptedValue;
// } catch (Exception var6) {
// throw new RuntimeException(var6);
// }
// }
public static String decrypt(String content,String password) throws Exception {
byte[] contentNew = Base64.decodeBase64(content);
SecretKeySpec skeySpec = new SecretKeySpec(password.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // "算法/模式/补码方式"
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
return new String(cipher.doFinal(contentNew));
}
private static Key generateKey() throws Exception {
Key key = new SecretKeySpec(keyValue, ALGO);
return key;
}
private static Key generateKey(String key) throws Exception {
return new SecretKeySpec(key.getBytes(), ALGO);
}
}
③调用方式
String decrypt = AesTool.decrypt("D5DT/9wwi89s0ny9VYwMTA==", "5bzMeuzs2XQfG8QFyx1hGg==");
System.out.println(decrypt);
(3)说明
ECB模式加密,将明文及密钥通过CryptoJS.enc.Utf8.parse进行转换,将UTF8编码的字符串转换为字节数组,然后加密模式及填充方式,最后生成密文。
密文分为两种格式,一种是base64,另一种是hex。
前端加密之后,后端进行解密时,首先根据前端的密文格式,将密文转换成字节数组,再设置和前端一致的模式和补充方式进行解密。
ECB模式加密安全性较低,但性能很高,相同的明文段加密后的密文一致。
前端加密需要的密钥可以由后端生成,生成方式如下:
// 方式1
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey secretKey = keyGen.generateKey();
byte[] keyBytes = secretKey.getEncoded();
String key = Base64.getEncoder().encodeToString(keyBytes);
System.out.println(key);
// 方式2
SecureRandom secureRandom = new SecureRandom();
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128, secureRandom);
byte[] key2 = keyGenerator.generateKey().getEncoded();
String base64Key = java.util.Base64.getEncoder().encodeToString(key2);
System.out.println(base64Key);
主要分为两步:生成一个随机的AES密钥;将密钥已base64编码的形式保存或传输。
(1)前端加密及解密
①引入依赖
npm install crypto-js
②加密及解密工具类
import CryptoJS from 'crypto-js'
// CBC模式
export function Encrypt1(word,key,iv) {
let srcs = CryptoJS.enc.Utf8.parse(word);
let keyInfo = CryptoJS.enc.Utf8.parse(key);
let ivInfo = CryptoJS.enc.Utf8.parse(iv);
let encrypted = CryptoJS.AES.encrypt(srcs, keyInfo, {
iv: ivInfo ,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
})
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
}
export function Decrypt1(word,key,iv) {
let keyInfo = CryptoJS.enc.Utf8.parse(key);
let ivInfo = CryptoJS.enc.Utf8.parse(iv);
let base64 = CryptoJS.enc.Base64.parse(word);
let srcs = CryptoJS.enc.Base64.stringify(base64);
const decrypt = CryptoJS.AES.decrypt(srcs, keyInfo, {
iv: ivInfo ,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
return decryptedStr;
}
(2)后端加密及解密
①引入依赖
commons-codec
commons-codec
1.14
②加密及解密工具类
package com.example.utils;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* @Author linaibo
* @Date 2024/2/7 15:53
* CBC加解密工具
* @Version 1.0
*/
public class AexCbcTool {
// public static String decryptAES(String content,String key, String ivInfo) throws Exception {
// SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("ASCII"), "AES");
// Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// IvParameterSpec iv = new IvParameterSpec(ivInfo.getBytes());
// cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
// byte[] encrypted1 = Base64.decodeBase64(content);//先用bAES64解密
// return new String(cipher.doFinal(encrypted1));
// }
/**
* 解密
*
* @param enc 加密内容
* @param uniqueKey 唯一key
* @return
*/
public static String decrypt(String enc, String uniqueKey, String iv) throws Exception {
byte[] key = uniqueKey.getBytes();
SecretKey secretKey = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes("utf-8"));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
byte[] encrypted1 = Base64.decodeBase64(enc);//先用bAES64解密
byte[] plainBytes = cipher.doFinal(encrypted1);
return new String(plainBytes, "UTF-8");
}
/**
* 加密
*
* @param src
* @param uniqueKey
* @return
*/
public static String encrypt(String src, String uniqueKey, String iv) throws Exception {
byte[] key = uniqueKey.getBytes();
SecretKey secretKey = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes("utf-8"));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
byte[] cipherBytes = cipher.doFinal(src.getBytes("UTF-8"));
String encryptedValue = Base64.encodeBase64String(cipherBytes);
return encryptedValue;
}
}
在项目中使用时,比如对密码的加密,需要先调用后端接口获取加密密钥,根据获取的加密密钥在前端对密码进行加密,将加密后的信息发送给后端,在后端进行解密,解密之后再进行加密,将加密后的密码和表中的密码进行比较,一致则登录成功
https://blog.51cto.com/u_16175526/7323980
AES算法的CBC和ECB两种工作模式_aes iv需要保密吗-CSDN博客
AES加解密模式_aes_cbc_encrypt-CSDN博客
AES解密报错Invalid AES key length: xx bytes与Given final block not properly padded的解决方法-CSDN博客