package com.ruoyi.system.utils; import com.ruoyi.common.exception.ServiceException; import org.apache.commons.codec.digest.DigestUtils; import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Arrays; import java.util.Base64; /** * 门禁设备登录密码加解密工具。 */ public final class DoorDeviceCredentialCrypto { private static final String ENV_KEY = "DOOR_DEVICE_CREDENTIAL_KEY"; private static final String SYSTEM_PROPERTY_KEY = "door.device.credential.key"; private static final String PREFIX = "v1:"; private static final byte[] AAD = "FangKeJiDoorDeviceCredential".getBytes(StandardCharsets.UTF_8); private static final SecureRandom SECURE_RANDOM = new SecureRandom(); private DoorDeviceCredentialCrypto() { } public static String encrypt(String plainText) { if (plainText == null || plainText.length() == 0) { throw new ServiceException("设备登录密码不能为空"); } try { byte[] nonce = new byte[12]; SECURE_RANDOM.nextBytes(nonce); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey(), new GCMParameterSpec(128, nonce)); cipher.updateAAD(AAD); byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); byte[] payload = new byte[nonce.length + encrypted.length]; System.arraycopy(nonce, 0, payload, 0, nonce.length); System.arraycopy(encrypted, 0, payload, nonce.length, encrypted.length); return PREFIX + Base64.getEncoder().encodeToString(payload); } catch (ServiceException e) { throw e; } catch (Exception e) { throw new ServiceException("设备登录密码加密失败"); } } public static String decrypt(String cipherText) { if (cipherText == null || cipherText.length() == 0) { throw new ServiceException("设备未配置登录密码"); } if (!cipherText.startsWith(PREFIX)) { throw new ServiceException("设备登录密码密文版本不支持"); } try { byte[] payload = Base64.getDecoder().decode(cipherText.substring(PREFIX.length())); if (payload.length <= 28) { throw new IllegalArgumentException("invalid payload"); } byte[] nonce = Arrays.copyOfRange(payload, 0, 12); byte[] encrypted = Arrays.copyOfRange(payload, 12, payload.length); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, secretKey(), new GCMParameterSpec(128, nonce)); cipher.updateAAD(AAD); return new String(cipher.doFinal(encrypted), StandardCharsets.UTF_8); } catch (ServiceException e) { throw e; } catch (Exception e) { throw new ServiceException("设备登录密码解密失败,请检查主密钥"); } } private static SecretKeySpec secretKey() { String value = System.getProperty(SYSTEM_PROPERTY_KEY); if (value == null || value.trim().length() == 0) { value = System.getenv(ENV_KEY); } if (value == null || value.length() < 32) { throw new ServiceException(ENV_KEY + " 必须配置且长度不能少于32位"); } return new SecretKeySpec(Arrays.copyOf(DigestUtils.sha256(value), 16), "AES"); } }