You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 组合,完全符合你的需求,同时满足安全规范:

  1. 支持无填充模式
  2. 兼容所有Java 7版本
  3. 增加完整性校验,避免篡改风险
  4. 随机IV生成,符合安全要求
  5. 密钥派生逻辑规范,支持任意长度的输入密钥

完整实现代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 07:15:08