feat:配置加密机制
This commit is contained in:
parent
3040ed443b
commit
3a9a76c100
24
README.md
24
README.md
@ -62,6 +62,30 @@ chmod +x /home/tms/scripts/tms.sh
|
||||
- `apply_standard_db.sh` 依赖预置环境变量,例如 `DB_USER`、`DB_PASSWORD`,不要直接写入 `application.yml`。
|
||||
- 运行目录结构、配置项说明、文件上传 `fileId` 流程和故障排查,请查看上面的完整部署手册。
|
||||
|
||||
## 配置敏感信息加密
|
||||
|
||||
配置文件中的敏感值可以写成 `ENC(...)`,应用启动早期会自动解密后再交给 Spring 绑定:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
password: ENC(v1:<iv>:<ciphertext>)
|
||||
```
|
||||
|
||||
当前实现使用 `AES-256-GCM`,主密钥按当前交付要求临时硬编码在代码中。生成密文:
|
||||
|
||||
```bash
|
||||
java -jar tms-framework.jar --tms.crypto.encrypt
|
||||
```
|
||||
|
||||
命令会从标准输入读取一行明文并输出 `ENC(...)`。解密校验:
|
||||
|
||||
```bash
|
||||
java -jar tms-framework.jar --tms.crypto.decrypt
|
||||
```
|
||||
|
||||
生产加固时应把硬编码密钥替换为环境变量、独立密钥文件或密码机/KMS 托管密钥。
|
||||
|
||||
运行时建议:
|
||||
- 本地构建和测试统一使用 JDK 17,与项目和 CI 运行时保持一致。
|
||||
|
||||
|
||||
@ -37,6 +37,8 @@ tms:
|
||||
cpu-model-default: ""
|
||||
memory-total-default: 16GB
|
||||
disk-total-default: 256G
|
||||
runtime-status:
|
||||
disk-usage-path: /home/tms
|
||||
file:
|
||||
storage:
|
||||
upload-base-dir: /home/tms/uploads
|
||||
@ -115,7 +117,7 @@ tms:
|
||||
product-types:
|
||||
- ENTERPRISE
|
||||
- INDIRECT
|
||||
# TMS 库备份排除表。默认跳过角色、授权、会话、审计和资源任务表,避免恢复时覆盖新机器安全状态或带回旧 RUNNING 任务。
|
||||
# TMS 库备份排除表。默认跳过角色、授权、会话和资源任务表,避免恢复时覆盖新机器安全登录状态或带回旧 RUNNING 任务。
|
||||
mysqldump-timeout-seconds: 300
|
||||
restore-db-timeout-seconds: 300
|
||||
tms-db-excluded-tables:
|
||||
@ -125,7 +127,6 @@ tms:
|
||||
- tms_auth_session
|
||||
- tms_auth_challenge
|
||||
- tms_auth_audit_log
|
||||
- tms_operation_audit_log
|
||||
- tms_resource_backup_task
|
||||
- tms_resource_restore_task
|
||||
allowed-restore-roots:
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.cisd.tms;
|
||||
|
||||
import com.cisd.tms.common.crypto.config.ConfigCryptoCli;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@ -11,6 +12,13 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
public class TmsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (ConfigCryptoCli.shouldHandle(args)) {
|
||||
int exitCode = ConfigCryptoCli.run(args, System.in, System.out, System.err);
|
||||
if (exitCode != 0) {
|
||||
System.exit(exitCode);
|
||||
}
|
||||
return;
|
||||
}
|
||||
SpringApplication.run(TmsApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
package com.cisd.tms.common.crypto.config;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class ConfigCryptoCli {
|
||||
|
||||
private static final String ENCRYPT_ARG = "--tms.crypto.encrypt";
|
||||
private static final String DECRYPT_ARG = "--tms.crypto.decrypt";
|
||||
private static final String VALUE_ARG_PREFIX = "--tms.crypto.value=";
|
||||
|
||||
private ConfigCryptoCli() {
|
||||
}
|
||||
|
||||
public static boolean shouldHandle(String[] args) {
|
||||
return contains(args, ENCRYPT_ARG) || contains(args, DECRYPT_ARG);
|
||||
}
|
||||
|
||||
public static int run(String[] args, InputStream in, PrintStream out, PrintStream err) {
|
||||
boolean encrypt = contains(args, ENCRYPT_ARG);
|
||||
boolean decrypt = contains(args, DECRYPT_ARG);
|
||||
if (encrypt == decrypt) {
|
||||
err.println("Usage: --tms.crypto.encrypt|--tms.crypto.decrypt [--tms.crypto.value=<value>]");
|
||||
return 2;
|
||||
}
|
||||
try {
|
||||
String input = valueFromArgs(args);
|
||||
if (input == null) {
|
||||
input = readLine(in);
|
||||
}
|
||||
ConfigTextEncryptor encryptor = ConfigTextEncryptor.withDefaultHardcodedKey();
|
||||
out.println(encrypt ? encryptor.encrypt(input) : encryptor.decryptIfNecessary(input));
|
||||
return 0;
|
||||
} catch (Exception ex) {
|
||||
err.println(ex.getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean contains(String[] args, String expected) {
|
||||
return Arrays.stream(args == null ? new String[0] : args).anyMatch(expected::equals);
|
||||
}
|
||||
|
||||
private static String valueFromArgs(String[] args) {
|
||||
if (args == null) {
|
||||
return null;
|
||||
}
|
||||
for (String arg : args) {
|
||||
if (arg != null && arg.startsWith(VALUE_ARG_PREFIX)) {
|
||||
return arg.substring(VALUE_ARG_PREFIX.length());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String readLine(InputStream in) throws IOException {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||
String line = reader.readLine();
|
||||
if (line == null) {
|
||||
throw new IllegalArgumentException("请输入待处理文本");
|
||||
}
|
||||
return line;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.cisd.tms.common.crypto.config;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
public class ConfigDecryptingEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
|
||||
|
||||
private static final String DECRYPTED_PROPERTY_SOURCE_NAME = "tmsConfigDecryptedProperties";
|
||||
|
||||
private final ConfigTextEncryptor encryptor;
|
||||
|
||||
public ConfigDecryptingEnvironmentPostProcessor() {
|
||||
this(ConfigTextEncryptor.withDefaultHardcodedKey());
|
||||
}
|
||||
|
||||
ConfigDecryptingEnvironmentPostProcessor(ConfigTextEncryptor encryptor) {
|
||||
this.encryptor = encryptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
Map<String, Object> decryptedValues = new LinkedHashMap<>();
|
||||
for (PropertySource<?> propertySource : environment.getPropertySources()) {
|
||||
if (!(propertySource instanceof EnumerablePropertySource<?> enumerablePropertySource)) {
|
||||
continue;
|
||||
}
|
||||
for (String propertyName : enumerablePropertySource.getPropertyNames()) {
|
||||
Object rawValue = enumerablePropertySource.getProperty(propertyName);
|
||||
if (rawValue instanceof String stringValue && encryptor.isEncrypted(stringValue)) {
|
||||
decryptedValues.putIfAbsent(propertyName, encryptor.decryptIfNecessary(stringValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!decryptedValues.isEmpty()) {
|
||||
environment.getPropertySources().addFirst(new MapPropertySource(DECRYPTED_PROPERTY_SOURCE_NAME, decryptedValues));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
package com.cisd.tms.common.crypto.config;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class ConfigTextEncryptor {
|
||||
|
||||
private static final String ENCRYPTED_PREFIX = "ENC(";
|
||||
private static final String ENCRYPTED_SUFFIX = ")";
|
||||
private static final String FORMAT_VERSION = "v1";
|
||||
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
|
||||
private static final String KEY_ALGORITHM = "AES";
|
||||
private static final int IV_LENGTH_BYTES = 12;
|
||||
private static final int GCM_TAG_LENGTH_BITS = 128;
|
||||
private static final Base64.Encoder ENCODER = Base64.getUrlEncoder().withoutPadding();
|
||||
private static final Base64.Decoder DECODER = Base64.getUrlDecoder();
|
||||
|
||||
// Temporary fixed key required by the current delivery request. Replace with an external key source later.
|
||||
private static final String HARDCODED_KEY_BASE64 = "LMJI3dMjle5U9sMH/tlX1/1mOuVAYsg+cPsxGppFua0=";
|
||||
|
||||
private final SecretKeySpec keySpec;
|
||||
private final SecureRandom secureRandom;
|
||||
|
||||
public static ConfigTextEncryptor withDefaultHardcodedKey() {
|
||||
return new ConfigTextEncryptor(Base64.getDecoder().decode(HARDCODED_KEY_BASE64), new SecureRandom());
|
||||
}
|
||||
|
||||
ConfigTextEncryptor(byte[] keyBytes, SecureRandom secureRandom) {
|
||||
if (keyBytes == null || keyBytes.length != 32) {
|
||||
throw new IllegalArgumentException("配置加密密钥必须是32字节AES-256密钥");
|
||||
}
|
||||
this.keySpec = new SecretKeySpec(keyBytes, KEY_ALGORITHM);
|
||||
this.secureRandom = secureRandom;
|
||||
}
|
||||
|
||||
public String encrypt(String plainText) {
|
||||
if (plainText == null) {
|
||||
throw new IllegalArgumentException("待加密明文不能为空");
|
||||
}
|
||||
byte[] iv = new byte[IV_LENGTH_BYTES];
|
||||
secureRandom.nextBytes(iv);
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, iv));
|
||||
byte[] ciphertext = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
|
||||
return ENCRYPTED_PREFIX
|
||||
+ FORMAT_VERSION
|
||||
+ ":"
|
||||
+ ENCODER.encodeToString(iv)
|
||||
+ ":"
|
||||
+ ENCODER.encodeToString(ciphertext)
|
||||
+ ENCRYPTED_SUFFIX;
|
||||
} catch (GeneralSecurityException ex) {
|
||||
throw new IllegalStateException("配置加密失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public String decryptIfNecessary(String value) {
|
||||
if (value == null || !isEncrypted(value)) {
|
||||
return value;
|
||||
}
|
||||
return decrypt(value);
|
||||
}
|
||||
|
||||
public boolean isEncrypted(String value) {
|
||||
return value != null && value.startsWith(ENCRYPTED_PREFIX) && value.endsWith(ENCRYPTED_SUFFIX);
|
||||
}
|
||||
|
||||
private String decrypt(String encryptedValue) {
|
||||
String payload = encryptedValue.substring(ENCRYPTED_PREFIX.length(), encryptedValue.length() - ENCRYPTED_SUFFIX.length());
|
||||
String[] parts = payload.split(":", -1);
|
||||
if (parts.length != 3 || !FORMAT_VERSION.equals(parts[0])) {
|
||||
throw new IllegalArgumentException("配置密文格式无效");
|
||||
}
|
||||
try {
|
||||
byte[] iv = DECODER.decode(parts[1]);
|
||||
byte[] ciphertext = DECODER.decode(parts[2]);
|
||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, iv));
|
||||
return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
|
||||
} catch (IllegalArgumentException | GeneralSecurityException ex) {
|
||||
throw new IllegalArgumentException("配置密文解密失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
src/main/resources/META-INF/spring.factories
Normal file
2
src/main/resources/META-INF/spring.factories
Normal file
@ -0,0 +1,2 @@
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
com.cisd.tms.common.crypto.config.ConfigDecryptingEnvironmentPostProcessor
|
||||
@ -1,8 +1,8 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://127.0.0.1:4000/tms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: Sunyard123
|
||||
username: ENC(v1:XBsGZZ5ugvSBy0HZ:nvihsBsr4b9HhHYGTDbVlqy7kT0)
|
||||
password: ENC(v1:K_VO3NW0dq59UjJh:KcngqSlmeiCP0VXGwkB031xOMjE4ktoz6vo)
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
mybatis-plus:
|
||||
|
||||
@ -182,6 +182,9 @@ tms:
|
||||
memory-total-default: ${TMS_DEVICE_PROFILE_MEMORY_TOTAL_DEFAULT:16GB}
|
||||
# 磁盘总量默认展示值;命令读取失败或为空时回退。
|
||||
disk-total-default: ${TMS_DEVICE_PROFILE_DISK_TOTAL_DEFAULT:256G}
|
||||
runtime-status:
|
||||
# 运行状态磁盘已用量探测路径;总量使用 lsblk 统计 OS 可见物理磁盘容量。
|
||||
disk-usage-path: ${TMS_DEVICE_RUNTIME_STATUS_DISK_USAGE_PATH:/home/tms}
|
||||
upgrade:
|
||||
# 升级包解压和脚本执行暂存目录。
|
||||
staging-root-dir: ${TMS_UPGRADE_STAGING_ROOT_DIR:/home/tmp/tms-upgrade-staging}
|
||||
@ -300,17 +303,22 @@ tms:
|
||||
mysql-path: ${TMS_BACKUP_MYSQL_PATH:mysql}
|
||||
# 数据库恢复脚本路径;apply 脚本在 RESTORE_DATABASES 阶段调用。
|
||||
restore-db-script-path: ${TMS_BACKUP_RESTORE_DB_SCRIPT_PATH:/home/tms/bin/resource-restore/restore-db.sh}
|
||||
# 单个数据库恢复最大等待时间(秒);超时会标记恢复失败并尝试重新启动服务。
|
||||
restore-db-timeout-seconds: ${TMS_BACKUP_RESTORE_DB_TIMEOUT_SECONDS:300}
|
||||
# MQ replay 脚本路径;apply 脚本在 MQ_REPLAY 阶段调用。
|
||||
mq-replay-script-path: ${TMS_BACKUP_MQ_REPLAY_SCRIPT_PATH:/home/tms/bin/resource-restore/replay-mq.sh}
|
||||
# 备份 TMS 库名;为空时默认从 spring.datasource.url 中解析。
|
||||
tms-database-name: ${TMS_BACKUP_TMS_DATABASE_NAME:}
|
||||
# TMS 库备份排除表。默认不备份角色、授权、会话和审计表,避免恢复时覆盖新机器安全状态。
|
||||
# TMS 库备份排除表。默认不备份角色、授权、会话和资源备份/恢复任务表,避免恢复时覆盖新机器安全登录状态或带回旧 RUNNING 任务。
|
||||
tms-db-excluded-tables:
|
||||
- ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_ROLE_ACCOUNT:tms_role_account}
|
||||
- ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_ROLE_UKEY:tms_role_ukey_binding}
|
||||
- ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_AUTH_FULL:tms_auth_full_account}
|
||||
- ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_AUTH_SESSION:tms_auth_session}
|
||||
- ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_AUTH_CHALLENGE:tms_auth_challenge}
|
||||
- ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_AUTH_AUDIT:tms_auth_audit_log}
|
||||
- ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_RESOURCE_BACKUP_TASK:tms_resource_backup_task}
|
||||
- ${TMS_BACKUP_TMS_DB_EXCLUDED_TABLE_RESOURCE_RESTORE_TASK:tms_resource_restore_task}
|
||||
# 备份/恢复标准收发器业务库名;第一版默认使用 CMEP。
|
||||
cmep-database-name: ${TMS_BACKUP_CMEP_DATABASE_NAME:CMEP}
|
||||
# 恢复完成后的最小健康检查地址。
|
||||
@ -341,7 +349,7 @@ tms:
|
||||
debug-session-token: ${TMS_SECURITY_INTERNAL_AUTH_DEBUG_SESSION_TOKEN:DEBUG-BYPASS}
|
||||
replay:
|
||||
# 是否启用防重放校验;本地 Postman/联调可临时设为 false,生产环境应保持 true。
|
||||
enabled: ${TMS_SECURITY_REPLAY_ENABLED:false}
|
||||
enabled: ${TMS_SECURITY_REPLAY_ENABLED:true}
|
||||
openapi:
|
||||
# 外部签名服务接口允许的时间戳偏差(秒),防重放。
|
||||
timestamp-skew-seconds: 300
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
package com.cisd.tms.common.crypto.config;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ConfigCryptoCliTest {
|
||||
|
||||
@Test
|
||||
void encryptCommandReadsPlainTextFromStdinAndPrintsEncValue() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
int exitCode = ConfigCryptoCli.run(
|
||||
new String[] {"--tms.crypto.encrypt"},
|
||||
new ByteArrayInputStream("cli-secret\n".getBytes(StandardCharsets.UTF_8)),
|
||||
new PrintStream(out, true, StandardCharsets.UTF_8),
|
||||
new PrintStream(new ByteArrayOutputStream(), true, StandardCharsets.UTF_8)
|
||||
);
|
||||
|
||||
String encrypted = out.toString(StandardCharsets.UTF_8).trim();
|
||||
|
||||
Assertions.assertEquals(0, exitCode);
|
||||
Assertions.assertTrue(encrypted.startsWith("ENC(v1:"));
|
||||
Assertions.assertEquals(
|
||||
"cli-secret",
|
||||
ConfigTextEncryptor.withDefaultHardcodedKey().decryptIfNecessary(encrypted)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void decryptCommandReadsEncryptedTextFromStdinAndPrintsPlainText() {
|
||||
String encrypted = ConfigTextEncryptor.withDefaultHardcodedKey().encrypt("cli-secret");
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
int exitCode = ConfigCryptoCli.run(
|
||||
new String[] {"--tms.crypto.decrypt"},
|
||||
new ByteArrayInputStream((encrypted + "\n").getBytes(StandardCharsets.UTF_8)),
|
||||
new PrintStream(out, true, StandardCharsets.UTF_8),
|
||||
new PrintStream(new ByteArrayOutputStream(), true, StandardCharsets.UTF_8)
|
||||
);
|
||||
|
||||
Assertions.assertEquals(0, exitCode);
|
||||
Assertions.assertEquals("cli-secret", out.toString(StandardCharsets.UTF_8).trim());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.cisd.tms.common.crypto.config;
|
||||
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
|
||||
class ConfigDecryptingEnvironmentPostProcessorTest {
|
||||
|
||||
@Test
|
||||
void decryptsEncryptedEnumerablePropertiesBeforeBinding() {
|
||||
ConfigTextEncryptor encryptor = ConfigTextEncryptor.withDefaultHardcodedKey();
|
||||
String encryptedPassword = encryptor.encrypt("db-password");
|
||||
StandardEnvironment environment = new StandardEnvironment();
|
||||
environment.getPropertySources().addFirst(new MapPropertySource("test", Map.of(
|
||||
"spring.datasource.password", encryptedPassword,
|
||||
"server.port", "8080"
|
||||
)));
|
||||
|
||||
new ConfigDecryptingEnvironmentPostProcessor()
|
||||
.postProcessEnvironment(environment, new SpringApplication(Object.class));
|
||||
|
||||
Assertions.assertEquals("db-password", environment.getProperty("spring.datasource.password"));
|
||||
Assertions.assertEquals("8080", environment.getProperty("server.port"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.cisd.tms.common.crypto.config;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ConfigTextEncryptorTest {
|
||||
|
||||
@Test
|
||||
void encryptWrapsValueAsEncAndDecryptRestoresPlainText() {
|
||||
ConfigTextEncryptor encryptor = ConfigTextEncryptor.withDefaultHardcodedKey();
|
||||
|
||||
String encrypted = encryptor.encrypt("Sunyard123");
|
||||
|
||||
Assertions.assertTrue(encrypted.startsWith("ENC(v1:"));
|
||||
Assertions.assertEquals("Sunyard123", encryptor.decryptIfNecessary(encrypted));
|
||||
}
|
||||
|
||||
@Test
|
||||
void encryptUsesRandomIvForEachValue() {
|
||||
ConfigTextEncryptor encryptor = ConfigTextEncryptor.withDefaultHardcodedKey();
|
||||
|
||||
String first = encryptor.encrypt("same-secret");
|
||||
String second = encryptor.encrypt("same-secret");
|
||||
|
||||
Assertions.assertNotEquals(first, second);
|
||||
Assertions.assertEquals("same-secret", encryptor.decryptIfNecessary(first));
|
||||
Assertions.assertEquals("same-secret", encryptor.decryptIfNecessary(second));
|
||||
}
|
||||
|
||||
@Test
|
||||
void decryptLeavesPlainValuesUnchanged() {
|
||||
ConfigTextEncryptor encryptor = ConfigTextEncryptor.withDefaultHardcodedKey();
|
||||
|
||||
Assertions.assertEquals("plain", encryptor.decryptIfNecessary("plain"));
|
||||
Assertions.assertNull(encryptor.decryptIfNecessary(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void decryptRejectsTamperedCiphertext() {
|
||||
ConfigTextEncryptor encryptor = ConfigTextEncryptor.withDefaultHardcodedKey();
|
||||
String encrypted = encryptor.encrypt("secret");
|
||||
String tampered = encrypted.substring(0, encrypted.length() - 2) + "AA)";
|
||||
|
||||
IllegalArgumentException exception = Assertions.assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> encryptor.decryptIfNecessary(tampered)
|
||||
);
|
||||
Assertions.assertTrue(exception.getMessage().contains("解密失败"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user