feat: app password use AES GCM #935

This commit is contained in:
tjq 2024-08-10 23:26:49 +08:00
parent eb4d7ab8eb
commit 29e0b2deb0
3 changed files with 53 additions and 32 deletions

View File

@ -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
// 密钥长度128192 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);
} }
} }

View File

@ -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";

View File

@ -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) {