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




