diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/AESUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/AESUtils.java new file mode 100644 index 00000000..660e3cba --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/AESUtils.java @@ -0,0 +1,78 @@ +package xiaozhi.common.utils; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +public class AESUtils { + + private static final String ALGORITHM = "AES"; + private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding"; + + /** + * AES加密 + * + * @param key 密钥(16位、24位或32位) + * @param plainText 待加密字符串 + * @return 加密后的Base64字符串 + */ + public static String encrypt(String key, String plainText) { + try { + // 确保密钥长度为16、24或32位 + byte[] keyBytes = padKey(key.getBytes(StandardCharsets.UTF_8)); + SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM); + + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + + byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(encryptedBytes); + } catch (Exception e) { + throw new RuntimeException("AES加密失败", e); + } + } + + /** + * AES解密 + * + * @param key 密钥(16位、24位或32位) + * @param encryptedText 待解密的Base64字符串 + * @return 解密后的字符串 + */ + public static String decrypt(String key, String encryptedText) { + try { + // 确保密钥长度为16、24或32位 + byte[] keyBytes = padKey(key.getBytes(StandardCharsets.UTF_8)); + SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM); + + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + + byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText); + byte[] decryptedBytes = cipher.doFinal(encryptedBytes); + return new String(decryptedBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new RuntimeException("AES解密失败", e); + } + } + + /** + * 填充密钥到指定长度(16、24或32位) + * + * @param keyBytes 原始密钥字节数组 + * @return 填充后的密钥字节数组 + */ + private static byte[] padKey(byte[] keyBytes) { + int keyLength = keyBytes.length; + if (keyLength == 16 || keyLength == 24 || keyLength == 32) { + return keyBytes; + } + + // 如果密钥长度不足,用0填充;如果超过,截取前32位 + byte[] paddedKey = new byte[32]; + System.arraycopy(keyBytes, 0, paddedKey, 0, Math.min(keyLength, 32)); + return paddedKey; + } +} diff --git a/main/manager-api/src/test/java/xiaozhi/common/utils/AESUtilsTest.java b/main/manager-api/src/test/java/xiaozhi/common/utils/AESUtilsTest.java new file mode 100644 index 00000000..c4f37115 --- /dev/null +++ b/main/manager-api/src/test/java/xiaozhi/common/utils/AESUtilsTest.java @@ -0,0 +1,85 @@ +package xiaozhi.common.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class AESUtilsTest { + + @Test + public void testEncryptAndDecrypt() { + String key = "xiaozhi1234567890"; + String plainText = "Hello, 小智!"; + + System.out.println("原始文本: " + plainText); + System.out.println("密钥: " + key); + + // 加密 + String encrypted = AESUtils.encrypt(key, plainText); + System.out.println("加密结果: " + encrypted); + + // 解密 + String decrypted = AESUtils.decrypt(key, encrypted); + System.out.println("解密结果: " + decrypted); + + // 验证 + assertEquals(plainText, decrypted, "加解密结果应该一致"); + System.out.println("加解密一致性: " + plainText.equals(decrypted)); + } + + @Test + public void testDifferentKeyLengths() { + String[] keys = { + "1234567890123456", // 16位 + "123456789012345678901234", // 24位 + "12345678901234567890123456789012", // 32位 + "short", // 短密钥 + "verylongkeythatwillbetruncatedto32bytes" // 长密钥 + }; + + String plainText = "测试文本"; + + for (String key : keys) { + String encrypted = AESUtils.encrypt(key, plainText); + String decrypted = AESUtils.decrypt(key, encrypted); + assertEquals(plainText, decrypted, "密钥长度: " + key.length()); + } + } + + @Test + public void testSpecialCharacters() { + String key = "xiaozhi1234567890"; + String[] testTexts = { + "Hello World", + "你好世界", + "Hello, 小智!", + "特殊字符: !@#$%^&*()", + "数字123和中文混合", + "Emoji: 😀🎉🚀", + "空字符串测试", + "" + }; + + for (String text : testTexts) { + String encrypted = AESUtils.encrypt(key, text); + String decrypted = AESUtils.decrypt(key, encrypted); + assertEquals(text, decrypted, "测试文本: " + text); + } + } + + @Test + public void testCrossLanguageCompatibility() { + // 这些是Python版本生成的加密结果,用于测试跨语言兼容性 + String key = "xiaozhi1234567890"; + String plainText = "Hello, 小智!"; + + // Python版本生成的加密结果(需要运行Python测试后获取) + // String pythonEncrypted = "从Python测试中获取的加密结果"; + // String decrypted = AESUtils.decrypt(key, pythonEncrypted); + // assertEquals(plainText, decrypted, "Java应该能解密Python加密的结果"); + + // 生成Java加密结果供Python测试 + String javaEncrypted = AESUtils.encrypt(key, plainText); + System.out.println("Java加密结果供Python测试: " + javaEncrypted); + } +} \ No newline at end of file diff --git a/main/xiaozhi-server/docker-compose_all.yml b/main/xiaozhi-server/docker-compose_all.yml index c8672fb5..4707a449 100644 --- a/main/xiaozhi-server/docker-compose_all.yml +++ b/main/xiaozhi-server/docker-compose_all.yml @@ -52,7 +52,7 @@ services: volumes: # 配置文件目录 - ./uploadfile:/uploadfile - + # 数据库模块 xiaozhi-esp32-server-db: image: mysql:latest container_name: xiaozhi-esp32-server-db @@ -73,6 +73,7 @@ services: - MYSQL_ROOT_PASSWORD=123456 - MYSQL_DATABASE=xiaozhi_esp32_server - MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" + # redis模块 xiaozhi-esp32-server-redis: image: redis expose: