You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android AES解密返回异常字符,解密实现遇问题求助

解决AES解密出现乱码/罕见字符的问题

看起来你遇到的问题几乎可以肯定是AES算法的模式与填充方式未正确指定导致的!从你给出的代码片段里看到AES_MODE只定义了"AES",这是个常见误区——只写算法名的话,不同系统/平台会使用不同的默认模式和填充规则,而且这些默认值往往不符合安全规范,还会导致加密解密的参数不匹配,最终解密出乱码。

接下来给你一步步分析和修正方案:

核心问题分析

AES算法不能只指定算法名,必须明确模式(Mode)填充方式(Padding),比如AES/CBC/PKCS5Padding。如果只写"AES",Android默认会用AES/ECB/PKCS5Padding,但ECB模式是不安全的,而且如果加密时用了其他模式/填充,解密就会完全失败,出现你看到的罕见字符。

另外还有两个容易踩的坑:

  • 密钥长度不符合AES要求:AES只支持128/192/256位密钥(对应16/24/32字节),直接用用户输入的密码字符串转字节数组,大概率长度不对。
  • 缺少初始化向量(IV):除了ECB模式(不推荐),其他模式比如CBC/GCM都需要IV,且加密解密必须用同一个IV。

修正后的代码示例

下面是一个安全且能正常工作的AES加密解密实现,以AES/CBC/PKCS5Padding为例:

1. 先定义完整的AES模式

private static final String AES_MODE = "AES/CBC/PKCS5Padding";
private static final String KEY_ALGORITHM = "PBKDF2WithHmacSHA256";
private static final int KEY_SIZE = 256;
private static final int ITERATION_COUNT = 65536;

2. 密钥派生函数(把用户密码转成符合要求的AES密钥)

private SecretKey generateKey(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
    char[] passwordChars = password.toCharArray();
    // 盐值要随机,这里可以把盐值和密文一起保存,解密时用同一个盐
    byte[] salt = new byte[16];
    new SecureRandom().nextBytes(salt);
    
    KeySpec spec = new PBEKeySpec(passwordChars, salt, ITERATION_COUNT, KEY_SIZE);
    SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
    return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
}

3. 加密方法(包含IV生成)

public String encrypt(String plainText, String password) throws Exception {
    SecretKey key = generateKey(password);
    Cipher cipher = Cipher.getInstance(AES_MODE);
    
    // 生成随机IV(16字节,AES块大小)
    byte[] iv = new byte[cipher.getBlockSize()];
    new SecureRandom().nextBytes(iv);
    IvParameterSpec ivParams = new IvParameterSpec(iv);
    
    cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
    byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
    
    // 把IV和密文拼接在一起,方便解密时取出
    byte[] result = new byte[iv.length + encryptedBytes.length];
    System.arraycopy(iv, 0, result, 0, iv.length);
    System.arraycopy(encryptedBytes, 0, result, iv.length, encryptedBytes.length);
    
    // 转成Base64字符串方便存储/传输
    return Base64.encodeToString(result, Base64.DEFAULT);
}

4. 解密方法(先取出IV再解密)

public String decrypt(String encryptedText, String password) throws Exception {
    byte[] encryptedData = Base64.decode(encryptedText, Base64.DEFAULT);
    
    // 取出前面的IV
    Cipher cipher = Cipher.getInstance(AES_MODE);
    int blockSize = cipher.getBlockSize();
    byte[] iv = new byte[blockSize];
    System.arraycopy(encryptedData, 0, iv, 0, blockSize);
    byte[] encryptedBytes = new byte[encryptedData.length - blockSize];
    System.arraycopy(encryptedData, blockSize, encryptedBytes, 0, encryptedBytes.length);
    
    // 生成密钥(注意盐值要和加密时一致,这里可以把盐值也存在密文里,示例中简化了)
    SecretKey key = generateKey(password);
    IvParameterSpec ivParams = new IvParameterSpec(iv);
    
    cipher.init(Cipher.DECRYPT_MODE, key, ivParams);
    byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
    
    return new String(decryptedBytes, StandardCharsets.UTF_8);
}

关键注意事项

  • 加密解密参数必须完全一致:模式、填充、密钥、IV,任何一项不匹配都会导致解密乱码。
  • 不要用ECB模式:ECB模式没有使用IV,相同的明文会生成相同的密文,容易被破解,优先选择CBC或更安全的GCM模式(GCM还能提供完整性校验)。
  • 盐值和IV要随机生成:盐值用于密钥派生,每次加密最好用不同的盐;IV用于加密模式,每次加密必须用不同的IV,这两者都不需要保密,但要和密文一起保存。

内容的提问来源于stack exchange,提问作者Emilio Calderon Vergara

火山引擎 最新活动