1. 项目概述为什么AES在Java开发中如此重要如果你是一名Java开发者无论是处理用户密码、保护配置文件还是实现安全的网络通信加密都是一个绕不开的话题。在众多加密算法中AESAdvanced Encryption Standard高级加密标准无疑是出场率最高的明星选手。它不仅是美国国家标准与技术研究院NIST钦定的标准更是全球范围内金融、通信、软件等领域数据保护的基石。简单来说当你需要一种既安全又高效的对称加密方案时AES通常是你的首选。我接触AES加密已经超过十年从早期的Java Cryptography Architecture (JCA) 手动拼装到后来使用Spring Security等框架封装好的工具踩过的坑不计其数。比如你是否遇到过“Invalid AES key length”的报错或者在跨语言加解密时发现结果对不上这些问题背后往往是对AES的工作模式、填充方式或密钥处理理解不透彻。这篇文章我将从一个一线开发者的视角带你彻底搞懂Java中的AES实现。我们不仅会介绍核心概念更会深入到具体的应用场景并提供可直接“抄作业”的、生产环境验证过的示例代码。无论你是正在准备面试的求职者还是需要在项目中快速集成加密功能的老手这篇文章都能为你提供清晰的路径和实用的避坑指南。2. AES算法核心原理与Java实现机制拆解在动手写代码之前我们必须先理解AES到底是如何工作的。这能帮助你在遇到问题时不是盲目地搜索“AES解密失败怎么办”而是能精准地定位到是密钥、IV初始化向量、模式还是填充环节出了差错。2.1 AES算法的“心脏”对称加密与分组密码AES是一种对称分组密码算法。“对称”意味着加密和解密使用同一把密钥这就像你用同一把钥匙锁门和开门。它的高效性正源于此但同时也带来了密钥分发和管理的挑战。“分组”则指它并非一个字节一个字节地处理数据而是将明文数据分割成固定长度的“块”进行加密。AES的标准块大小是128位即16个字节。AES根据密钥长度的不同分为AES-128、AES-192和AES-256。密钥越长理论上安全性越高但加解密过程也会稍慢一些。在绝大多数商业和民用场景中AES-128已足够安全。Java的javax.crypto包为我们提供了操作这些核心组件的接口。注意选择密钥长度时需权衡安全与性能。对于普通业务数据AES-128是平衡之选若涉及极高安全要求如国家机密信息此处泛指安全等级极高的数据可考虑AES-256。但请注意Java默认的JCE策略文件可能限制使用256位密钥需要安装“无限强度管辖权策略文件”。2.2 工作模式与填充模式决定算法行为的“性格”单独一个分组加密是远远不够的。当你的数据超过一个块16字节时如何将这些块连接起来这就是工作模式Cipher Mode要解决的问题。同时数据长度不可能总是16字节的整数倍多出来的部分怎么处理这需要填充模式Padding Scheme。常见工作模式ECB (Electronic Codebook)最简单的模式每个块独立加密。致命缺点相同的明文块会生成相同的密文块容易暴露数据模式。绝对不要用于加密有意义的数据它更像一个教学示例。CBC (Cipher Block Chaining)最经典、最常用的模式。每个明文块在加密前会先与前一个密文块进行异或操作。第一个块则需要一个随机的初始化向量IV。CBC能有效隐藏数据模式但它是串行处理的不利于并行计算。GCM (Galois/Counter Mode)现代应用的首选尤其是网络通信。它同时提供了加密和认证功能能确保数据的机密性和完整性即数据未被篡改。GCM效率高且支持并行是TLS 1.2/1.3等协议中的宠儿。常见填充模式PKCS5Padding / PKCS7Padding最常用的填充方式。假设块大小为16字节最后一个块还差3个字节它会填充数值为0x03的3个字节。在Java中PKCS5Padding实际上指代的是PKCS7Padding。NoPadding不进行填充。这要求你的待加密数据长度必须是块大小的整数倍否则会抛出异常。在Java中我们通过一个称为“转换”的字符串来指定这些参数格式为算法/模式/填充例如AES/CBC/PKCS5Padding或AES/GCM/NoPadding。3. Java中实现AES加密解密的完整实操指南理论说再多不如一行代码。下面我将分步骤展示如何在Java中安全、正确地使用AES。我们将涵盖从密钥生成到完整加解密的每一个环节并重点讲解那些容易出错的地方。3.1 环境准备与核心API介绍Java通过javax.crypto包提供加密支持你无需引入额外依赖。核心类有三个KeyGenerator/SecretKeyFactory用于生成或还原密钥。Cipher加密和解密的核心引擎。IvParameterSpec用于封装初始化向量IV在CBC等模式中使用。首先我们来看一个生成AES密钥的标准方法。切记永远不要使用一个固定的、硬编码在代码中的字符串作为密钥import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Base64; public class AESKeyGenerator { public static SecretKey generateAESKey(int keySize) throws NoSuchAlgorithmException { // 1. 获取KeyGenerator实例指定算法为AES KeyGenerator keyGen KeyGenerator.getInstance(AES); // 2. 使用安全的随机数源初始化密钥生成器 SecureRandom secureRandom new SecureRandom(); keyGen.init(keySize, secureRandom); // keySize: 128, 192, 256 // 3. 生成密钥 return keyGen.generateKey(); } public static void main(String[] args) throws Exception { SecretKey secretKey generateAESKey(256); // 将密钥以Base64格式打印便于存储和传输 String encodedKey Base64.getEncoder().encodeToString(secretKey.getEncoded()); System.out.println(Generated AES-256 Key (Base64): encodedKey); } }实操心得SecureRandom是生成密码学安全随机数的关键。在Linux系统上它默认使用/dev/urandom这是一个良好的熵源。避免使用new Random()因为它产生的随机数可预测会严重削弱安全性。3.2 场景一使用CBC模式进行基础加解密CBC模式因其广泛的兼容性和可靠性仍然是许多遗留系统或内部系统的选择。下面是完整的示例。import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Base64; public class AESCBCExample { private static final String TRANSFORMATION AES/CBC/PKCS5Padding; /** * 加密 * param plaintext 明文 * param secretKey 密钥 * return 返回一个包含IV和密文的字符串通常用特定分隔符连接或封装成对象 */ public static String encrypt(String plaintext, SecretKey secretKey) throws Exception { Cipher cipher Cipher.getInstance(TRANSFORMATION); // 生成一个随机的16字节IV对于AES块大小 byte[] iv new byte[16]; SecureRandom secureRandom new SecureRandom(); secureRandom.nextBytes(iv); IvParameterSpec ivSpec new IvParameterSpec(iv); cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); byte[] cipherText cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); // 将IV和密文一起返回。IV不是秘密但必须唯一且随机。 // 常见做法IV 密文或者将IV作为密文的前缀。 byte[] combined new byte[iv.length cipherText.length]; System.arraycopy(iv, 0, combined, 0, iv.length); System.arraycopy(cipherText, 0, combined, iv.length, cipherText.length); return Base64.getEncoder().encodeToString(combined); } /** * 解密 * param combinedBase64 包含IV和密文的Base64字符串 * param secretKey 密钥 * return 明文 */ public static String decrypt(String combinedBase64, SecretKey secretKey) throws Exception { byte[] combined Base64.getDecoder().decode(combinedBase64); // 分离IV和密文 byte[] iv new byte[16]; byte[] cipherText new byte[combined.length - 16]; System.arraycopy(combined, 0, iv, 0, 16); System.arraycopy(combined, 16, cipherText, 0, cipherText.length); Cipher cipher Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); byte[] plaintextBytes cipher.doFinal(cipherText); return new String(plaintextBytes, StandardCharsets.UTF_8); } public static void main(String[] args) throws Exception { // 假设我们已经有一个SecretKey可以从上面的生成器获得 // 这里为了演示我们从一个Base64字符串还原实际生产环境应从安全存储如KMS、HashiCorp Vault获取 String base64Key 你的256位密钥Base64字符串; byte[] keyBytes Base64.getDecoder().decode(base64Key); SecretKey secretKey new javax.crypto.spec.SecretKeySpec(keyBytes, AES); String originalText 这是一段需要加密的敏感数据比如用户手机号13800138000; System.out.println(原始文本: originalText); String encrypted encrypt(originalText, secretKey); System.out.println(加密后 (Base64): encrypted); String decrypted decrypt(encrypted, secretKey); System.out.println(解密后: decrypted); System.out.println(匹配结果: originalText.equals(decrypted)); } }关键点解析与避坑指南IV的管理IV必须随机且唯一但不需要保密。同一个密钥下绝对不要重复使用同一个IV否则会严重降低安全性。常见的做法是将IV和密文一起存储或传输。异常处理Cipher.doFinal()可能抛出BadPaddingException。这通常意味着解密时使用的密钥、IV或密文不正确。切勿在捕获到此异常后直接返回“密码错误”等明确信息这会给攻击者提供侧信道信息。应该统一记录日志并返回一个模糊的错误提示。字符编码务必在getBytes()和new String()时明确指定字符编码如StandardCharsets.UTF_8避免因平台默认编码不同导致加解密结果不一致。3.3 场景二使用GCM模式进行认证加密推荐对于新项目我强烈推荐使用GCM模式。它解决了CBC模式可能存在的填充预言攻击Padding Oracle Attack风险并自带完整性校验。import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Base64; public class AESGCMExample { private static final String TRANSFORMATION AES/GCM/NoPadding; private static final int TAG_LENGTH_BIT 128; // 认证标签长度通常为128位 private static final int IV_LENGTH_BYTE 12; // GCM推荐使用12字节96位的IV效率最高 public static String encrypt(String plaintext, SecretKey secretKey) throws Exception { Cipher cipher Cipher.getInstance(TRANSFORMATION); byte[] iv new byte[IV_LENGTH_BYTE]; new SecureRandom().nextBytes(iv); GCMParameterSpec parameterSpec new GCMParameterSpec(TAG_LENGTH_BIT, iv); cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec); // GCM模式可以关联一些额外的认证数据AAD此处未使用 // cipher.updateAAD(...); byte[] cipherText cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); // 同样将IV和密文合并返回 byte[] combined new byte[iv.length cipherText.length]; System.arraycopy(iv, 0, combined, 0, iv.length); System.arraycopy(cipherText, 0, combined, iv.length, cipherText.length); return Base64.getEncoder().encodeToString(combined); } public static String decrypt(String combinedBase64, SecretKey secretKey) throws Exception { byte[] combined Base64.getDecoder().decode(combinedBase64); byte[] iv new byte[IV_LENGTH_BYTE]; byte[] cipherText new byte[combined.length - IV_LENGTH_BYTE]; System.arraycopy(combined, 0, iv, 0, IV_LENGTH_BYTE); System.arraycopy(combined, IV_LENGTH_BYTE, cipherText, 0, cipherText.length); Cipher cipher Cipher.getInstance(TRANSFORMATION); GCMParameterSpec parameterSpec new GCMParameterSpec(TAG_LENGTH_BIT, iv); cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec); byte[] plaintextBytes cipher.doFinal(cipherText); return new String(plaintextBytes, StandardCharsets.UTF_8); } // main方法类似省略... }GCM模式特别注意事项NoPaddingGCM模式本身使用NoPadding因为它是一种流密码模式不需要填充。认证失败如果密文或IV在传输中被篡改doFinal方法会抛出AEADBadTagException。这是一个安全特性直接告诉你数据不可信。IV长度虽然IV可以任意长度但NIST特别推荐96位12字节因为这是最安全且最高效的。我们的示例遵循了这一最佳实践。4. AES在真实项目中的应用场景剖析理解了如何实现我们再来看看AES在哪些具体场景中大放异彩。这能帮助你在设计系统时做出更合适的技术选型。4.1 场景一敏感配置信息加密你是否曾将数据库密码、API密钥等直接写在application.properties或application.yml里这是非常危险的做法。一种常见的改进方案是使用AES加密这些配置项在应用启动时解密。实现思路将明文密码如MySecretDBPassword123用AES加密得到密文。将密文和IV如果使用CBC写入配置文件同时将加密密钥单独保管如通过环境变量APP_ENCRYPTION_KEY传入或存放在云厂商的密钥管理服务中。应用启动时读取环境变量中的密钥解密配置文件中的密文获得真实密码。# application-encrypted.yml spring: datasource: password: ENC(密文Base64字符串) # 可以使用Spring Cloud Config的格式或自定义前缀实操心得千万不要把解密密钥也放在同一个Git仓库或配置中心里。密钥和密文必须分离存储。对于微服务架构可以考虑使用HashiCorp Vault或阿里云KMS等专业密钥管理服务。4.2 场景二网络传输数据保护如API通信在微服务间或客户端与服务器的API调用中虽然普遍使用HTTPSTLS进行通道加密但有时需要对传输的报文主体进行额外的端到端加密。例如支付通知回调为了确保即使回调URL被劫持或日志泄露敏感信息也不暴露。实现方案双方预先共享或通过非对称加密协商一个AES会话密钥。发送方使用AES-GCM模式加密请求体将密文和IV放在HTTP Body或自定义Header中发送。接收方使用相同的密钥解密并验证数据的完整性。// 伪代码示例API请求体加密 public class SecureApiClient { public String sendSecureData(String url, Object data, SecretKey sessionKey) throws Exception { ObjectMapper mapper new ObjectMapper(); String jsonBody mapper.writeValueAsString(data); // 使用GCM加密JSON字符串 String encryptedBody AESGCMExample.encrypt(jsonBody, sessionKey); // 构建请求可以将encryptedBody作为POST的JSON字段发送 // 例如{ciphertext: 加密后的Base64字符串} // 或者直接作为整个请求体需与后端约定 // 发送HTTP请求... return encryptedBody; } }这种方案提供了TLS之上的第二层保护适用于对数据安全有极端要求的场景。4.3 场景三数据库字段级加密某些法规如GDPR要求对特定的个人身份信息PII进行加密存储。全盘加密数据库TDE成本高此时可以对特定字段如身份证号、手机号、邮箱进行应用层AES加密。挑战与方案挑战加密后无法直接使用SQL的、LIKE进行查询和索引。方案精确匹配查询对查询条件用同样的密钥和算法加密后在数据库中进行密文匹配。但这要求每次查询的IV是确定的例如使用基于主键生成的确定性IV这会带来一定的安全风险需谨慎评估。模糊查询几乎无法实现。通常需要业务折中如只加密部分字段加密身份证后6位保留前几位用于查询或建立额外的、脱敏的查询索引。使用专门的加密数据库或插件如MySQL的AES_ENCRYPT/AES_DECRYPT函数但密钥管理仍在数据库层且不同数据库函数实现有差异跨数据库移植性差。字段加密示例应用层// 在数据入库前加密 public UserEntity saveUser(UserDTO userDTO) throws Exception { UserEntity entity new UserEntity(); entity.setName(userDTO.getName()); // 加密手机号 entity.setEncryptedPhone(AESCBCExample.encrypt(userDTO.getPhone(), dataEncryptionKey)); // 其他字段... return userRepository.save(entity); } // 在数据查询出后解密 public UserDTO getUserById(Long id) throws Exception { UserEntity entity userRepository.findById(id).orElseThrow(); UserDTO dto new UserDTO(); dto.setName(entity.getName()); // 解密密文手机号 dto.setPhone(AESCBCExample.decrypt(entity.getEncryptedPhone(), dataEncryptionKey)); return dto; }5. 开发中的常见陷阱与性能优化实战即使知道了正确做法在实际编码和系统集成中依然会遇到各种“坑”。下面是我总结的一些高频问题和优化建议。5.1 密钥管理最大的安全瓶颈问题密钥硬编码、写在配置文件、或通过不安全的方式分发。解决方案本地开发使用环境变量。例如在~/.bashrc中设置export APP_AES_KEYxxx代码中通过System.getenv(APP_AES_KEY)读取。服务器部署容器化环境使用Docker Secrets或Kubernetes Secrets。云环境使用AWS KMS、阿里云KMS、腾讯云KMS等。它们能提供密钥的创建、轮转、访问审计等全生命周期管理。自建服务部署HashiCorp Vault。密钥轮转定期更换密钥是安全最佳实践。设计系统时应考虑支持多版本密钥新数据用新密钥加密旧数据在后台逐步迁移或解密后用新密钥再加密。5.2 跨平台/跨语言加解密不一致问题Java加密的数据用Python或Node.js解密失败反之亦然。排查清单密钥确保密钥的字节序列完全一致。检查Base64编码/解码是否正确有无添加换行符。算法/模式/填充字符串确保两端字符串完全一致。AES/CBC/PKCS5Padding在Java中对应Python的AES.MODE_CBC和PKCS7填充Python的Crypto.Util.Padding中pad和unpad方法。IV处理确认IV的生成方式随机还是固定、长度CBC通常16字节GCM推荐12字节以及传输方式是否拼接在密文前。字符编码加解密操作的是字节数组。确保明文转换为字节数组时String.getBytes(“UTF-8”)和密文转换回来时编码一致。认证标签GCM模式确认GCM的认证标签长度通常128位。在Java中由GCMParameterSpec指定在其他语言中可能有默认值或需显式设置。一个实用的调试方法是在两端分别用相同的固定密钥、固定IV、固定明文进行一次加密对比输出的密文Hex或Base64格式。如果不同逐项比对上述清单。5.3 性能考量与最佳实践AES本身是高性能的对称加密算法但在高并发、大数据量场景下仍需注意Cipher对象复用Cipher.getInstance()是一个相对昂贵的操作。对于需要频繁加解密的场景如网关对每个请求体解密应该使用ThreadLocal或对象池来复用Cipher实例。private static final ThreadLocalCipher CIPHER_THREAD_LOCAL ThreadLocal.withInitial(() - { try { return Cipher.getInstance(AES/GCM/NoPadding); } catch (Exception e) { throw new RuntimeException(e); } });选择合适的工作模式GCM模式虽然安全但加解密计算量略大于CBC。在纯粹需要加密且环境可控无填充预言风险的内部系统CBC可能是一个性能稍好的选择。但安全优先除非经过严格评估否则默认选GCM。大文件加密不要一次性将整个文件读入内存进行doFinal。应使用Cipher.update()和Cipher.doFinal()进行流式处理。try (InputStream in new FileInputStream(input.txt); OutputStream out new FileOutputStream(encrypted.bin); CipherOutputStream cipherOut new CipherOutputStream(out, cipher)) { byte[] buffer new byte[8192]; int length; while ((length in.read(buffer)) ! -1) { cipherOut.write(buffer, 0, length); } } // CipherOutputStream会在close时自动调用doFinal5.4 典型异常与排查思路java.security.InvalidKeyException: Invalid AES key length: X bytes原因提供的密钥字节数组长度不符合AES要求16, 24, 32字节对应128, 192, 256位。解决检查密钥来源。如果是密码字符串不要直接getBytes()应使用PBKDF2Password-Based Key Derivation Function 2等算法从密码派生密钥。// 从密码生成固定长度密钥的正确方式 SecretKeyFactory factory SecretKeyFactory.getInstance(PBKDF2WithHmacSHA256); byte[] salt new byte[16]; // 随机盐值需要保存 SecureRandom.getInstanceStrong().nextBytes(salt); PBEKeySpec spec new PBEKeySpec(password.toCharArray(), salt, 65536, 256); // 迭代次数密钥长度 SecretKey tmp factory.generateSecret(spec); SecretKey aesKey new SecretKeySpec(tmp.getEncoded(), AES);javax.crypto.BadPaddingException: Given final block not properly padded原因解密时密钥错误、IV错误、密文被篡改、或加密/解密时使用的填充模式不匹配。解决这是最常见的异常。首先确认加解密双方的“转换”字符串算法/模式/填充完全一致。然后检查密钥和IV是否正确传递。java.security.InvalidAlgorithmParameterException: GCMParameterSpec ...原因GCM模式的IV长度不符合要求通常不是12字节或认证标签长度设置错误必须是96, 104, 112, 120, 128之一。解决严格使用12字节的IV并将标签长度设为128。6. 进阶话题密钥派生与集成Spring框架对于企业级应用直接使用原始密钥的场景较少更多是基于密码派生密钥并与现有框架如Spring集成。6.1 使用PBKDF2从密码派生安全密钥如前所述直接从用户输入的密码字符串生成密钥是不安全的。PBKDF2是一个标准的密钥派生函数它通过加入盐值和多次哈希迭代来增加暴力破解的难度。import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.util.Base64; public class PBKDF2KeyDerivation { public static SecretKey deriveKeyFromPassword(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { // 参数密码字符数组、盐、迭代次数越高越安全但越慢、期望的密钥长度位 int iterationCount 65536; int keyLength 256; // AES-256 PBEKeySpec spec new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength); SecretKeyFactory factory SecretKeyFactory.getInstance(PBKDF2WithHmacSHA256); byte[] keyBytes factory.generateSecret(spec).getEncoded(); return new SecretKeySpec(keyBytes, AES); } public static void main(String[] args) throws Exception { String userPassword MyStrongPassword!123; byte[] salt new byte[16]; new SecureRandom().nextBytes(salt); // 盐值必须随机且每个用户/每个密钥不同 SecretKey aesKey deriveKeyFromPassword(userPassword, salt); System.out.println(Salt (Base64): Base64.getEncoder().encodeToString(salt)); System.out.println(Derived Key (Base64): Base64.getEncoder().encodeToString(aesKey.getEncoded())); // 注意盐值必须和派生出的密钥一起保存用于后续解密。 } }6.2 在Spring Boot项目中优雅集成AES加解密你可以创建一些工具类或Spring Bean方便在整个项目中注入和使用。Component public class AESEncryptionService { private final SecretKey secretKey; private final String transformation AES/GCM/NoPadding; private static final int GCM_IV_LENGTH 12; private static final int GCM_TAG_LENGTH 128; Autowired public AESEncryptionService(Value(${app.encryption.key-base64}) String base64Key) { // 从配置中读取Base64编码的密钥 byte[] keyBytes Base64.getDecoder().decode(base64Key); this.secretKey new SecretKeySpec(keyBytes, AES); } public String encrypt(String plaintext) throws Exception { // ... 实现GCM加密同上文AESGCMExample Cipher cipher Cipher.getInstance(transformation); byte[] iv new byte[GCM_IV_LENGTH]; SecureRandom.getInstanceStrong().nextBytes(iv); GCMParameterSpec spec new GCMParameterSpec(GCM_TAG_LENGTH, iv); cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec); byte[] cipherText cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); // 组合IV和密文 return Base64.getEncoder().encodeToString(ByteBuffer.allocate(iv.length cipherText.length) .put(iv) .put(cipherText) .array()); } public String decrypt(String ciphertextBase64) throws Exception { // ... 实现GCM解密 byte[] combined Base64.getDecoder().decode(ciphertextBase64); ByteBuffer buffer ByteBuffer.wrap(combined); byte[] iv new byte[GCM_IV_LENGTH]; buffer.get(iv); byte[] cipherText new byte[buffer.remaining()]; buffer.get(cipherText); Cipher cipher Cipher.getInstance(transformation); cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(GCM_TAG_LENGTH, iv)); byte[] plaintextBytes cipher.doFinal(cipherText); return new String(plaintextBytes, StandardCharsets.UTF_8); } }然后你就可以在Service中轻松注入并使用它Service public class UserService { Autowired private AESEncryptionService encryptionService; public void saveUser(User user) { String encryptedPhone encryptionService.encrypt(user.getPhone()); // ... 保存encryptedPhone到数据库 } public User getUser(Long id) { // ... 从数据库查询出实体其中包含加密的phone字段 String decryptedPhone encryptionService.decrypt(entity.getEncryptedPhone()); user.setPhone(decryptedPhone); return user; } }通过这样的封装业务代码无需关心加密解密的细节只需调用简单的encrypt和decrypt方法符合单一职责原则也使代码更易于维护和测试。