mirror of
https://github.com/PowerJob/PowerJob.git
synced 2025-07-17 00:00:04 +08:00
feat: app password use AES GCM #935
This commit is contained in:
parent
eb4d7ab8eb
commit
29e0b2deb0
@ -1,10 +1,10 @@
|
|||||||
package tech.powerjob.server.common.utils;
|
package tech.powerjob.server.common.utils;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
import tech.powerjob.common.utils.DigestUtils;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.KeyGenerator;
|
import javax.crypto.spec.GCMParameterSpec;
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@ -12,60 +12,81 @@ import java.util.Base64;
|
|||||||
|
|
||||||
public class AESUtil {
|
public class AESUtil {
|
||||||
|
|
||||||
// 加密算法的名称
|
|
||||||
private static final String ALGORITHM = "AES";
|
private static final String ALGORITHM = "AES";
|
||||||
// 加密/解密模式和填充方式
|
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
|
||||||
private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
|
private static final int KEY_SIZE = 256; // AES 256-bit
|
||||||
// 密钥长度(128、192 或 256 位)
|
private static final int GCM_NONCE_LENGTH = 12; // GCM nonce length (12 bytes)
|
||||||
private static final int KEY_SIZE = 128;
|
private static final int GCM_TAG_LENGTH = 16; // GCM authentication tag length (16 bytes)
|
||||||
|
|
||||||
|
// SecureRandom 实例,用于生成 nonce
|
||||||
|
private static final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成密钥
|
* 生成密钥
|
||||||
*
|
*
|
||||||
* @param key 密钥的种子,可以是任意字符串
|
* @param key 传入的密钥字符串,必须是 32 字节(256 位)长度
|
||||||
* @return 生成的密钥
|
* @return SecretKeySpec 实例
|
||||||
* @throws Exception 异常
|
|
||||||
*/
|
*/
|
||||||
private static SecretKeySpec generateKey(String key) throws Exception {
|
private static SecretKeySpec getKey(String key) {
|
||||||
KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
|
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
|
||||||
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
|
// 不足 32 字节,则使用 MD5 转为 32 位
|
||||||
secureRandom.setSeed(key.getBytes());
|
if (keyBytes.length != KEY_SIZE / 8) {
|
||||||
keyGen.init(KEY_SIZE, secureRandom);
|
keyBytes = DigestUtils.md5(key).getBytes(StandardCharsets.UTF_8);
|
||||||
SecretKey secretKey = keyGen.generateKey();
|
}
|
||||||
return new SecretKeySpec(secretKey.getEncoded(), ALGORITHM);
|
return new SecretKeySpec(keyBytes, ALGORITHM);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加密
|
* 加密
|
||||||
*
|
*
|
||||||
* @param data 要加密的数据
|
* @param data 要加密的数据
|
||||||
* @param key 密钥
|
* @param key 加密密钥
|
||||||
* @return 加密后的数据(Base64 编码)
|
* @return 加密后的数据(Base64 编码),包含 nonce
|
||||||
* @throws Exception 异常
|
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static String encrypt(String data, String key) {
|
public static String encrypt(String data, String key) {
|
||||||
|
byte[] nonce = new byte[GCM_NONCE_LENGTH];
|
||||||
|
secureRandom.nextBytes(nonce); // 生成随机的 nonce
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||||
SecretKeySpec secretKey = generateKey(key);
|
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, nonce);
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
cipher.init(Cipher.ENCRYPT_MODE, getKey(key), gcmParameterSpec);
|
||||||
|
|
||||||
byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||||
return Base64.getEncoder().encodeToString(encryptedData);
|
|
||||||
|
// 将 nonce 和密文连接在一起,然后进行 Base64 编码
|
||||||
|
byte[] combinedData = new byte[nonce.length + encryptedData.length];
|
||||||
|
System.arraycopy(nonce, 0, combinedData, 0, nonce.length);
|
||||||
|
System.arraycopy(encryptedData, 0, combinedData, nonce.length, encryptedData.length);
|
||||||
|
|
||||||
|
return Base64.getEncoder().encodeToString(combinedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解密
|
* 解密
|
||||||
*
|
*
|
||||||
* @param encryptedData 要解密的数据(Base64 编码)
|
* @param encryptedData 要解密的数据(Base64 编码),包含 nonce
|
||||||
* @param key 密钥
|
* @param key 解密密钥
|
||||||
* @return 解密后的数据
|
* @return 解密后的数据
|
||||||
* @throws Exception 异常
|
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static String decrypt(String encryptedData, String key) {
|
public static String decrypt(String encryptedData, String key) {
|
||||||
|
byte[] combinedData = Base64.getDecoder().decode(encryptedData);
|
||||||
|
|
||||||
|
// 提取 nonce
|
||||||
|
byte[] nonce = new byte[GCM_NONCE_LENGTH];
|
||||||
|
System.arraycopy(combinedData, 0, nonce, 0, nonce.length);
|
||||||
|
|
||||||
|
// 提取实际的加密数据
|
||||||
|
byte[] encryptedText = new byte[combinedData.length - nonce.length];
|
||||||
|
System.arraycopy(combinedData, nonce.length, encryptedText, 0, encryptedText.length);
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||||
SecretKeySpec secretKey = generateKey(key);
|
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, nonce);
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKey);
|
cipher.init(Cipher.DECRYPT_MODE, getKey(key), gcmParameterSpec);
|
||||||
byte[] decryptedData = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
|
|
||||||
|
byte[] decryptedData = cipher.doFinal(encryptedText);
|
||||||
return new String(decryptedData, StandardCharsets.UTF_8);
|
return new String(decryptedData, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class AESUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
void testAes() throws Exception {
|
void testAes() throws Exception {
|
||||||
|
|
||||||
String sk = "cmxzjzty";
|
String sk = "ChinaNo.1_ChinaNo.1_ChinaNo.1";
|
||||||
|
|
||||||
String txt = "kyksjdfh";
|
String txt = "kyksjdfh";
|
||||||
|
|
||||||
|
@ -37,9 +37,9 @@ public class AppInfoServiceImpl implements AppInfoService {
|
|||||||
|
|
||||||
private final AppInfoRepository appInfoRepository;
|
private final AppInfoRepository appInfoRepository;
|
||||||
|
|
||||||
private static final String ENCRYPT_KEY = "ChinaNo.1";
|
private static final String ENCRYPT_KEY = "ChinaNo.1_ChinaNo.1_ChinaNo.1AAA";
|
||||||
|
|
||||||
private static final String ENCRYPT_PWD_PREFIX = "aes:";
|
private static final String ENCRYPT_PWD_PREFIX = "sys_encrypt_aes:";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<AppInfoDO> findByAppName(String appName) {
|
public Optional<AppInfoDO> findByAppName(String appName) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user