package com.yami.trading.security.common.util;
|
|
import com.yami.trading.common.domain.Result;
|
import com.yami.trading.security.common.enums.CryptoCurrencyEnum;
|
|
import javax.crypto.*;
|
import javax.crypto.spec.*;
|
import java.io.File;
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.security.*;
|
import java.util.*;
|
import java.nio.charset.StandardCharsets;
|
import java.nio.file.*;
|
import java.util.logging.Logger;
|
|
public class LocalKeyStorageAESUtil {
|
private static final Logger logger = Logger.getLogger(LocalKeyStorageAESUtil.class.getName());
|
|
// 加密算法参数
|
private static final String ALGORITHM = "AES";
|
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
|
private static final int TAG_LENGTH = 128; // bits
|
private static final int IV_LENGTH = 12; // bytes
|
private static final int KEY_LENGTH = 256; // bits
|
private static final int SALT_LENGTH = 16; // bytes
|
private static final int ITERATIONS = 65536;
|
|
// 安全配置 - 生产环境中应从外部配置读取
|
private static final Path KEY_FILE = Paths.get("/www/aes/aes_key.dat");
|
private static final String FILE = "/www/aes/";
|
private static final String FILENAME = "_address.dat";
|
|
/**
|
* 加密文本并保存到文件,加密前校验密码与派生密钥是否匹配
|
* @param password 用户提供的密码
|
* @param text 要加密的文本
|
* @param symbolName 用于生成文件名
|
* @return 操作结果
|
* @throws Exception 加密过程中发生的任何异常
|
*/
|
public static Result encryptAndStore(String password, String text, String symbolName) throws Exception {
|
try {
|
logger.info("开始加密过程...");
|
|
// 1. 检查密钥文件是否存在
|
if (!Files.exists(KEY_FILE)) {
|
return Result.failed("密钥文件丢失,请联系管理员");
|
}
|
|
//重置密钥 客户更新完密码之后要重新打包提交
|
reset(password);
|
|
// 2. 加载现有密钥材料
|
KeyMaterial existingKey = loadKeyMaterial();
|
|
// 4. 正常加密流程(非重置情况)
|
if (!validatePassword(password, existingKey.salt, existingKey.secretKey.getEncoded())) {
|
logger.info("修改地址密码验证失败!");
|
return Result.failed("密码验证失败");
|
}
|
|
// 5. 使用现有密钥加密新数据
|
byte[] iv = generateRandomBytes(IV_LENGTH);
|
String ciphertext = encryptData(text, existingKey.secretKey, iv);
|
String encryptedData = Base64.getEncoder().encodeToString(iv) + ":" + ciphertext;
|
|
// 6. 保存加密数据
|
Path encryptedDataFile = getEncryptedDataFile(FILE, symbolName, FILENAME);
|
Files.write(encryptedDataFile, encryptedData.getBytes(StandardCharsets.UTF_8));
|
|
logger.info("加密完成并已保存到文件");
|
return Result.succeed();
|
} catch (Exception e) {
|
logger.severe("加密过程中发生错误: " + e.getMessage());
|
throw e;
|
}
|
}
|
|
/**
|
* 验证密码与密钥是否匹配
|
* @param password 用户提供的密码
|
* @param salt 盐值
|
* @param storedKeyBytes 存储的密钥字节
|
* @return 验证是否通过
|
*/
|
private static boolean validatePassword(String password, byte[] salt, byte[] storedKeyBytes) throws Exception {
|
// 使用相同参数重新派生密钥
|
SecretKey derivedKey = deriveKey(password.toCharArray(), salt);
|
|
// 比较派生出的密钥与存储的密钥是否一致
|
return Arrays.equals(derivedKey.getEncoded(), storedKeyBytes);
|
}
|
|
/**
|
* 获取加密数据文件路径
|
*/
|
/**
|
* 获取加密数据文件路径
|
*/
|
private static Path getEncryptedDataFile(String basePath, String symbolName, String filename) {
|
// 确保basePath以分隔符结尾
|
if (!basePath.endsWith(File.separator)) {
|
basePath += File.separator;
|
}
|
|
// 创建目录结构
|
File dir = new File(basePath);
|
if (!dir.exists()) {
|
dir.mkdirs();
|
}
|
|
// 构建完整文件路径 (basePath + symbolName + filename)
|
String filePath = basePath + symbolName + filename;
|
return Paths.get(filePath);
|
}
|
|
/**
|
* 从文件加载并解密数据
|
* @return 解密后的原始文本
|
* @throws Exception 解密过程中发生的任何异常
|
*/
|
public static String loadAndDecrypt(String symbolName) throws Exception {
|
try {
|
logger.info("开始解密过程...");
|
Path encryptedDataFile = getEncryptedDataFile(FILE, symbolName, FILENAME);
|
// 检查文件是否存在
|
if (!Files.exists(KEY_FILE) || !Files.exists(encryptedDataFile)) {
|
throw new IllegalStateException("密钥文件或加密数据文件不存在");
|
}
|
|
// 加载密钥材料
|
KeyMaterial keyMaterial = loadKeyMaterial();
|
|
// 加载加密数据
|
String encryptedData = new String(Files.readAllBytes(encryptedDataFile), StandardCharsets.UTF_8);
|
String[] parts = encryptedData.split(":");
|
if (parts.length != 2) {
|
throw new SecurityException("加密数据格式无效");
|
}
|
|
byte[] iv = Base64.getDecoder().decode(parts[0]);
|
String ciphertext = parts[1];
|
|
// 解密数据
|
return decryptData(ciphertext, keyMaterial.secretKey, iv);
|
} catch (Exception e) {
|
logger.severe("解密过程中发生错误: " + e.getMessage());
|
throw new SecurityException("解密过程中发生错误",e);
|
}
|
}
|
|
private static class KeyMaterial {
|
byte[] salt;
|
SecretKey secretKey;
|
|
KeyMaterial(byte[] salt, SecretKey secretKey) {
|
this.salt = salt;
|
this.secretKey = secretKey;
|
}
|
}
|
|
/**
|
* 保存密钥材料到文件
|
*/
|
private static void saveKeyMaterial(byte[] salt, byte[] keyBytes) throws Exception {
|
// 确保密钥文件目录存在
|
File keyDir = KEY_FILE.getParent().toFile();
|
if (!keyDir.exists()) {
|
keyDir.mkdirs();
|
}
|
|
String content = Base64.getEncoder().encodeToString(salt) + ":" +
|
Base64.getEncoder().encodeToString(keyBytes);
|
Files.write(KEY_FILE, content.getBytes(StandardCharsets.UTF_8));
|
}
|
|
/**
|
* 从文件加载密钥材料
|
*/
|
private static KeyMaterial loadKeyMaterial() throws Exception {
|
String content = new String(Files.readAllBytes(KEY_FILE), StandardCharsets.UTF_8);
|
String[] parts = content.split(":");
|
if (parts.length != 2) {
|
throw new SecurityException("密钥文件格式无效");
|
}
|
|
byte[] salt = Base64.getDecoder().decode(parts[0]);
|
byte[] keyBytes = Base64.getDecoder().decode(parts[1]);
|
SecretKey secretKey = new SecretKeySpec(keyBytes, ALGORITHM);
|
|
return new KeyMaterial(salt, secretKey);
|
}
|
|
/**
|
* 派生加密密钥
|
*/
|
private static SecretKey deriveKey(char[] password, byte[] salt) throws Exception {
|
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
|
byte[] keyBytes = factory.generateSecret(spec).getEncoded();
|
return new SecretKeySpec(keyBytes, ALGORITHM);
|
}
|
|
/**
|
* 加密数据
|
*/
|
private static String encryptData(String plaintext, SecretKey key, byte[] iv) throws Exception {
|
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH, iv);
|
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
|
}
|
|
/**
|
* 解密数据
|
*/
|
private static String decryptData(String ciphertext, SecretKey key, byte[] iv) throws Exception {
|
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH, iv);
|
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
|
}
|
|
/**
|
* 生成随机字节数组
|
*/
|
private static byte[] generateRandomBytes(int length) {
|
byte[] bytes = new byte[length];
|
new SecureRandom().nextBytes(bytes);
|
return bytes;
|
}
|
|
|
|
public static void reset(String password) throws Exception {
|
|
// 生成新的salt和密钥
|
byte[] newSalt = generateRandomBytes(SALT_LENGTH);
|
SecretKey newSecretKey = deriveKey(password.toCharArray(), newSalt);
|
|
// 重新加密所有现有文件
|
List<CryptoCurrencyEnum> currencyEnums = CryptoCurrencyEnum.getAll();
|
for (CryptoCurrencyEnum currency : currencyEnums) {
|
Path encryptedDataFile = getEncryptedDataFile(FILE, currency.getName(), FILENAME );
|
if (Files.exists(encryptedDataFile)) {
|
try {
|
// 读取原有加密数据
|
String encryptedContent = new String(Files.readAllBytes(encryptedDataFile), StandardCharsets.UTF_8);
|
String[] parts = encryptedContent.split(":");
|
if (parts.length == 2) {
|
//获取地址
|
String address = loadAndDecrypt(currency.getName());
|
// 使用新密钥重新加密
|
byte[] newIv = generateRandomBytes(IV_LENGTH);
|
String newCiphertext = encryptData(address, newSecretKey, newIv);
|
String newEncryptedData = Base64.getEncoder().encodeToString(newIv) + ":" + newCiphertext;
|
|
// 写回文件
|
Files.write(encryptedDataFile, newEncryptedData.getBytes(StandardCharsets.UTF_8));
|
logger.info("已重置加密文件: " + encryptedDataFile);
|
|
}
|
} catch (Exception e) {
|
logger.warning("重置文件失败: " + encryptedDataFile + " - " + e.getMessage());
|
}
|
}
|
}
|
// 保存新的密钥材料
|
saveKeyMaterial(newSalt, newSecretKey.getEncoded());
|
logger.info("密钥已更新");
|
}
|
|
// 测试主方法
|
public static void main(String[] args) throws Exception {
|
//
|
String password = "123456";
|
|
|
|
//
|
String USDT_TRC20 = "usdt_trc20";
|
String USDT_ERC20 = "usdt_erc20";
|
String ETH = "eth";
|
String BTC = "btc";
|
// String USDC_TRC20 = "usdc_trc20";
|
// String USDC_ERC20 = "usdc_erc20";
|
String USDC_ERC20 = "usdc_erc20(1)";
|
// 加密并保存
|
// encryptAndStore(password, "1111111111", USDT_TRC20);
|
// encryptAndStore(password,"222222222",USDT_ERC20);
|
encryptAndStore(password,"3333333333",USDC_ERC20);
|
// encryptAndStore(password,"我是BTC",BTC);
|
// encryptAndStore(password,"44444444444444",USDC_TRC20);
|
// encryptAndStore(password,"55555555555555",USDC_ERC20);
|
//
|
List<CryptoCurrencyEnum> cryptoCurrencyEnums = CryptoCurrencyEnum.getAll();
|
cryptoCurrencyEnums.forEach(f->{
|
try {
|
String address = LocalKeyStorageAESUtil.loadAndDecrypt(f.getName());
|
System.out.println(address);
|
} catch (Exception ex) {
|
logger.info("------------"+f.getName()+"地址文件被更改-----------------");
|
}
|
});
|
// } catch (Exception e) {
|
// e.printStackTrace();
|
// }
|
// }
|
}
|
}
|