Java7兼容AES加密优化:解决加密输出密码长度过短问题
适配Java 7的AES安全加解密实现方案
核心问题说明
你观察到的密文长度过短是正常现象:AES的CFB/OFB/CBC无填充模式下,密文长度和明文长度完全相等,你测试的明文safe仅4字节,Base64编码后自然只有6字符加==后缀。如果需要更长的输出,可以在加密前给明文添加固定长度的随机前缀,解密时丢弃前缀即可。
原代码安全缺陷
- 密钥长度不合法:AES仅支持128/192/256位固定长度密钥,直接将输入字符串的字节作为密钥,长度不匹配时会直接报错
- IV生成逻辑错误:IV使用密钥的MD5值生成,相同密钥的IV固定不变,严重降低加密安全性,IV必须每次加密随机生成
- 无完整性校验:密文被篡改后无法识别,存在被攻击的风险
- 使用不安全的MD5算法:MD5已被证实存在碰撞漏洞,不适合用于加密场景
最优实现方案(兼容Java 7全版本)
选用AES/CBC/NoPadding + HMAC-SHA256 组合,完全符合你的需求,同时满足安全规范:
- 支持无填充模式
- 兼容所有Java 7版本
- 增加完整性校验,避免篡改风险
- 随机IV生成,符合安全要求
- 密钥派生逻辑规范,支持任意长度的输入密钥
完整实现代码
import java.security.MessageDigest; import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.xml.security.utils.Base64; public class StringEncrypt { private static final String AES_ALG = "AES"; private static final String CIPHER_MODE = "AES/CBC/NoPadding"; private static final String HMAC_ALG = "HmacSHA256"; private static final int AES_BLOCK_SIZE = 16; private static final int HMAC_SIZE = 32; // 可调整前缀长度,数值越大最终输出的密文越长 private static final int RANDOM_PREFIX_LENGTH = 16; static { // 初始化Apache XML Security的Base64依赖 org.apache.xml.security.Init.init(); } public static String encrypt(String cleartext, String key) throws Exception { // 1. 生成16字节随机IV SecureRandom random = new SecureRandom(); byte[] iv = new byte[AES_BLOCK_SIZE]; random.nextBytes(iv); // 2. 派生AES密钥:将输入密钥通过SHA-256哈希得到256位密钥,取前16字节作为128位AES密钥,兼容所有Java7默认配置 MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); byte[] keyBytes = sha256.digest(key.getBytes("UTF-8")); byte[] aesKey = new byte[AES_BLOCK_SIZE]; System.arraycopy(keyBytes, 0, aesKey, 0, AES_BLOCK_SIZE); // 3. 给明文添加随机前缀+补位到16字节倍数,适配NoPadding要求 byte[] clearBytes = cleartext.getBytes("UTF-8"); int totalLength = RANDOM_PREFIX_LENGTH + clearBytes.length; int padLength = AES_BLOCK_SIZE - (totalLength % AES_BLOCK_SIZE); byte[] paddedClear = new byte[totalLength + padLength]; // 填充随机前缀 random.nextBytes(paddedClear, 0, RANDOM_PREFIX_LENGTH); // 填充明文 System.arraycopy(clearBytes, 0, paddedClear, RANDOM_PREFIX_LENGTH, clearBytes.length); // 末尾补0填充到块大小倍数 for (int i = RANDOM_PREFIX_LENGTH + clearBytes.length; i < paddedClear.length; i++) { paddedClear[i] = 0; } // 4. AES加密 Cipher cipher = Cipher.getInstance(CIPHER_MODE); SecretKeySpec skeySpec = new SecretKeySpec(aesKey, AES_ALG); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivSpec); byte[] encrypted = cipher.doFinal(paddedClear); // 5. 生成HMAC校验值 Mac mac = Mac.getInstance(HMAC_ALG); SecretKeySpec hmacKey = new SecretKeySpec(keyBytes, HMAC_ALG); mac.init(hmacKey); mac.update(iv); mac.update(encrypted); byte[] hmac = mac.doFinal(); // 6. 拼接IV + 密文 + HMAC,Base64编码后输出 byte[] result = new byte[iv.length + encrypted.length + hmac.length]; System.arraycopy(iv, 0, result, 0, iv.length); System.arraycopy(encrypted, 0, result, iv.length, encrypted.length); System.arraycopy(hmac, 0, result, iv.length + encrypted.length, hmac.length); return Base64.encode(result); } public static String decrypt(String encryptedStr, String key) throws Exception { byte[] encryptedData = Base64.decode(encryptedStr); // 拆分IV、密文、HMAC byte[] iv = new byte[AES_BLOCK_SIZE]; byte[] encrypted = new byte[encryptedData.length - AES_BLOCK_SIZE - HMAC_SIZE]; byte[] hmac = new byte[HMAC_SIZE]; System.arraycopy(encryptedData, 0, iv, 0, AES_BLOCK_SIZE); System.arraycopy(encryptedData, AES_BLOCK_SIZE, encrypted, 0, encrypted.length); System.arraycopy(encryptedData, AES_BLOCK_SIZE + encrypted.length, hmac, 0, HMAC_SIZE); // 派生密钥 MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); byte[] keyBytes = sha256.digest(key.getBytes("UTF-8")); byte[] aesKey = new byte[AES_BLOCK_SIZE]; System.arraycopy(keyBytes, 0, aesKey, 0, AES_BLOCK_SIZE); // 校验HMAC Mac mac = Mac.getInstance(HMAC_ALG); SecretKeySpec hmacKey = new SecretKeySpec(keyBytes, HMAC_ALG); mac.init(hmacKey); mac.update(iv); mac.update(encrypted); byte[] calcHmac = mac.doFinal(); if (!MessageDigest.isEqual(hmac, calcHmac)) { throw new SecurityException("密文被篡改或密钥错误"); } // AES解密 Cipher cipher = Cipher.getInstance(CIPHER_MODE); SecretKeySpec skeySpec = new SecretKeySpec(aesKey, AES_ALG); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec); byte[] decryptedPadded = cipher.doFinal(encrypted); // 去掉随机前缀和末尾的填充0 int clearLength = 0; for (int i = RANDOM_PREFIX_LENGTH; i < decryptedPadded.length; i++) { if (decryptedPadded[i] != 0) { clearLength = i - RANDOM_PREFIX_LENGTH + 1; } else { break; } } byte[] clearBytes = new byte[clearLength]; System.arraycopy(decryptedPadded, RANDOM_PREFIX_LENGTH, clearBytes, 0, clearLength); return new String(clearBytes, "UTF-8"); } public static void main(String[] args) throws Exception { // 测试用例:明文safe,密钥Bk206V4ytQ1zZAukPE6/2c5KUcxGYpBf String plaintext = "safe"; String key = "Bk206V4ytQ1zZAukPE6/2c5KUcxGYpBf"; String encrypted = encrypt(plaintext, key); System.out.println("加密后结果:" + encrypted); System.out.println("加密后长度:" + encrypted.length()); String decrypted = decrypt(encrypted, key); System.out.println("解密后结果:" + decrypted); if (plaintext.equals(decrypted)) { System.out.println("[OK] 解密正确"); } else { System.out.println("[KO] 解密错误"); } } }
补充说明
- 可通过调整
RANDOM_PREFIX_LENGTH参数控制最终密文长度,数值越大密文越长 - 代码默认使用128位AES,兼容所有Java7默认环境,如需使用256位AES,需安装Oracle官方的JCE无限强度权限包
- 如果需要切换为CFB/OFB无填充模式,只需修改
CIPHER_MODE常量值为对应模式即可,其他逻辑无需调整
内容的提问来源于stack exchange,提问作者JLLMNCHR
相关产品推荐
相关产品推荐

