
1. 项目概述为什么Java加密解密是开发者的必修课在当今这个数据即资产的时代无论是用户密码、交易信息还是配置文件、通信报文只要涉及数据的存储与传输加密解密技术就是一道绕不开的防线。作为一名Java开发者你可能每天都在与加密打交道只是未必察觉。比如你配置的数据库连接密码如果明文写在application.yml里无异于将家门钥匙挂在门把手上你调用第三方支付接口如果不使用HTTPS或对请求参数签名资金安全就无从谈起。面试官随口一问“MD5是加密算法吗”或者“AES的CBC模式和GCM模式有什么区别”就能让不少候选人语塞。这不仅仅是八股文更是实实在在的生产力与安全意识的体现。本文将从一线开发者的视角彻底拆解Java中的加密与解密技术不空谈理论只聚焦于那些你项目中马上就能用上、面试中大概率会被问到的核心知识点与实操细节。2. 加密解密核心概念与Java生态工具选型在动手写代码之前我们必须先理清几个关键概念这能帮助你在纷繁的工具和算法中做出正确选择。2.1 编码、加密与哈希别再傻傻分不清这是最容易混淆的三个概念也是面试高频考点。编码Encoding 目的是为了数据交换而非安全。它是一种可逆的转换使用公开的算法没有密钥。最常见的例子就是Base64。当你需要把二进制数据如图片、加密后的字节通过JSON、XML等文本协议传输时就需要用Base64将其编码成ASCII字符串。在Java中从JDK 8开始可以使用java.util.Base64类轻松进行编解码。import java.util.Base64; String original “Hello, Crypto!”; String encoded Base64.getEncoder().encodeToString(original.getBytes()); System.out.println(encoded); // SGVsbG8sIENyeXB0byE byte[] decoded Base64.getDecoder().decode(encoded); System.out.println(new String(decoded)); // Hello, Crypto!注意千万不要用Base64来“加密”敏感信息它只是换了一种表示形式任何人都可以轻松解码。哈希Hashing 目的是验证数据完整性或生成唯一摘要理论上是不可逆的单向函数。它接收任意长度的输入生成固定长度的输出哈希值。一个经典的误区就是称“MD5加密密码”正确的说法是“对密码进行MD5哈希”。在Java中你可以使用MessageDigest类。import java.security.MessageDigest; public static String md5(String input) throws Exception { MessageDigest md MessageDigest.getInstance(“MD5”); byte[] digest md.digest(input.getBytes()); // 将byte数组转换为十六进制字符串 StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(“%02x”, b)); } return sb.toString(); }哈希的典型应用密码存储 不存明文密码只存其哈希值。用户登录时对比输入密码的哈希值与存储的哈希值。但单纯使用MD5或SHA-1已不安全需加盐Salt并使用慢哈希算法如PBKDF2、bcrypt。文件完整性校验 下载文件后计算其SHA-256哈希值与官方提供的对比确保文件未被篡改。数据唯一标识 利用哈希生成数据的“指纹”。加密Encryption 这才是真正为了保密性而设计的可逆过程核心要素是算法和密钥。加密后的密文在没有密钥的情况下无法或极难恢复出明文。这正是本文要讨论的核心。2.2 对称加密 vs. 非对称加密场景决定选择选择哪种加密方式取决于你的具体场景。对称加密 加密和解密使用同一把密钥。就像你用同一把钥匙锁门和开门。优点 速度快效率高适合加密大量数据如文件内容、HTTP请求体。缺点密钥分发困难。如何安全地把密钥交给通信对方是个大问题。常见算法 AES高级加密标准最常用、DES已不安全、3DES。Java实现 主要通过javax.crypto.Cipher类。非对称加密 使用一对密钥公钥Public Key和私钥Private Key。公钥公开私钥自己严格保管。用公钥加密的数据只有对应的私钥能解密用私钥签名的数据任何人都可以用公钥验证签名真伪。优点 解决了密钥分发问题。任何人都可以用你的公钥加密信息发给你只有你能用私钥解密。缺点 速度慢比对称加密慢几个数量级不适合加密大量数据。常见算法 RSA最常用、ECC椭圆曲线加密更高效。典型应用密钥交换 实际通信中常用非对称加密来安全地传递一个临时生成的对称加密密钥即会话密钥。TLS/SSL握手过程就基于此原理。数字签名 用私钥对数据的哈希值进行加密生成签名。接收方用公钥解密签名得到哈希值再与计算的数据哈希对比即可验证数据完整性和发送方身份。2.3 Java中的加密支持JCA与JCEJava通过一套标准架构来提供加密服务主要由两部分组成JCA (Java Cryptography Architecture) 定义了加密服务的框架和接口如MessageDigest哈希、Signature签名、KeyPairGenerator密钥对生成器。JCE (Java Cryptography Extension) 是JCA的扩展提供了具体的加密、密钥交换和消息认证码MAC实现核心类就是Cipher。现代JDK如Oracle JDK 8、OpenJDK已经将JCE功能包含在标准发行版中无需单独安装。你的项目通常只需要依赖JDK本身就能获得强大的加密能力。但在某些受限环境如早期JDK版本或需要特定算法提供商时可能需要额外配置。3. 核心算法实战从哈希到对称与非对称加密理论说再多不如一行代码。我们直接进入实战环节看看在Java中如何具体使用这些算法。3.1 哈希算法实战以密码存储为例如前所述直接存储密码哈希是危险的彩虹表攻击。正确的做法是“加盐哈希”。实操使用PBKDF2WithHmacSHA256进行密码哈希import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.util.Base64; public class PasswordHasher { // 盐值长度建议至少16字节 private static final int SALT_LENGTH 16; // 迭代次数越高越安全但越慢建议10万次以上 private static final int ITERATIONS 100000; // 生成的密钥长度 private static final int KEY_LENGTH 256; /** * 生成加盐哈希密码 * param password 明文密码 * return 格式为 “算法:迭代次数:盐(base64):哈希密码(base64)” 的字符串 */ public static String hashPassword(String password) throws NoSuchAlgorithmException, InvalidKeySpecException { // 1. 生成随机盐 SecureRandom random new SecureRandom(); byte[] salt new byte[SALT_LENGTH]; random.nextBytes(salt); // 2. 使用PBKDF2生成哈希 PBEKeySpec spec new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH); SecretKeyFactory factory SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA256”); byte[] hash factory.generateSecret(spec).getEncoded(); // 3. 拼接存储信息 String encodedSalt Base64.getEncoder().encodeToString(salt); String encodedHash Base64.getEncoder().encodeToString(hash); return String.format(“pbkdf2:sha256:%d:%s:%s”, ITERATIONS, encodedSalt, encodedHash); } /** * 验证密码 * param inputPassword 用户输入的密码 * param storedPassword 数据库中存储的密码字符串 */ public static boolean verifyPassword(String inputPassword, String storedPassword) throws NoSuchAlgorithmException, InvalidKeySpecException { // 1. 解析存储的字符串 String[] parts storedPassword.split(“:”); if (parts.length ! 5 || !“pbkdf2”.equals(parts[0]) || !“sha256”.equals(parts[1])) { throw new IllegalArgumentException(“存储的密码格式无效”); } int iterations Integer.parseInt(parts[2]); byte[] salt Base64.getDecoder().decode(parts[3]); byte[] expectedHash Base64.getDecoder().decode(parts[4]); // 2. 用相同的参数计算输入密码的哈希 PBEKeySpec spec new PBEKeySpec(inputPassword.toCharArray(), salt, iterations, expectedHash.length * 8); SecretKeyFactory factory SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA256”); byte[] inputHash factory.generateSecret(spec).getEncoded(); // 3. 恒定时间比较防止时序攻击 return MessageDigest.isEqual(inputHash, expectedHash); } }实操心得盐值必须随机且唯一 每个用户的密码都要使用不同的随机盐防止攻击者用同一张彩虹表破解所有密码。使用MessageDigest.isEqual进行比较 不要用Arrays.equals前者是恒定时间比较能有效防御基于响应时间的侧信道攻击。迭代次数可配置 随着硬件性能提升迭代次数也应增加。可以将迭代次数也存储在哈希结果中方便未来升级。考虑使用更专业的库 对于全新项目强烈推荐使用如Spring Security Crypto中的BCryptPasswordEncoder或者jBCrypt库。它们封装了更优的算法bcrypt, scrypt和最佳实践。3.2 对称加密实战AES的GCM模式AES是目前最安全、最常用的对称加密算法。它有不同的工作模式如ECB, CBC, GCM和填充方案。ECB模式绝对不要用因为它会导致相同的明文块产生相同的密文块安全性很差。我们直接上手目前推荐的模式AES/GCM/NoPadding。GCM模式同时提供了加密和认证功能能确保密文不被篡改。实操使用AES-GCM加密解密文件import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Paths; import java.security.SecureRandom; public class AesGcmExample { private static final String ALGORITHM “AES/GCM/NoPadding”; private static final int TAG_LENGTH_BIT 128; // GCM认证标签长度必须是128, 120, 112, 104, 96之一 private static final int IV_LENGTH_BYTE 12; // GCM推荐IV长度为12字节 /** * 加密 * param key 密钥必须是16, 24或32字节对应AES-128, AES-192, AES-256 * param plaintext 明文 * return 包含IV、密文和认证标签的字节数组 */ public static byte[] encrypt(byte[] key, byte[] plaintext) throws Exception { SecretKey secretKey new SecretKeySpec(key, “AES”); Cipher cipher Cipher.getInstance(ALGORITHM); // 1. 生成随机IV初始化向量 byte[] iv new byte[IV_LENGTH_BYTE]; SecureRandom random new SecureRandom(); random.nextBytes(iv); // 2. 初始化Cipher为加密模式并传入IV GCMParameterSpec parameterSpec new GCMParameterSpec(TAG_LENGTH_BIT, iv); cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec); // 3. 执行加密 byte[] ciphertext cipher.doFinal(plaintext); // 4. 将IV和密文拼接在一起IV不需要保密但必须唯一 ByteBuffer byteBuffer ByteBuffer.allocate(iv.length ciphertext.length); byteBuffer.put(iv); byteBuffer.put(ciphertext); return byteBuffer.array(); } /** * 解密 * param key 密钥 * param ciphertextWithIv 加密方法返回的字节数组 * return 明文 */ public static byte[] decrypt(byte[] key, byte[] ciphertextWithIv) throws Exception { SecretKey secretKey new SecretKeySpec(key, “AES”); Cipher cipher Cipher.getInstance(ALGORITHM); // 1. 从字节数组中分离出IV和实际密文 ByteBuffer byteBuffer ByteBuffer.wrap(ciphertextWithIv); byte[] iv new byte[IV_LENGTH_BYTE]; byteBuffer.get(iv); byte[] ciphertext new byte[byteBuffer.remaining()]; byteBuffer.get(ciphertext); // 2. 初始化Cipher为解密模式 GCMParameterSpec parameterSpec new GCMParameterSpec(TAG_LENGTH_BIT, iv); cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec); // 3. 执行解密会自动验证认证标签 return cipher.doFinal(ciphertext); } // 示例加密一个文本文件 public static void main(String[] args) throws Exception { // 生成一个安全的随机密钥在实际中密钥需要安全存储如使用KeyStore SecureRandom sr new SecureRandom(); byte[] key new byte[32]; // AES-256 sr.nextBytes(key); String originalContent “这是一段需要加密的敏感文件内容。\n包含多行信息。”; byte[] plaintext originalContent.getBytes(“UTF-8”); // 加密 byte[] encryptedData encrypt(key, plaintext); Files.write(Paths.get(“encrypted.bin”), encryptedData); System.out.println(“文件已加密保存。”); // 解密 byte[] readEncryptedData Files.readAllBytes(Paths.get(“encrypted.bin”)); byte[] decryptedData decrypt(key, readEncryptedData); String recoveredContent new String(decryptedData, “UTF-8”); System.out.println(“解密后的内容\n” recoveredContent); System.out.println(“内容是否一致” originalContent.equals(recoveredContent)); } }注意事项与踩坑记录密钥管理是核心难题 上述示例中密钥在代码里生成这绝不适用于生产环境生产环境中密钥必须从安全的密钥管理系统如HashiCorp Vault、AWS KMS获取或使用Java KeyStoreJKS文件进行存储绝不能硬编码或写在配置文件中。IV必须唯一且随机 对于GCM模式同一个密钥下绝对不要重复使用IV否则会严重破坏安全性。代码中使用SecureRandom生成是正确做法。处理AEADBadTagException 解密时如果密文或认证标签在传输/存储过程中被篡改cipher.doFinal()会抛出AEADBadTagException。这意味着数据完整性校验失败你应该直接拒绝此次解密请求并记录安全日志。选择正确的密钥长度 AES-128通常已足够安全AES-256提供更高的安全边际。确保你的密钥字节数组长度是16、24或32。3.3 非对称加密实战RSA密钥对与数字签名非对称加密我们重点看两个场景加密小数据如对称密钥和数字签名。实操1生成RSA密钥对并加密解密import javax.crypto.Cipher; import java.security.*; import java.util.Base64; public class RsaExample { private static final String ALGORITHM “RSA”; private static final int KEY_SIZE 2048; // 至少2048位安全考虑不推荐1024位 // 生成密钥对 public static KeyPair generateKeyPair() throws NoSuchAlgorithmException { KeyPairGenerator keyGen KeyPairGenerator.getInstance(ALGORITHM); keyGen.initialize(KEY_SIZE); return keyGen.generateKeyPair(); } // 使用公钥加密 public static String encrypt(String plainText, PublicKey publicKey) throws Exception { Cipher cipher Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] cipherBytes cipher.doFinal(plainText.getBytes(“UTF-8”)); return Base64.getEncoder().encodeToString(cipherBytes); } // 使用私钥解密 public static String decrypt(String cipherText, PrivateKey privateKey) throws Exception { Cipher cipher Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] plainBytes cipher.doFinal(Base64.getDecoder().decode(cipherText)); return new String(plainBytes, “UTF-8”); } public static void main(String[] args) throws Exception { // 1. 生成密钥对 KeyPair keyPair generateKeyPair(); PublicKey publicKey keyPair.getPublic(); PrivateKey privateKey keyPair.getPrivate(); // 2. 待加密数据RSA不适合加密大量数据这里模拟一个会话密钥 String sessionKey “ThisIsASecretSessionKey123”; // 3. 公钥加密 String encrypted encrypt(sessionKey, publicKey); System.out.println(“加密后的会话密钥 (Base64): “ encrypted); // 4. 私钥解密 String decrypted decrypt(encrypted, privateKey); System.out.println(“解密后的会话密钥: “ decrypted); System.out.println(“解密是否成功: “ sessionKey.equals(decrypted)); } }重要限制 RSA算法有明文长度限制。对于2048位的密钥使用PKCS#1 v1.5填充时最多只能加密245字节左右的数据。因此它绝不能用于直接加密大文件或长报文其正确用途是加密一个随机的对称密钥如AES密钥。实操2使用RSA进行数字签名与验证数字签名用于验证数据的完整性和来源真实性。import java.security.*; import java.util.Base64; public class DigitalSignatureExample { private static final String SIGNATURE_ALGORITHM “SHA256withRSA”; // 生成签名 public static String sign(String data, PrivateKey privateKey) throws Exception { Signature signature Signature.getInstance(SIGNATURE_ALGORITHM); signature.initSign(privateKey); signature.update(data.getBytes(“UTF-8”)); byte[] digitalSignature signature.sign(); return Base64.getEncoder().encodeToString(digitalSignature); } // 验证签名 public static boolean verify(String data, String signatureBase64, PublicKey publicKey) throws Exception { Signature signature Signature.getInstance(SIGNATURE_ALGORITHM); signature.initVerify(publicKey); signature.update(data.getBytes(“UTF-8”)); byte[] signatureBytes Base64.getDecoder().decode(signatureBase64); return signature.verify(signatureBytes); } public static void main(String[] args) throws Exception { KeyPair keyPair RsaExample.generateKeyPair(); // 复用上面的密钥对生成方法 String importantMessage “订单号202310270001 支付金额999.99元”; // 发送方用私钥签名 String signature sign(importantMessage, keyPair.getPrivate()); System.out.println(“生成签名: “ signature); // 接收方用公钥验证 boolean isValid verify(importantMessage, signature, keyPair.getPublic()); System.out.println(“签名验证结果: “ isValid); // 模拟数据被篡改 String tamperedMessage “订单号202310270001 支付金额0.99元”; boolean isTamperedValid verify(tamperedMessage, signature, keyPair.getPublic()); System.out.println(“篡改后签名验证结果: “ isTamperedValid); // 应为 false } }实操心得签名的是哈希值SHA256withRSA意味着先对数据做SHA-256哈希再对哈希值用RSA私钥加密。所以签名过程本身不加密数据只保护哈希值。公钥分发 验证签名需要公钥。如何确保你拿到的公钥就是对方的真实公钥这依赖于公钥基础设施PKI和证书体系。在实际应用中如HTTPS公钥通常通过受信任的CA签发的数字证书来传递。算法选择 除了SHA256withRSA还可以考虑SHA256withECDSA基于椭圆曲线更高效签名更短。4. 生产环境中的密钥全生命周期管理聊了这么多算法你会发现加密体系的安全性强弱最终不取决于算法本身AES、RSA都很坚固而取决于密钥管理。密钥一旦泄露一切加密形同虚设。4.1 密钥存储告别硬编码和配置文件绝对禁止的做法// 灾难性代码示例 String aesKey “mySuperSecretKey123”; // 硬编码在源码中或者# application.yml - 同样危险 security: aes-key: mySuperSecretKey123推荐的生产级方案使用Java KeyStore (JKS或PKCS12) KeyStore是一个密码保护的文件可以存储私钥、公钥证书和对称密钥。// 从KeyStore加载密钥示例 KeyStore ks KeyStore.getInstance(“PKCS12”); try (InputStream is new FileInputStream(“/path/to/keystore.p12”)) { ks.load(is, “keystore-password”.toCharArray()); // 加载KeyStore需要密码 KeyStore.ProtectionParameter entryPassword new KeyStore.PasswordProtection(“key-entry-password”.toCharArray()); KeyStore.SecretKeyEntry secretKeyEntry (KeyStore.SecretKeyEntry) ks.getEntry(“my-aes-key”, entryPassword); SecretKey secretKey secretKeyEntry.getSecretKey(); // 现在可以使用secretKey进行加密解密了 }操作流程 使用keytool命令JDK自带生成KeyStore和密钥条目然后将keystore.p12文件放在服务器安全位置通过环境变量或配置中心传递访问密码。使用云服务商或专业的密钥管理服务KMSAWS KMS / GCP Cloud KMS / Azure Key Vault 这些服务负责密钥的安全生成、存储、轮换和访问审计。你的应用程序通过API调用向KMS请求加密解密操作或者请求一个“数据密钥”在本地使用。密钥本身永远不会离开KMS的安全边界。HashiCorp Vault 开源的密钥管理工具功能强大可以动态生成数据库凭据、管理加密密钥等。优势 集中管理、自动轮换、精细的访问权限控制、完整的操作审计日志。4.2 密钥轮换与版本控制密钥不能一成不变。定期轮换密钥是安全最佳实践。策略 为每个密钥设置一个激活日期和失效日期。系统同时支持新旧两个密钥用新密钥加密新数据用旧密钥解密老数据。待所有老数据都被访问并重新用新密钥加密后再彻底淘汰旧密钥。实现 可以在数据库中为加密字段增加一个key_version或key_id字段标识加密时使用的是哪个版本的密钥。加解密服务根据这个标识去查找对应的密钥。4.3 环境分离与权限最小化开发、测试、生产环境使用不同的密钥 绝对禁止用生产密钥在测试环境加密数据。应用程序权限 运行应用的服务器/容器账号只应拥有读取密钥或调用KMS解密API的最小必要权限不应有创建或删除密钥的权限。5. 典型应用场景与实战集成掌握了核心算法和密钥管理我们来看看如何将它们应用到具体的开发场景中。5.1 场景一数据库字段级加密需求用户手机号、身份证号等PII个人身份信息需要加密存储到数据库且在某些情况下需要支持模糊查询如手机号后4位查询。方案与挑战直接使用AES加密 问题在于相同的明文如相同的手机号加密后会产生不同的密文由于IV随机导致无法进行等值查询更别说模糊查询了。可搜索加密 这是一个前沿密码学领域方案复杂。实战折中方案 将字段拆分为“密文部分”和“可查询部分”。CREATE TABLE user_info ( id BIGINT PRIMARY KEY, name VARCHAR(100), -- 手机号全文加密存储用于精确匹配解密 phone_ciphertext TEXT NOT NULL, -- 手机号后4位明文或哈希存储用于模糊查询 phone_last_four CHAR(4), -- 加密使用的密钥版本或IV可拼接在密文中 -- key_version INT ... );操作流程存储时phone_ciphertext AES-GCM-Encrypt(fullPhoneNumber)phone_last_four RIGHT(fullPhoneNumber, 4)。精确查询时 应用层取出所有记录的phone_ciphertext在内存中解密后比对数据量大时性能差可结合分区或索引优化。模糊查询后4位时 直接对phone_last_four字段进行SQL查询WHERE phone_last_four ‘5678’。注意事项 此方案牺牲了部分隐私暴露了后4位需根据合规要求评估。也可对后4位进行哈希存储但就完全无法模糊查询了。5.2 场景二API接口参数签名防篡改需求 保证HTTP API请求在传输过程中不被篡改常用于支付回调、开放平台等场景。方案 使用HMAC哈希消息认证码。双方预先共享一个密钥Secret Key。发送方将请求参数按固定规则如按参数名ASCII码升序拼接成字符串加上时间戳然后用密钥生成HMAC签名将签名放在请求头如X-Signature中。接收方用同样的规则和密钥生成签名与请求头中的签名对比一致则通过。import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class HmacSigner { private static final String HMAC_ALGORITHM “HmacSHA256”; public static String sign(String data, String secret) throws Exception { Mac mac Mac.getInstance(HMAC_ALGORITHM); SecretKeySpec secretKeySpec new SecretKeySpec(secret.getBytes(“UTF-8”), HMAC_ALGORITHM); mac.init(secretKeySpec); byte[] hmacBytes mac.doFinal(data.getBytes(“UTF-8”)); return Base64.getEncoder().encodeToString(hmacBytes); } public static boolean verify(String data, String signature, String secret) throws Exception { String expectedSignature sign(data, secret); // 恒定时间比较 return MessageDigest.isEqual( Base64.getDecoder().decode(signature), Base64.getDecoder().decode(expectedSignature) ); } // 构建待签名字符串的示例 public static String buildSignString(String appId, long timestamp, String nonce, String body) { // 按规则拼接例如appIdxxxtimestampxxxnoncexxxbodyxxx return String.format(“appId%s×tamp%dnonce%sbody%s”, appId, timestamp, nonce, body); } }实操心得Secret Key管理 同样需要安全存储每个客户端分配不同的Secret。加入时效性 签名字符串中必须包含时间戳服务器端验证时检查时间戳是否在合理窗口内如±5分钟防止重放攻击。加入随机数 加入一个随机数nonce并在服务端缓存一段时间可以确保同一签名短时间内只能使用一次。5.3 场景三配置文件敏感信息加密使用Spring Boot时可以使用jasypt-spring-boot库对application.properties中的敏感信息进行加密。引入依赖dependency groupIdcom.github.ulisesbocchio/groupId artifactIdjasypt-spring-boot-starter/artifactId version3.0.5/version /dependency加密密码java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input“your_db_password” password“your_encryption_master_password” algorithmPBEWithMD5AndDES # 输出 ENC(加密后的字符串)修改配置文件# 原来的明文 # spring.datasource.password123456 # 改为 spring.datasource.passwordENC(加密后的字符串) # 指定解密密码可通过环境变量JASYPT_ENCRYPTOR_PASSWORD传入更安全 jasypt.encryptor.password${JASYPT_MASTER_PASSWORD:your_encryption_master_password}应用启动时jasypt会自动解密ENC(...)包裹的内容。关键点 加解密的“主密码”jasypt.encryptor.password必须通过环境变量、启动参数或安全的配置中心传入绝不能写在配置文件中。6. 常见问题、性能调优与安全陷阱规避在实际开发和运维中你会遇到各种各样的问题。6.1 常见异常与排查异常信息可能原因解决方案javax.crypto.BadPaddingException: Given final block not properly padded1. 密钥错误。2. 密文在传输/存储中被损坏。3. 加密和解密使用的算法/模式/填充不匹配。1. 确认密钥正确且一致。2. 检查密文完整性如Base64解码是否正确。3. 确保Cipher.getInstance(“AES/CBC/PKCS5Padding”)中的算法字符串完全一致。java.security.InvalidKeyException: Illegal key size受限制的策略文件导致。早期JDK默认限制了加密强度。对于Java 8 Update 151及以上版本默认已解除限制。如果使用旧版本需从Oracle官网下载并替换local_policy.jar和US_export_policy.jar两个JAR文件。java.security.InvalidAlgorithmParameterException: Cannot find any provider supporting AES/GCM/NoPaddingJDK版本过低或未包含相应算法提供者。确保使用Java 8或更高版本。GCM模式在Java 8中已得到支持。AEADBadTagException(GCM模式)密文被篡改、IV重复使用、密钥错误或认证标签验证失败。1. 确保数据未被篡改。2.绝对确保同一个密钥下IV是唯一的随机值。3. 检查密钥是否正确。6.2 性能考量与调优建议加密解密是CPU密集型操作在高并发场景下需要关注性能。对称加密远快于非对称加密 这是基本原则。大量数据加密务必使用AES。使用线程安全的Cipher实例Cipher对象不是线程安全的。频繁创建Cipher实例开销很大。建议使用ThreadLocal或对象池如Apache Commons Pool来缓存和复用Cipher实例。private static final ThreadLocalCipher AES_CIPHER ThreadLocal.withInitial(() - { try { return Cipher.getInstance(“AES/GCM/NoPadding”); } catch (Exception e) { throw new RuntimeException(“Failed to create Cipher”, e); } });选择更快的算法和模式 在相同安全强度下AES-GCM通常比AES-CBC略快因为它可以并行化。如果硬件支持AES-NI指令集性能会有数量级提升。批量操作 对于大量小数据可以考虑批量加密后再传输减少调用开销。6.3 必须规避的安全陷阱使用不安全的算法或模式 绝对禁止使用DES、RC4、ECB模式。使用AES时优先选择GCM或CBC模式需配合HMAC进行完整性验证。密钥硬编码或不当存储 重申密钥必须通过安全渠道管理。IV/Nonce重复使用 对于GCM、CBC等模式重复使用IV会导致严重的安全漏洞可能直接导致明文泄露。使用MD5或SHA-1进行密码哈希 这些算法速度太快且已存在碰撞漏洞。密码存储必须使用加盐的慢哈希函数PBKDF2, bcrypt, scrypt, Argon2。自行实现加密算法不要自己发明加密算法使用经过广泛验证的标准库和算法。忽略日志中的敏感信息 确保日志框架不会打印出密钥、明文密码、完整的加密数据等。配置日志级别和脱敏规则。加密解密不是魔法而是一套严谨的工程实践。从理解哈希、对称与非对称的区别开始到熟练使用JCA/JCE API实现AES-GCM和RSA签名再到最终直面生产环境中最棘手的密钥管理问题每一步都需要清晰的认知和谨慎的操作。记住安全的系统是一个过程而不是一个产品。持续关注密码学进展如后量子密码学定期审查和更新你的加密实践才能让你的应用在数据安全的道路上走得更稳。