From 29e0b2deb0f07105c7c9cadfd3acef94e01c433e Mon Sep 17 00:00:00 2001 From: tjq Date: Sat, 10 Aug 2024 23:26:49 +0800 Subject: [PATCH] feat: app password use AES GCM #935 --- .../powerjob/server/common/utils/AESUtil.java | 79 ++++++++++++------- .../server/common/utils/AESUtilTest.java | 2 +- .../core/service/impl/AppInfoServiceImpl.java | 4 +- 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/powerjob-server/powerjob-server-common/src/main/java/tech/powerjob/server/common/utils/AESUtil.java b/powerjob-server/powerjob-server-common/src/main/java/tech/powerjob/server/common/utils/AESUtil.java index 0c3c2153..fba942e0 100644 --- a/powerjob-server/powerjob-server-common/src/main/java/tech/powerjob/server/common/utils/AESUtil.java +++ b/powerjob-server/powerjob-server-common/src/main/java/tech/powerjob/server/common/utils/AESUtil.java @@ -1,10 +1,10 @@ package tech.powerjob.server.common.utils; import lombok.SneakyThrows; +import tech.powerjob.common.utils.DigestUtils; import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; @@ -12,60 +12,81 @@ import java.util.Base64; public class AESUtil { - // 加密算法的名称 + private static final String ALGORITHM = "AES"; - // 加密/解密模式和填充方式 - private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding"; - // 密钥长度(128、192 或 256 位) - private static final int KEY_SIZE = 128; + private static final String TRANSFORMATION = "AES/GCM/NoPadding"; + private static final int KEY_SIZE = 256; // AES 256-bit + private static final int GCM_NONCE_LENGTH = 12; // GCM nonce length (12 bytes) + 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 密钥的种子,可以是任意字符串 - * @return 生成的密钥 - * @throws Exception 异常 + * @param key 传入的密钥字符串,必须是 32 字节(256 位)长度 + * @return SecretKeySpec 实例 */ - private static SecretKeySpec generateKey(String key) throws Exception { - KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM); - SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); - secureRandom.setSeed(key.getBytes()); - keyGen.init(KEY_SIZE, secureRandom); - SecretKey secretKey = keyGen.generateKey(); - return new SecretKeySpec(secretKey.getEncoded(), ALGORITHM); + private static SecretKeySpec getKey(String key) { + byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); + // 不足 32 字节,则使用 MD5 转为 32 位 + if (keyBytes.length != KEY_SIZE / 8) { + keyBytes = DigestUtils.md5(key).getBytes(StandardCharsets.UTF_8); + } + return new SecretKeySpec(keyBytes, ALGORITHM); } /** * 加密 * * @param data 要加密的数据 - * @param key 密钥 - * @return 加密后的数据(Base64 编码) - * @throws Exception 异常 + * @param key 加密密钥 + * @return 加密后的数据(Base64 编码),包含 nonce */ @SneakyThrows public static String encrypt(String data, String key) { + byte[] nonce = new byte[GCM_NONCE_LENGTH]; + secureRandom.nextBytes(nonce); // 生成随机的 nonce + Cipher cipher = Cipher.getInstance(TRANSFORMATION); - SecretKeySpec secretKey = generateKey(key); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, nonce); + cipher.init(Cipher.ENCRYPT_MODE, getKey(key), gcmParameterSpec); + 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 key 密钥 + * @param encryptedData 要解密的数据(Base64 编码),包含 nonce + * @param key 解密密钥 * @return 解密后的数据 - * @throws Exception 异常 */ @SneakyThrows 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); - SecretKeySpec secretKey = generateKey(key); - cipher.init(Cipher.DECRYPT_MODE, secretKey); - byte[] decryptedData = cipher.doFinal(Base64.getDecoder().decode(encryptedData)); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, nonce); + cipher.init(Cipher.DECRYPT_MODE, getKey(key), gcmParameterSpec); + + byte[] decryptedData = cipher.doFinal(encryptedText); return new String(decryptedData, StandardCharsets.UTF_8); } } diff --git a/powerjob-server/powerjob-server-common/src/test/java/tech/powerjob/server/common/utils/AESUtilTest.java b/powerjob-server/powerjob-server-common/src/test/java/tech/powerjob/server/common/utils/AESUtilTest.java index 7632347c..84c924cf 100644 --- a/powerjob-server/powerjob-server-common/src/test/java/tech/powerjob/server/common/utils/AESUtilTest.java +++ b/powerjob-server/powerjob-server-common/src/test/java/tech/powerjob/server/common/utils/AESUtilTest.java @@ -13,7 +13,7 @@ class AESUtilTest { @Test void testAes() throws Exception { - String sk = "cmxzjzty"; + String sk = "ChinaNo.1_ChinaNo.1_ChinaNo.1"; String txt = "kyksjdfh"; diff --git a/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/service/impl/AppInfoServiceImpl.java b/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/service/impl/AppInfoServiceImpl.java index 480ad6f9..facc8839 100644 --- a/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/service/impl/AppInfoServiceImpl.java +++ b/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/service/impl/AppInfoServiceImpl.java @@ -37,9 +37,9 @@ public class AppInfoServiceImpl implements AppInfoService { 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 public Optional findByAppName(String appName) {