| New file |
| | |
| | | package com.nq.utils; |
| | | |
| | | |
| | | import com.nq.common.ServerResponse; |
| | | |
| | | import javax.crypto.Cipher; |
| | | import javax.crypto.SecretKey; |
| | | import javax.crypto.SecretKeyFactory; |
| | | import javax.crypto.spec.GCMParameterSpec; |
| | | import javax.crypto.spec.PBEKeySpec; |
| | | import javax.crypto.spec.SecretKeySpec; |
| | | import java.io.File; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Path; |
| | | import java.nio.file.Paths; |
| | | import java.security.SecureRandom; |
| | | import java.util.Arrays; |
| | | import java.util.Base64; |
| | | import java.util.List; |
| | | 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("D:/aes/kf_aes_key.dat"); |
| | | private static final String FILE = "D:/aes/"; |
| | | private static final String FILENAME = ".dat"; |
| | | |
| | | /** |
| | | * 加密文本并保存到文件,加密前校验密码与派生密钥是否匹配 |
| | | * @param password 用户提供的密码 |
| | | * @param text 要加密的文本 |
| | | * @param symbolName 用于生成文件名 |
| | | * @return 操作结果 |
| | | * @throws Exception 加密过程中发生的任何异常 |
| | | */ |
| | | public static ServerResponse encryptAndStore(String password, String text, String symbolName) throws Exception { |
| | | try { |
| | | logger.info("开始加密过程..."); |
| | | |
| | | // // 1. 检查密钥文件是否存在 |
| | | // if (!Files.exists(KEY_FILE)) { |
| | | // return ServerResponse.createByErrorMsg("密钥文件丢失,请联系管理员"); |
| | | // } |
| | | |
| | | |
| | | |
| | | // 2. 加载现有密钥材料 |
| | | KeyMaterial existingKey = loadKeyMaterial(); |
| | | |
| | | // 4. 正常加密流程(非重置情况) |
| | | if (!validatePassword(password, existingKey.salt, existingKey.secretKey.getEncoded())) { |
| | | logger.info("修改地址密码验证失败!"); |
| | | return ServerResponse.createByErrorMsg("密码验证失败"); |
| | | } |
| | | |
| | | // 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 ServerResponse.createBySuccess(); |
| | | } 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; |
| | | } |
| | | |
| | | |
| | | } |